📌 Note: This upgrade kit requires Custom Fields, which are only available on our Pro, Enterprise or Network plans. For help adjusting your plan please contact support at [email protected].
Supported themes: Raise, Momentum, Action, Aware
How it works
The form uses progressive disclosure. Nothing is pre-selected, ensuring donors who ignore the feature are not served a half-filled default.
Page Load: A single checkbox appears: "This gift is in honor or memory of someone."
Box Checked: The panel expands revealing the "In Honor of" and "In Memory of" radio options.
Type Chosen: The text field expands to capture the "Full name of the person being honored or remembered."
Unchecking the box collapses the panel, clears the radio selection, and empties the name field. Because an unchecked radio group submits no key at all, a donation made without opening the section carries no tribute data.
Both values are written directly to the donation record, visible at Finances > Donations > Listing > [Edit] > Custom fields. They are filterable, included in CSV exports, and readable via API.
Before you begin
You will need:
Admin access to your NationBuilder control panel
Ability and Permissions to add Custom Fields
Access to your theme files via Theme > Current Custom Theme > Edit code
Basic familiarity with editing NationBuilder theme templates
Part 1 — Create the custom fields
You need to create two donation custom fields in your NationBuilder admin before adding any code to your theme. These fields are where the tribute data will be stored on each donation record.
Go to Settings > Defaults > Custom fields > Donations. Create the following two fields exactly as written below.
📌 Important: The slug is entered at creation time, cannot contain spaces, and cannot be changed afterward. The code references both slugs directly, so exact spelling is required.
Setting | Value |
Label | Tribute type |
Slug |
|
Type | Multiple choice |
Option 1 | In Honor of |
Option 2 | In Memory of |
Note on options: Use human-readable text for the options. NationBuilder uses the exact option text as both the stored value and the display text. Using human-readable strings ensures your admin records, CSV exports, and Finance filters are easily readable without translation.
Setting | Value |
Label | Tribute name |
Slug |
|
Type | Text |
Verify your fields: Load your live donation page and view the page source. Because custom fields auto-render, they will appear at the bottom of your form. Confirm the <select> for tribute type contains <option> strings that exactly match what you typed, including capitalization and spacing.
Part 2 — Add the shared CSS
This CSS applies to all four themes. Paste this block at the very bottom of theme.scss (Website > Theme > Current custom theme > Files > theme.scss).
/* ============================================
Tribute fields — shared base (all themes)
============================================ */
.tribute-block {
margin: 20px 0 10px;
}
/* The `hidden` attribute is the single source of truth for visibility.
!important guards against theme rules that set display on these containers. */
.tribute-block [hidden] {
display: none !important;
}
.tribute-toggle-row {
display: flex;
align-items: flex-start;
gap: 9px;
}
.tribute-toggle-row input[type="checkbox"] {
margin: 0;
flex: 0 0 auto;
position: relative;
top: 4px;
}
.tribute-toggle-row label {
margin: 0;
cursor: pointer;
font-weight: 600;
}
.tribute-panel {
margin-top: 14px;
padding: 16px 18px;
border: 1px solid rgba(0, 0, 0, .12);
border-radius: 4px;
background: rgba(0, 0, 0, .02);
}
/* Reset fieldset inheritance */
.tribute-type {
border: 0;
margin: 0 0 14px;
padding: 0;
min-width: 0;
}
.tribute-type-row {
display: flex;
flex-wrap: wrap;
gap: 10px 26px;
}
.tribute-radio {
display: flex;
align-items: center;
gap: 7px;
}
.tribute-radio input[type="radio"] {
margin: 0;
flex: 0 0 auto;
}
.tribute-radio label {
margin: 0;
cursor: pointer;
font-weight: normal;
}
.tribute-name-field {
margin-bottom: 0;
}
.tribute-name-field label {
display: block;
margin-bottom: 4px;
}
.tribute-name-field input[type="text"] {
max-width: 420px;
}
/* Visually hidden, still read aloud (Scoped to support Aware) */
.tribute-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
4. Save the file.
Part 3 — Add the JavaScript
Paste this script at the very bottom of pages_show_donation_v2_wide.html, immediately after the {% endform_for %} tag (next to the existing theme_donation_v2.js tag).
Do not wrap this in $(document).ready(): The script must sit after the markup to run at parse time, ensuring the cleanup handler binds before NationBuilder's deferred payment module fires its own submit listener.
<script>
/* ============================================
Tribute fields — 3-level progressive disclosure
Runs at parse time. No document.ready.
============================================ */
(function ($) {
'use strict';
if (!$) { return; }
var TYPE_NAME = 'donation[custom_values][tribute_type]';
var NAME_ID = 'donation_custom_values_tribute_name_custom';
var $toggle = $('#tribute-toggle');
if (!$toggle.length) { return; }
var $panel = $('#tribute-panel');
var $radios = $('input[name="' + TYPE_NAME + '"]');
var $nameField = $('#tribute-name-field');
var $name = $('#' + NAME_ID);
var motion = window.matchMedia('(prefers-reduced-motion: reduce)');
var booting = true;
function ms() { return (booting || motion.matches) ? 0 : 200; }
function reveal($el) {
if ($el.data('tributeOpen')) { return; }
$el.data('tributeOpen', true)
.stop(true, true)
.prop('hidden', false)
.css('display', 'none')
.slideDown(ms());
}
function conceal($el) {
if (!$el.data('tributeOpen') && $el.prop('hidden')) { return; }
$el.data('tributeOpen', false);
if ($el[0].contains(document.activeElement)) { $toggle.trigger('focus'); }
$el.stop(true, true).slideUp(ms(), function () {
if (!$el.data('tributeOpen')) {
$el.prop('hidden', true).css('display', '');
}
});
}
function enforce() {
var on = $toggle.prop('checked');
if (!on) { $radios.prop('checked', false); }
if (!on || !$radios.filter(':checked').length) { $name.val(''); }
$name.prop('required', false);
}
function render() {
var on = $toggle.prop('checked');
var chosen = $radios.filter(':checked').length > 0;
$toggle.attr('aria-expanded', String(on));
if (on) { reveal($panel); } else { conceal($panel); }
if (on && chosen) { reveal($nameField); } else { conceal($nameField); }
}
function sync() { enforce(); render(); }
$toggle.on('change', sync);
$radios.on('change', sync);
sync();
booting = false;
var form = document.querySelector('form[data-form="donation"]');
if (form) {
form.addEventListener('submit', enforce);
}
}(window.jQuery));
</script>
Part 4 — Add the HTML/Liquid tribute block
Step 4: Theme-Specific Code
4A: Suppress the Automatic Loop (All Themes)
Because NationBuilder auto-renders custom fields, you must suppress the default output so the fields don't render twice.
In pages_show_donation_v2_wide.html, locate the {% for cf in custom_fields.donation %} loop and add the bolded {% continue %} line immediately after the assign tag:
{% for cf in custom_fields.donation %}
{% assign custom_field = cf[1] %}
{% if custom_field.slug == 'tribute_type' or custom_field.slug == 'tribute_name' %}{% continue %}{% endif %}
{% assign custom_field_id = 'custom_values.' | append: custom_field.slug %}
{% comment %} …the rest of the stock loop, unchanged… {% endcomment %}
{% endfor %}
4B: Insert the Tribute Block
Place the HTML block based on your theme directly into pages_show_donation_v2_wide.html.
Placement rules:
Put it: After the employer/occupation block (
is_employer_and_occupation_required?) and before the opt-in checkboxes (email_opt_in).Never inside
#address-info: NB hides this region on Apple Pay/Google Pay paths.Never inside
#cc-infoor.payment-input: Rebuilt dynamically by NB's UI modifier.Never inside
.errorExplanation: Handled dynamically on form errors.
Choose your theme below for the specific Liquid and CSS additions.
Raise
Open raise/pages_show_donation_v2_wide.html and paste the following block at the insertion point described above.
HTML/Liquid:
{% comment %} ===== TRIBUTE FIELDS ===== {% endcomment %}
{% if custom_fields.donation.tribute_type %}
<div class="form-row">
<div class="form-group col-md-12">
<div class="tribute-block">
<div class="tribute-toggle-row">
<input type="checkbox" id="tribute-toggle" aria-expanded="false" aria-controls="tribute-panel">
<label for="tribute-toggle">This gift is in honor or memory of someone</label>
</div>
<div id="tribute-panel" class="tribute-panel" hidden>
<fieldset class="tribute-type">
<legend class="tribute-sr-only">Is this gift in honor of or in memory of someone?</legend>
<div class="tribute-type-row">
{% for opt in custom_fields.donation.tribute_type.multiple_choice_options %}
<div class="tribute-radio">
<input type="radio" id="tribute-type-{{ forloop.index }}" name="donation[custom_values][tribute_type]" value="{{ opt }}" aria-controls="tribute-name-field">
<label for="tribute-type-{{ forloop.index }}">{{ opt }}</label>
</div>
{% endfor %}
</div>
</fieldset>
<div id="tribute-name-field" class="tribute-name-field form-group" hidden>
<label for="donation_custom_values_tribute_name_custom">Full name of the person being honored or remembered</label>
{% text_field "custom_values.tribute_name", class:"text form-control", autocomplete:"off" %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% comment %} ===== END TRIBUTE FIELDS ===== {% endcomment %}
CSS (Append to theme.scss):
.tribute-block {
.tribute-block {
.tribute-panel {
border-color: rgba(0, 0, 0, .10);
background: rgba(0, 0, 0, .015);
}
.tribute-name-field label {
position: static;
transform: none;
font-size: .875rem;
opacity: 1;
}
}
Momentum
Note: Momentum supports multilingual translations. For full translation support, create variables in your _translation_en.html (and other languages) for the static text, as Momentum's theme architecture requires.
HTML/Liquid:
{% comment %} ===== TRIBUTE FIELDS ===== {% endcomment %}
{% if custom_fields.donation.tribute_type %}
<div class="form-row">
<div class="form-group col-md-12">
<div class="tribute-block">
<div class="tribute-toggle-row">
<input type="checkbox" id="tribute-toggle" aria-expanded="false" aria-controls="tribute-panel">
<label for="tribute-toggle">This gift is in honor or memory of someone</label>
</div>
<div id="tribute-panel" class="tribute-panel" hidden>
<fieldset class="tribute-type">
<legend class="tribute-sr-only">Is this gift in honor of or in memory of someone?</legend>
<div class="tribute-type-row">
{% for opt in custom_fields.donation.tribute_type.multiple_choice_options %}
<div class="tribute-radio custom-control custom-radio">
<input type="radio" class="custom-control-input" id="tribute-type-{{ forloop.index }}" name="donation[custom_values][tribute_type]" value="{{ opt }}" aria-controls="tribute-name-field">
<label class="custom-control-label" for="tribute-type-{{ forloop.index }}">{{ opt }}</label>
</div>
{% endfor %}
</div>
</fieldset>
<div id="tribute-name-field" class="tribute-name-field form-group" hidden>
<label for="donation_custom_values_tribute_name_custom">Full name of the person being honored or remembered</label>
{% text_field "custom_values.tribute_name", class:"form-control", autocomplete:"off" %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% comment %} ===== END TRIBUTE FIELDS ===== {% endcomment %}
CSS (Append to theme.scss):
.tribute-block {
.tribute-panel {
border-color: #dde1e5;
background: #f8f9fa;
}
.tribute-radio.custom-radio {
gap: 0;
}
.tribute-name-field label {
font-size: .875rem;
margin-bottom: 6px;
}
}
Action
Open action/pages_show_donation_v2_wide.html. The Action theme uses a single-column form layout — the tribute block spans the full form width.
HTML/Liquid:
{% comment %} ===== TRIBUTE FIELDS ===== {% endcomment %}
{% if custom_fields.donation.tribute_type %}
<div class="row">
<div class="col-sm-12">
<div class="tribute-block">
<div class="tribute-toggle-row">
<input type="checkbox" id="tribute-toggle" aria-expanded="false" aria-controls="tribute-panel">
<label for="tribute-toggle">This gift is in honor or memory of someone</label>
</div>
<div id="tribute-panel" class="tribute-panel" hidden>
<fieldset class="tribute-type">
<legend class="tribute-sr-only">Is this gift in honor of or in memory of someone?</legend>
<div class="tribute-type-row">
{% for opt in custom_fields.donation.tribute_type.multiple_choice_options %}
<div class="tribute-radio">
<input type="radio" id="tribute-type-{{ forloop.index }}" name="donation[custom_values][tribute_type]" value="{{ opt }}" aria-controls="tribute-name-field">
<label for="tribute-type-{{ forloop.index }}">{{ opt }}</label>
</div>
{% endfor %}
</div>
</fieldset>
<div id="tribute-name-field" class="tribute-name-field form-group" hidden>
<label for="donation_custom_values_tribute_name_custom">Full name of the person being honored or remembered</label>
{% text_field "custom_values.tribute_name", class:"text form-control", autocomplete:"off" %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% comment %} ===== END TRIBUTE FIELDS ===== {% endcomment %}CSS (Append to theme.scss):
.tribute-block {
.tribute-panel {
border-radius: 2px;
border-color: #ccc;
}
.tribute-name-field input[type="text"] {
max-width: 100%;
}
.tribute-name-field label {
display: block;
font-size: 14px;
font-weight: 600;
margin: 0 0 6px;
}
}
Aware
HTML/Liquid:
{% comment %} ===== TRIBUTE FIELDS ===== {% endcomment %}
{% if custom_fields.donation.tribute_type %}
<div class="row-fluid">
<div class="span12">
<div class="tribute-block">
<div class="tribute-toggle-row">
<input type="checkbox" id="tribute-toggle" aria-expanded="false" aria-controls="tribute-panel">
<label for="tribute-toggle">This gift is in honor or memory of someone</label>
</div>
<div id="tribute-panel" class="tribute-panel" hidden>
<fieldset class="tribute-type">
<legend class="tribute-sr-only">Is this gift in honor of or in memory of someone?</legend>
<div class="tribute-type-row">
{% for opt in custom_fields.donation.tribute_type.multiple_choice_options %}
<div class="tribute-radio">
<input type="radio" id="tribute-type-{{ forloop.index }}" name="donation[custom_values][tribute_type]" value="{{ opt }}" aria-controls="tribute-name-field">
<label for="tribute-type-{{ forloop.index }}">{{ opt }}</label>
</div>
{% endfor %}
</div>
</fieldset>
<div id="tribute-name-field" class="tribute-name-field" hidden>
<label for="donation_custom_values_tribute_name_custom">Full name of the person being honored or remembered</label>
{% text_field "custom_values.tribute_name", class:"text", autocomplete:"off" %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% comment %} ===== END TRIBUTE FIELDS ===== {% endcomment %}
CSS (Append to theme.scss):
.tribute-block {
.tribute-panel {
border-color: #b0b8bf;
border-width: 2px;
}
.tribute-name-field input[type="text"] {
width: 100%;
max-width: 420px;
box-sizing: border-box;
}
.tribute-name-field label {
font-weight: 600;
}
}
