Overview#
Nonprofit is a warm, trustworthy website for a charity, built as a WordPress block theme. The demo
belongs to Clearwater Trust, a fictional clean-water organisation working in nine countries. The
theme has a home page with impact counters, a live appeal progress bar, a story spotlight, partner
logos and a newsletter sign-up, an "About & finances" page with a timeline, leadership, the board
and a chart of where the money goes, a programs page with a region filter and a dot-matrix world
map, a stories page and a long-form story, a "Get involved" page with volunteer roles, an
application form, fundraising steps and corporate partnerships, an events page with RSVP buttons
and a form with a guest stepper, a complete donate page and a 404 page. It is a light design in
cream and teal with sunny yellow donate buttons. It is version 1.0.1 (see template.json and
style.css), works with WordPress 7.1 and PHP 7.4 or newer, and is the WordPress version of the
HTML template of the same name. See it live at
freeframe.dev/templates/nonprofit.
The pages are made of core WordPress blocks arranged in block patterns, so you edit text, numbers,
links, buttons and images in the block editor without touching code. The interactive parts (the
appeal bar, the region filter and map, the forms and the donate form) are small Custom HTML blocks
driven by assets/js/main.js. Colours, the two fonts and font sizes are design tokens in
theme.json: change them in Appearance > Editor > Styles and the whole site follows.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
nonprofit.zipfrom freeframe.dev/templates/nonprofit (the WordPress tab). - In your WordPress admin go to Appearance > Themes > Add New Theme > Upload Theme, choose the zip and click Install Now, then Activate. The theme is listed as Freeframe Nonprofit.
- Go to Appearance > Nonprofit setup and click Create the demo pages. This creates the Home, About & finances, Programs, Stories, Story ("The well that brought Amina back to school"), Get involved, Events and Donate pages from the theme's page patterns, sets Home as the front page (Settings > Reading) and creates the two header menus. Pages that already exist with the same slug are left alone, so it is safe to click it again.
- In Settings > General set the Site Title (the demo uses "Clearwater Trust") and the
Tagline ("Safe water, for good"); they make the browser tab titles. Use pretty permalinks
(Settings > Permalinks, any option except Plain): the theme's buttons and links point at
addresses such as
/donate/and/programs/#east-africa. - Change the organisation's name, address and numbers (see Content) and connect the donate page to your payment provider (see Donations). Edit any page in Pages, and the header, footer and templates in Appearance > Editor.
You can also try the theme without installing anything: the Try it in WordPress button on freeframe.dev opens it in WordPress Playground (WordPress running in your browser) with the demo pages already set up.
To work on the theme's files locally you need Node.js 24 and pnpm; WordPress itself runs in WordPress Playground, so there is no PHP, web server or database to install:
pnpm install
pnpm dev # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400pnpm dev signs you in as admin (password password), sets the site title to "Clearwater
Trust" and the tagline to "Safe water, for good", switches on pretty permalinks and creates the
demo pages (blueprint.json). The site is rebuilt from scratch every time you start it; your edits
to theme files are live, edits made in the WordPress admin are not kept.
Pages#
Each page is a page pattern (in Patterns > Nonprofit pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Nonprofit.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (headline, buttons, donor faces, picture with a "Just completed" card), impact-numbers (four count-up numbers), mission (picture, text and four points), what-we-do (three program cards), story-spotlight (picture and quote), appeal (appeal with progress bar), partners (six partner logos), newsletter (sign-up card) |
| About & finances | /about/ |
page-about.php |
about-hero, mission-vision, history (timeline), leadership (four portraits and the board), finances (spending list and chart, anchor #finances), donate-cta |
| Programs | /programs/ |
page-programs.php |
programs-hero, programs-map (region filter, map, six program cards), how-it-works (four steps), donate-cta |
| Stories | /stories/ |
page-stories.php |
stories-hero, stories-list (featured story and five story cards), donate-cta |
| Story | /story/ |
page-story.php |
story-article (breadcrumb, headline, picture, story text with a pull quote and a figure, share buttons, project facts), more-stories |
| Get involved | /get-involved/ |
page-get-involved.php |
involved-hero, volunteer (four roles and the application form, anchors #volunteer and #apply), fundraise (three steps, anchor #fundraise), partnerships (anchor #partners) |
| Events | /events/ |
page-events.php |
events-hero, events-list (five events and the RSVP form, anchor #rsvp), donate-cta |
| Donate | /donate/ |
page-donate.php |
donate-form (introduction and the donation form), donor-faq (anchor #faq) |
| 404 | any missing URL | templates/404.html |
404 |
donate-cta ("Every $50 is a year of clean water for a family") is one pattern used on four
pages. Every page has the same header and footer; the footer holds a newsletter sign-up form.
Posts use templates/single.html; the blog, archives and search results use
templates/index.html, templates/archive.html and templates/search.html.
The stories are pages, as in the HTML version: every story card links to the one example story
(/story/). For a new story, duplicate the Story page (Pages, hover the page, Duplicate
with a plugin, or insert the page pattern Story in a new page), change its text and pictures,
and point a card at it. If you publish many stories, writing them as posts is also an option (the
theme's single.html shows posts with a title and readable text).
Project structure#
freeframe-nonprofit/
style.css theme header (name, version, requirements, license); no styles
theme.json design tokens: palette, fonts, font sizes, spacing, layout widths, button;
the custom template and the template parts
functions.php loads the files in inc/
inc/setup.php stylesheet and script, the script's translations, the `js` class, editor
styles, head meta (theme colour, favicon), title separator, pattern
categories
inc/block-styles.php the Sea and Light (on dark) button styles
inc/icons.php registers assets/icons/*.svg for the Icon block
inc/icon-labels.php the icons' names shown in the editor
inc/site-setup.php Appearance > Nonprofit setup: creates the demo pages and the two menus
templates/ page.html (pages), page-with-title.html, single.html, index.html,
archive.html, search.html, 404.html
parts/ header.html and footer.html (they show the header and footer patterns)
patterns/ every section and page (PHP files with block markup)
assets/css/theme.css the compiled stylesheet (do not edit: built from src/)
assets/css/editor.css the same for the block editor (generated by src/editor-css.mjs)
assets/js/main.js the site's JavaScript: header and mobile menu, count-up numbers, appeal
bar, reveals, programs filter, forms, volunteer and RSVP buttons, copy
link, the donate form, back to top
assets/icons/ SVG icons for the Icon block (arrow-right, bolt, calendar, check, drop,
flag, health, heart, lock, map-pin, mountain, rain, users, wrench)
assets/img/ page and story illustrations, leadership portraits, logo, favicon,
social image
assets/fonts/ Fraunces and DM Sans files and their licenses (OFL.txt)
src/theme.css the Tailwind CSS source of assets/css/theme.css
src/icons.css icons drawn before or after text with CSS (generated)
src/editor-css.mjs writes assets/css/editor.css after each build
blueprint.json WordPress Playground setup used by `pnpm dev`
screenshot.png the picture shown in Appearance > Themes
package.json the CSS build and the local WordPress (Node.js tooling)
readme.txt, README.md, DOCS.md, CHANGELOG.md, LICENSE.txtassets/js/main.js starts with a short "WordPress:" adapter, then the HTML template's script in
sections marked by comment banners (Mobile menu, Header shadow on scroll, Count-up numbers,
Campaign progress, Scroll reveal, Programs: region filter + map, Generic form validation,
Volunteer, Events, Copy link, Donate form), and finally the back-to-top button.
Customising#
Brand and colours#
The colours are in theme.json (settings.color.palette) and in Appearance > Editor > Styles >
Colors > Palette. The stylesheet reads them through Tailwind tokens (in src/theme.css), so a
change in Styles recolours every block that uses the token:
| Colour (slug) | Tailwind token | Used for |
|---|---|---|
Cream (base) |
cream |
page background, the header |
Ink (contrast) |
ink |
main text, text on donate buttons |
Sand (sand) |
sand |
soft panels, the frequency switch, hover backgrounds |
Muted (muted) |
muted |
secondary text, menu links |
Line (line) |
line |
borders and dividers |
Sea 50 to Sea 900 (sea-50...sea-900) |
sea-50...sea-900 |
the teal brand scale: headings (sea-900), eyebrows and links (sea-600, sea-700), the Sea button (sea-700), dark bands and the footer (sea-800, sea-900), soft panels (sea-50) |
Sun 100 to Sun 600 (sun-100...sun-600) |
sun-100...sun-600 |
donate buttons (sun-400, hover sun-300), the progress bar, focus rings and eyebrow dashes (sun-500), soft panels (sun-100), tags (sun-600) |
Clay 400 to Clay 600 (clay-400...clay-600) |
clay-400...clay-600 |
warm accents: map pins, the heart on "Monthly", the demo note on the donate form |
White (white) |
(Tailwind's white) |
cards, form fields |
To re-brand in blue instead of teal, change the Sea colours in Styles, or in theme.json:
{ "slug": "sea-500", "name": "Sea 500", "color": "#2563eb" },
{ "slug": "sea-600", "name": "Sea 600", "color": "#1d4ed8" },
{ "slug": "sea-700", "name": "Sea 700", "color": "#1e40af" }The Button block's default colours (the yellow donate button) are also in theme.json
(styles.elements.button) and in Styles > Blocks > Button; the theme's stylesheet draws the
same look.
Nonprofit has no dark mode. Some colours are written outside the palette, so change them by hand if you change the brand colours:
- the browser UI colour printed by
freeframe_nonprofit_head_meta()ininc/setup.php(<meta name="theme-color" content="#0d5955">); - the donut chart on the About page (a Custom HTML block in
finances:stroke="#1a8a81"and so on) and the legend swatches next to it (classesbefore:bg-[#1a8a81],before:bg-[#f2b04a],before:bg-[#d96c4a]on the list items); - the scrolled header background
rgb(251 247 240 / .92)(.site-header.is-scrolledinsrc/theme.css) and the donate button's shadow; - the logo (
assets/img/logo.svg), the illustrations inassets/img/, the partner logos and the donor faces (inline SVG in Custom HTML blocks).
Typography#
Two fonts are bundled in assets/fonts/ (variable files, one per character subset) and declared in
theme.json (settings.typography.fontFamilies), so no request goes to Google:
| Font | Slug | Used for |
|---|---|---|
| DM Sans (weights 400 to 700) | dm-sans |
all text (the site's Text font) |
| Fraunces (400 to 700, italic 400 to 600) | fraunces |
headings (h1 to h3), numbers, the quotes, the logo (the font-display class) |
To use another font, install it in Appearance > Editor > Styles > Typography > Fonts (the Font
Library) and select it for Text, or replace the fontFace entries in theme.json. The theme's
utility classes use the fonts through their slugs (--font-sans is
--wp--preset--font-family--dm-sans, --font-display is --wp--preset--font-family--fraunces in
src/theme.css), so rename a slug only together with src/theme.css. The donut chart's centre
label names "Fraunces" and "DM Sans" in its SVG.
Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The
stylesheet uses them too: changing Large in Styles changes every text-lg in the design. The
big headlines use fluid sizes written as classes on the block (the home headline is
text-[clamp(2.8rem,7vw,5.2rem)], section titles use the class section-title): change them in
Additional CSS class(es).
Content#
Edit pages in Pages: click any text to change it, select a button to change its link, select an image and use Replace. Sections are groups; open List View (the stacked lines icon at the top left) to select a section, move it, duplicate it or delete it. To add a section from another page, use the block inserter (+) > Patterns > Nonprofit. Nonprofit setup copies the patterns into the pages it creates, so later changes to the pattern files don't change your pages.
The organisation's name ("Clearwater Trust"), address ("2140 SE Water Ave, Portland, OR 97214"),
email addresses (hello@example.org, partners@example.org, give@example.org), phone number,
the EIN in the footer and staff names appear in several places: the header (the logo text and its
label "Clearwater Trust home", in patterns/header.php), the footer (patterns/footer.php,
including the copyright line), the about, get involved and donate pages. Search for them when you
rebrand.
Some content is in Custom HTML blocks, because blocks can't express it: edit those in the block's HTML view.
| Pattern | Custom HTML |
|---|---|
header |
the menu button |
home-hero |
the four donor faces (inline SVG) |
appeal |
the appeal amounts and progress bar (see Appeal progress) |
partners |
the six partner logos (inline SVG and names) |
newsletter, footer |
the newsletter forms and their thank-you messages |
finances |
the donut chart |
programs-map |
the region filter buttons, the map |
story-article |
the share buttons (Copy link, Email) |
volunteer |
the four "Apply for this role" buttons, the application form |
events-list |
the five RSVP buttons, the RSVP form |
donate-form |
the donation form |
Numbers and texts that scripts write or read:
- Impact numbers (
impact-numbers): each big number is a span withdata-count(<span data-count="1240000">1,240,000</span>) inside a paragraph. The number counts up from 0 to the value ofdata-countwhen it scrolls into view. To change it, open the paragraph's HTML view (block toolbar > Options > Edit as HTML) and change both the attribute and the text (the text is what shows without JavaScript). The "%" after "94" is plain text. - Appeal, regions, donation amounts: see Features.
WordPress turns straight apostrophes and quotes typed in normal text blocks into curly ones
(wptexturize), so "Can't" shows as "Can’t", where the HTML version keeps the straight
apostrophe.
Images and artwork#
The pictures are Image blocks showing SVG illustrations from the theme (assets/img/): the page
illustrations (hero.svg, about.svg, donate.svg, events.svg, involved.svg, cta.svg,
lost.svg), the story pictures (story-*.svg) and the leadership portraits (grace-wanjiru.svg,
daniel-okello.svg, priya-shrestha.svg, lucia-mamani.svg). Select one and use Replace to
upload a photo; use pictures of the same proportions (the crop comes from classes such as
[&_img]:aspect-[4/3] and [&_img]:object-cover on the block). Give photos meaningful alt text;
decorative pictures keep an empty one. The leadership portraits are SVG illustrations in the HTML
version too; in WordPress they are Image blocks with the person's name as alt text
("Portrait of Grace Wanjiru"), so you can replace them with photos.
- Logo: the header and footer show
assets/img/logo.svg(an Image block) and the name as a link. To use your own logo, replace the Image block in Appearance > Editor > Patterns > Header (and Footer). - Social image:
assets/img/og-image.png(1200 × 630, sourceog-image.svg). WordPress doesn't output Open Graph tags by itself; see SEO and sharing. - Favicon:
assets/img/favicon.svgis used until you set a Site Icon in Settings > General.
WordPress doesn't allow SVG uploads by default; upload your replacements as JPG, PNG or WebP.
Navigation, header and footer#
The header and footer are template parts (parts/header.html, parts/footer.html) showing the
header and footer patterns. Edit them in Appearance > Editor > Patterns > Header / Footer.
The header holds the logo, the desktop menu (from 1024 px wide), a Donate button (with a heart
from 640 px wide) and, below 1024 px, the menu button that opens the mobile menu (#mobile-menu:
the menu links and a "$50 gives a family clean water for a year. Give today" note). It has two
Navigation blocks with two menus, both created by Nonprofit setup:
| Menu | Links | Shown |
|---|---|---|
| Main menu | Programs, Stories, About, Get involved, Events | in the header from 1024 px wide |
| Mobile menu | the same | in the mobile menu below 1024 px |
The About link uses the short label of the HTML template ("About" for the "About & finances" page,
from freeframe_nonprofit_menu_label() in inc/site-setup.php). Donate isn't in the menus: the
Donate button links to it.
Edit the menus in Appearance > Editor > Navigation. To add a page to the site's navigation, add
it to both menus. The header pattern points each Navigation block at its menu by id
(freeframe_nonprofit_menu_ref() in inc/site-setup.php reads the ids saved in the
freeframe_nonprofit_menus option). If a menu doesn't exist, the Navigation block shows the most
recently created menu; select the block and choose the menu in the block settings. WordPress marks
the current page's link with aria-current="page" (a yellow underline on desktop, a sand
background in the mobile menu). The script does the same for the header's Donate button on the
Donate page and, on a page with a breadcrumb (the Story page), marks the menu link to the
breadcrumb's first link (Stories) with aria-current="true": the current section, styled the same,
but not the current page.
The footer has the logo, a short description and the address, the Our work and Take part
link lists, the monthly newsletter form ("Field notes, monthly") and the legal line with three more
links. The lists are List blocks (class footer-links): click a link to change it, press Enter to
add one. The wave along the footer's top edge is drawn by the class ff-wave in the page's
background colour.
The copyright line prints the current year when the footer comes from the theme's files. After you edit and save the footer in the Site Editor, the year is stored as text; update it there each year.
The links written in the patterns (buttons, cards, the footer) are addresses such as /donate/,
/about/#finances, /get-involved/#fundraise and /donate/?appeal=dry-season. If you change a
page's slug, update them too.
Classes the script reads#
Blocks keep classes, not data-* or ARIA attributes, so the theme writes the HTML template's
attributes as classes: data-x="y" is the class ffd-x--y (ffd-x when the attribute is empty;
an underscore in the value stands for a space), hidden is ff-hidden, and role, aria-*,
tabindex and title on block-level elements are ffa-<name>--<value>. The adapter at the top of
assets/js/main.js turns them back into attributes before anything else runs. You see them in
Block settings > Advanced > Additional CSS class(es); keep them when you edit.
| Class | Where | What it does |
|---|---|---|
ffd-programs |
the Group around the program cards (programs-map) |
the region filter's cards |
ffd-region--east-africa (southern-africa, south-asia, latin-america) |
each program card | the card's region |
ffd-region-title, ffd-region-text, ffd-program-count |
three paragraphs next to the filter | the region name, its summary and "Showing 6 programs" |
ffa-aria-live--polite |
the Group around those paragraphs | announces the change to screen readers |
ffa-aria-hidden--true |
decorative Groups (the glow behind the appeal, the gradients over the call to action and 404 pictures) | hidden from screen readers |
ff-hidden |
the mobile menu | hidden until the menu button opens it |
Other theme classes: reveal and [--d:80ms] (see Scroll reveals),
link-card (a card whose heading link covers the whole card, and the logo), wrap (the page
width: 1200 px with 20 px gutters), eyebrow, lead, section-title, card, prose-story (the
story text), site-nav, site-nav-mobile, ff-header-donate, footer-links, ff-wave and
icon-before-heart / icon-after-arrow-right (icons drawn in buttons and links).
In the editor, the mobile menu (ff-hidden) is shown with a dashed outline so you can edit it, and
content that fades in on the site is always visible.
Features#
Count-up numbers#
Paragraphs with a data-count span count up (1.6 s, eased) when they scroll into view. With
reduced motion the final number is shown immediately; without JavaScript the text is shown as
written.
Appeal progress#
The appeal on the Home page (appeal) is a Custom HTML block:
<div class="mt-10" data-campaign data-raised="528400" data-goal="750000">data-raised and data-goal are the only numbers to change: when the box scrolls into view the
script writes the raised amount ([data-raised-text]) and the goal ([data-goal-text]) with
thousands separators, fills the bar ([data-progress]) and sets the progress bar's
aria-valuenow to the percentage. Keep the printed numbers roughly right too, since they show
without JavaScript. The donors, days left and matching partner below the bar are plain text in the
same block. The Double my gift button links to /donate/?appeal=dry-season, which shows the
matched-giving note on the donate page (see Donate page).
Programs map and region filter#
On the Programs page (programs-map), the filter buttons (data-filter: all, east-africa,
southern-africa, south-asia, latin-america, in a Custom HTML block) show the program cards of
that region (Groups with the class ffd-region--<id> inside the ffd-programs Group), highlight
that region's dots and pin on the map ([data-map], a Custom HTML block), update the region title,
summary and count, and put the region in the address (/programs/#east-africa), so you can link
straight to it (the home page's program cards do). Clicking a pin on the map filters too.
The region names and summaries are REGIONS in the "Programs" section of assets/js/main.js
(translatable, see Translations):
var REGIONS = {
all: [__('All regions'), __('1.24 million people across 9 countries')],
'east-africa': [__('East Africa'), __('Kenya, Uganda and Ethiopia · 612,000 people')],
// ...
};To add a region: add an entry to REGIONS, a filter button
(<button type="button" class="chip" data-filter="<id>" aria-pressed="false">Name</button>), the
class ffd-region--<id> on its program cards, and on the map give the country dots
class="region" data-region="<id>" and add a <g class="pin" data-region="<id>">.
Donate page#
The donation form (donate-form, a Custom HTML block with data-donate) keeps a small state of
frequency and amount:
- Give once / Monthly buttons (
data-frequency) swap the preset amounts and set a default ($50 once, $25 monthly). - Preset amounts come from
AMOUNTS; the Other amount field (#d-custom) has a $5 minimum. - Impact message:
IMPACTis checked from the largest amount down, and the first row the amount reaches is shown. Monthly gifts also show the yearly total. - Cover the fees (
#d-fees) adds 3% to the total. The summary and the submit button update live. - Dedication (
#d-dedicate) reveals "In honour of / In memory of", a name (then required) and an optional email. /donate/?appeal=<id>(used by the appeal's "Double my gift" button) shows the matched-giving note ([data-appeal-note]) when<id>is listed inMATCHED_APPEALS(default['dry-season']); other values are ignored.
They are set in the "Donate form" section of assets/js/main.js:
var AMOUNTS = { once: [25, 50, 100, 250, 500], monthly: [10, 25, 50, 100, 200] };
var IMPACT = [
[500, __('repairs a broken well serving 300 people.'), __('Parts, a trained mechanic and a water test...')],
// ... down to
[0, __('helps bring water closer to home.'), __('Every gift, of any size, is pooled...')]
];Keep the [0, ...] row last so every amount has a message. The amounts on the buttons are written
by the script from AMOUNTS; the ones in the Custom HTML block are only shown without JavaScript.
The demo card fields format themselves and check the number (Luhn; try 4242 4242 4242 4242), a
future MM / YY expiry and a 3–4 digit CVC. A valid gift shows a thank-you message inside the form
("Thank you, Ada. Your gift of $51.50 gives a family clean water for a year.") and says that no
payment was taken. See Donations to take real payments.
Volunteer roles and event RSVPs#
On the Get involved page each Apply for this role button (data-apply="Event volunteer")
selects that role in the application form (#v-role), scrolls to it and focuses the field. The
value must match an option of #v-role. On the Events page each RSVP button
(data-rsvp="run") selects the event in #r-event (by option value) and focuses the name field.
The guests field is a stepper ([data-stepper] with data-step="-1" / "1" buttons) limited by
the input's min and max (1–10). To add an event, duplicate an event card (a Group), change its
text, and in the RSVP button's Custom HTML block give it a new data-rsvp value that you also add
as an <option value="..."> of the form's Event select.
Copy link#
On the Story page, the Copy link button (data-copy-link) copies the page address and reports
the result in [data-copy-status] ("Link copied"). The Email link opens a new email with the
story's title; change its subject and body in the same Custom HTML block when you copy the page
for another story.
Scroll reveals#
Blocks with the class reveal fade up as they scroll into view; a delay is a class such as
[--d:80ms] (the stylesheet contains the delays the demo uses; other values need pnpm build).
Jumping down the page reveals everything above too. Content is only hidden once JavaScript runs
(the js class that freeframe_nonprofit_theme_script() adds to <html> first in <head>), and
everything is shown at once with reduced motion. In the editor everything is always visible.
Header and mobile menu#
The header sticks to the top of the window and gets a soft shadow once the page scrolls
(is-scrolled). Below 1024 px the menu button (data-menu-toggle) opens #mobile-menu, updates
aria-expanded and its label ("Open menu" / "Close menu") and swaps its icon; Esc closes it
(returning focus to the button) and it closes itself when the window grows past 1024 px.
Frequently asked questions#
The donor FAQ (donor-faq) is made of core Details blocks: click the summary to edit the
question, and the paragraph inside for the answer; duplicate one to add a question. The round "+"
that turns into a "×" when a question is open is a span at the end of the summary text.
Back to top#
A button appears after scrolling about one screen down; it scrolls to the top (instantly with
reduced motion), moves keyboard focus to the main content and lifts itself above toasts. Add
data-back-to-top-avoid to anything you pin to the bottom of the screen (a cookie bar, a chat
launcher) and the button sits above it. It is created by assets/js/main.js; to remove it, delete
the "Back to top" part of that file and the .back-to-top styles in src/theme.css, then run
pnpm build.
Buttons#
The Button block's default style is the yellow donate button. In the block settings' Styles
panel there are also (registered in inc/block-styles.php):
| Style | Looks like |
|---|---|
| Sea | a teal button with white text |
| Outline (WordPress's own, restyled) | an outline in the text colour (give the block a text colour class such as text-sea-800) |
| Light (on dark) | a white button for dark sections |
The class is-large (Advanced > Additional CSS class(es)) makes a button larger, like the HTML
template's btn-lg. Icons inside buttons and links are classes too: icon-before-heart draws the
heart before the text, icon-after-arrow-right the arrow after it (icon-size-20 makes it 20 px).
Forms#
| Form | Pattern | Fields |
|---|---|---|
| Newsletter | newsletter (Home) |
first name, email |
| Footer newsletter | footer |
|
Volunteer application (#apply) |
volunteer |
name, email, role, availability (checkboxes), message (20+ characters), consent |
RSVP (#rsvp) |
events-list |
event, name, email, guests (1–10) |
Donation (#donate-form) |
donate-form |
other amount, fees, dedication (type, name, email), first and last name, email, demo card number, expiry and CVC |
Forms with data-validate check required fields, email format, minimum lengths and ranges with the
browser's own constraints. Errors appear under each field with aria-invalid and
aria-describedby, and the first problem is focused. On success the form's [data-success]
message shows the text of the form's data-success-text attribute (translatable) and the form is
reset.
Nothing is sent anywhere: they are demo forms until you connect them.
Option 1: a form plugin#
The usual way on WordPress. Install a form plugin (WPForms, Gravity Forms, Fluent Forms, Contact
Form 7, Jetpack Forms...), build the form there, then delete the form's Custom HTML block and insert
the plugin's block or shortcode in its place. To make its fields look like the theme's, add the
theme's classes where the plugin allows custom classes: field on inputs, label on labels,
btn btn-sea on the submit button. The "Apply for this role" and RSVP buttons only preselect the
theme's own forms.
Option 2: a form service#
Keep the theme's forms and send them to a service such as Formspree:
-
Create a form on the service and copy its endpoint URL.
-
Open the form's Custom HTML block and add the endpoint and method to the form tag, for example for the volunteer application:
html<form id="apply" action="https://formspree.io/f/your-id" method="POST" class="reveal card grid scroll-mt-28 gap-5 p-7 sm:p-9" novalidate="" data-validate="" data-success-text="Thank you! We'll be in touch within a week."> -
In
assets/js/main.js, in the "Generic form validation" section, replace the lines that show the message (under the comment "To send for real") with:jsfetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } }) .then(function (res) { if (!res.ok) throw new Error('Request failed'); var ok = $('[data-success]', form); ok.textContent = form.dataset.successText || __('Thank you!'); ok.classList.remove('hidden'); form.reset(); });Make this change in a child theme (see Child themes), or it will be overwritten when you update the theme. Each form can have its own endpoint. Also rewrite the forms'
data-success-text, which say that nothing was sent.
Donations#
Never collect card numbers on your own page. The card fields are a demo. Use your payment provider's hosted checkout or donation form (Stripe Checkout or Payment Links, PayPal Giving, Donorbox, GiveWP...):
- With a donation plugin (GiveWP, WP Charitable...), replace the donation form's Custom HTML block with the plugin's form block and keep the introduction, the picture and the FAQ around it.
- To keep the theme's amount picker and impact messages, delete the "3. Payment" part of the form
in its Custom HTML block, and in
assets/js/main.js, at the comment "Demo only: replace this with a call to your payment provider", send the amount (state.amount), the frequency (state.freq) and the fee choice (fees.checked) to your provider and redirect to its checkout (for example to a Stripe Payment Link with the amount, or to your server, which creates a Checkout Session).
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Programs — Clearwater Trust"); the home page shows the site name and tagline (Settings >
General). The theme doesn't output meta descriptions or Open Graph tags; use an SEO plugin such
as Yoast SEO, Rank Math or The SEO Framework, and give it assets/img/og-image.png (or your own
1200 × 630 image) as the default social image. WordPress already generates a sitemap at
/wp-sitemap.xml. Charities can add NGO structured data with most SEO
plugins.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks
(header, navigation with labels, main, footer, a labelled breadcrumb), one h1 per page, visible
yellow focus rings, the mobile menu button with aria-expanded and Esc to close, filter and
amount buttons with aria-pressed in labelled groups, the filter result announced with
aria-live, the impact message announced politely, a progress bar with aria-valuenow, a
labelled map and chart with text equivalents next to them, form errors linked with
aria-describedby, aria-invalid on wrong fields, the donation thank-you message receiving focus,
keyboard-friendly Details blocks, and reduced-motion support for reveals, count-up numbers, the
progress bar, smooth scrolling and the back-to-top button.
When you edit: keep one Heading 1 per page, give images alt text (or leave it empty for decorative
pictures), keep link text meaningful, label any field you add to a form (<label for="...">), and
check the contrast of any colour you change (WCAG AA).
Building and deploying#
You only need this if you change the theme's files. The stylesheet assets/css/theme.css is
compiled with Tailwind CSS from src/theme.css and the classes used in patterns/, parts/,
templates/, inc/ and assets/js/:
pnpm install
pnpm build # compile assets/css/theme.css and assets/css/editor.css
pnpm watch # recompile assets/css/theme.css on every change (run pnpm build before you commit)Run pnpm build after adding a utility class to a pattern, a template or assets/js/main.js,
otherwise the class has no style. Classes you type in the editor's Additional CSS class(es)
field are only styled if the stylesheet already contains them.
pnpm start starts WordPress without rebuilding the CSS; pnpm dev does both.
To deploy, zip the theme folder (named freeframe-nonprofit, without node_modules/) and upload
it in Appearance > Themes, or copy the folder to wp-content/themes/ on your server.
Translations#
The theme is translation-ready with the text domain freeframe-nonprofit. All text in patterns
(including the Custom HTML blocks and the forms' data-success-text messages), templates and PHP
goes through WordPress's translation functions; languages/freeframe-nonprofit.pot is the
template. The script's strings (menu labels, region names and summaries, "Showing 6 programs", the
donation impact messages and summary, form errors, the thank-you messages, "Link copied", "Back to
top") are translated in PHP (freeframe_nonprofit_script_strings() in inc/setup.php, printed as
window.freeframeNonprofitL10n). Create a translation with Loco Translate, or with WP-CLI and
Poedit:
wp i18n make-pot wp-content/themes/freeframe-nonprofit wp-content/themes/freeframe-nonprofit/languages/freeframe-nonprofit.potSave freeframe-nonprofit-de_DE.mo (for example) in wp-content/languages/themes/ or in a
languages/ folder in the theme. Page content created by Nonprofit setup is copied into your pages
in the site's language at that moment; translate it in the editor like any other content.
These don't follow the site's language: amounts are written in US dollars with US separators
(money() and toLocaleString('en-US') in assets/js/main.js), and the map's and chart's labels
are inside their SVG.
Child themes#
Use a child theme for changes you want to keep when Nonprofit is updated. Create
wp-content/themes/nonprofit-child/style.css:
/*
Theme Name: Nonprofit Child
Template: freeframe-nonprofit
*/Templates, parts, patterns and theme.json in the child theme override the parent's files of the
same name. The parent's stylesheet only contains the classes the parent uses: if your child theme's
patterns use new utility classes, compile a stylesheet for the child (copy package.json and
src/theme.css, point the @source lines at the child's folders) and enqueue it.
Your amounts, impact messages, regions and any change to the script (sending the forms, the
payment provider) belong in a copy of assets/js/main.js in the child theme, loaded in place of
the parent's from the child's functions.php. Keeping the script's handle keeps its translations:
add_action( 'wp_enqueue_scripts', function () {
wp_scripts()->registered['freeframe-nonprofit']->src = get_stylesheet_directory_uri() . '/assets/js/main.js';
}, 30 );Troubleshooting#
- The pages look unstyled or new classes do nothing. The stylesheet only contains classes it
found when it was built. Run
pnpm buildafter editing patterns, then reload. - "This block contains unexpected or invalid content". A pattern or page was edited by hand with markup that doesn't match the block. Click Attempt recovery, or re-insert the section from Patterns > Nonprofit.
- A program card doesn't show up when filtering. Its Group needs the class
ffd-region--<id>with an id fromREGIONS, inside the Group withffd-programs. /programs/#east-africashows all regions. The id after#must be a key ofREGIONS.- An Apply or RSVP button doesn't preselect the form. Its
data-applyvalue must match an option of the role select exactly; itsdata-rsvpvalue must match an option'svaluein the event select. - The appeal bar shows the wrong percentage. Change
data-raisedanddata-goalin the appeal's Custom HTML block (numbers without separators). - The header menu shows other pages, or an old menu. A Navigation block without a chosen menu shows the most recent one. Select the desktop one and choose "Main menu", and the one in the mobile menu and choose "Mobile menu", in the block settings.
- Nonprofit setup says the pages exist, but they are empty. It never overwrites existing pages. Delete (and empty the trash of) the pages with those slugs, or open each page and insert its page pattern from Patterns > Nonprofit pages.
- A form, the map, the chart or the logos are gone after someone saved the page. Forms and SVG
in Custom HTML need the
unfiltered_htmlcapability, which administrators have on a single site; when other roles (or site admins on multisite) save the page, WordPress strips them. Edit those pages as an administrator, or use a form plugin. - The forms "send" but nothing arrives, or no money arrives. They are demo forms; see Forms and Donations.
- A page shows the title twice or no title. Pages use a template without a title, because the page patterns start with their own heading. For a plain text page (privacy policy, terms), choose the Page with title template in the page's settings.
License#
MIT, which is compatible with the GPL that WordPress requires (see LICENSE.txt in the theme
folder). Free for personal and commercial use. The illustrations, portraits, logos, map, chart and
icons are included under the same license. Fraunces (assets/fonts/fraunces/OFL.txt) and DM Sans
(assets/fonts/dm-sans/OFL.txt) are licensed under the SIL Open Font License 1.1.