Freeframe

Documentation

Solace

v1.0.0Updated Nov 1, 2026A calm, warm website for a therapy and counseling practice, with a therapist match quiz and a sliding-scale fee checker.

Overview#

Solace is a website for a therapy and counseling practice, built as a WordPress block theme. The fictional practice is Budd Bay Counseling, a group of six clinicians (a psychologist, an LICSW, an LMFT, two LMHCs and a supervised associate) at 412 Legion Way SE in Olympia, Washington, who see adults, couples, teens, children and families in person and by video. The theme has a home page with the next free consultation and groups starting soon; an approach page with ten approaches in tabs; services with groups that start on rolling dates and an interest-list form; a clinician directory with filters and a saved shortlist; a clinician profile page; fees with a sliding-scale calculator and an insurance checker; a five-question match quiz; a searchable FAQ; a three-step consultation request; and a 404 page. A crisis bar with 988 and 911 is on every page. It is version 1.0.0 (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/solace.

Headings, copy, cards, lists, buttons and FAQ answers are core WordPress blocks arranged in block patterns, so you edit them in the block editor without touching code. The parts that are worked out in the browser (the crisis panel, office hours and holidays, the team lists, the directory, the profile, the approach tabs, the groups, the fee tools, the quiz and the forms) work as in the HTML version: they are Custom HTML blocks driven by assets/js/main.js, which reads the practice's data from assets/js/data.js. Colors and fonts are design tokens in theme.json: change them in Appearance > Editor > Styles and the whole site follows. Every date is worked out in America/Los_Angeles (Pacific time), whatever the visitor's own time zone is, from rules rather than fixed dates, so the demo never goes stale.

Mental health copy needs care. The theme keeps to general information, makes no promises about outcomes, shows no client testimonials or reviews, asks people not to put health details in forms, and points to the real crisis routes (call or text 988, call 911) on every page. Keep it that way when you edit, and have your own clinicians review the wording.

Quick start#

Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.

  1. Download solace.zip from freeframe.dev/templates/solace (the WordPress tab).
  2. 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 Solace.
  3. Go to Appearance > Solace setup and click Create the demo pages. This creates the Home, Our approach, Services and groups, Our clinicians, Clinician profile, Fees and insurance, Find a therapist, Questions and answers and Request a free consultation pages from the theme's page patterns, sets Home as the front page (Settings > Reading) and creates the header menu. Pages that already exist with the same slug are left alone, so it is safe to click it again.
  4. Visit your site. 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:

sh
pnpm install
pnpm dev     # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400

pnpm dev signs you in as admin (password password) and creates the demo pages. 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 > Solace pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Solace.

Page Slug Page pattern Sections (pattern files in patterns/)
Home / page-home.php home-hero (next free consultation), how-it-works, home-services, home-team (built from CLINICIANS), what-to-expect, home-fees (fees at a glance, groups starting soon), crisis-help, home-visit (map, open now, office hours)
Our approach /approach/ page-approach.php approach-header, approaches (tabs; #emdr opens one), common-ground, approach-cta
Services and groups /services/ page-services.php services-header, service-individual (#individual), service-couples (#couples), service-families (#families), groups (#groups, with the interest-list form), service-telehealth (#telehealth), services-cta
Our clinicians /team/ page-team.php team-header, directory (filters, shortlist, cards), teamwork
Clinician profile /clinician/ page-clinician.php profile-header, profile-body, profile-related, profile-missing (all filled from CLINICIANS by ?id=)
Fees and insurance /fees/ page-fees.php fees-header, session-fees (#session-fees), sliding-scale (#sliding), insurance (#insurance), good-faith-estimate (#gfe), fees-cta
Find a therapist /match/ page-match.php match-header, match-quiz
Questions and answers /faq/ page-faq.php faq-header (search box), faq (17 answers in five topics), faq-cta
Request a free consultation /contact/ page-contact.php contact-header, consultation (the request form, summary, office hours, reply-by date), finding-us
404 any missing URL templates/404.html 404

The clinician profile works as in the HTML version: one page, filled in the browser from assets/js/data.js. /clinician/?id=delacorte shows Eamon Delacorte; with no id it shows the first clinician (Dr. Ostrander), and an unknown id shows "We couldn't find that clinician". Every link the theme writes to a profile uses this address. WordPress posts use templates/single.html; the blog, archives and search results use templates/index.html, templates/archive.html and templates/search.html.

Project structure#

freeframe-solace/
  style.css                 theme header (name, version, requirements, license); no styles
  theme.json                design tokens: palette, fonts, font sizes, spacing, layout widths, button
  functions.php             loads the files in inc/
  inc/setup.php             stylesheet and scripts (with the script's strings, page and image URLs),
                            editor styles, the `js` class script, theme color and favicon,
                            title separator, pattern categories
  inc/icons.php             registers assets/icons/*.svg for the Icon block
  inc/icon-labels.php       the icons' names shown in the editor
  inc/block-styles.php      button block styles
  inc/site-setup.php        Appearance > Solace setup: creates the demo pages and the menu
  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/data.js         the practice's data (window.SOLACE): PRACTICE, HOURS, HOLIDAYS,
                            CLINICIANS, FEES, SLIDING, PLANS, GROUPS, MATCH and more
  assets/js/main.js         every interaction (the HTML version's script plus a WordPress adapter)
  assets/icons/             the icons used by Icon blocks (outline SVG paths)
  assets/img/               logo.svg and logo-light.svg (header and footer), favicon, social image,
                            hero-sound.svg, svc-*.svg, portrait-*.svg, map.svg and page art
  assets/fonts/             Gilda Display and Rosario, with their licenses
  src/theme.css             the Tailwind CSS source of assets/css/theme.css
  src/icons.css             icons drawn before text in buttons with CSS (generated)
  src/editor-css.mjs        writes assets/css/editor.css after each build
  languages/                freeframe-solace.pot, the template for translations
  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.txt

Customizing#

Brand and colors#

The colors are in theme.json (settings.color.palette) and in Appearance > Editor > Styles > Colors > Palette:

Color Default Used for
Clay 50 to Clay 800 #fbf2ed … #6a3221 the brand terracotta: Clay 600 (#a04e32) for primary buttons, Clay 700 for links and eyebrows
Sage 50 to Sage 700 #f1f4ee … #435440 calm fills, "accepting" pills, today's hours, selected states
Fir 600 to Fir 950 #3b5a50 … #12221d the deep green of the crisis bar, footer, dark panels and secondary buttons
Tide 100, 300, 600 #e3ecef, #a9c0c8, #4f6f7c water blue in artwork and small accents
Ink, Body, Muted #1d2f2a, #3c4742, #5b6660 heading, body and secondary text
Oat, Oat dark, Cream, Line #f3ecdf, #e9dfcd, #fbf8f2, #e2d8c7 backgrounds and borders
White, Black for your own blocks

The theme's stylesheet reads these colors from the palette (bg-clay-600, text-muted...), so changing Clay 600 in Styles recolors every primary button at once. Keep white text on Clay 600 and Fir 800 at 4.5:1 contrast or more. A few colors are also written inside the SVG files (the illustrations, assets/img/logo.svg, assets/img/logo-light.svg and assets/img/favicon.svg) and in a few small drawings in src/theme.css (the eyebrow wave, the menu underline, the FAQ plus); change those by hand if you rebrand.

Components worth knowing in src/theme.css: h-display and section-title (Gilda Display headings), lead, eyebrow (labels with a tide-line mark; eyebrow-light on dark), numeral, card, card-soft, card-hover, arch (the arched image frame), pill (pill-yes, pill-wait, pill-plain), tick-list (a list with drawn ticks), tide-rule, contours, icon-disc, chip and text-link. Add them to a block in Block settings > Advanced > Additional CSS class(es).

Typography#

Two fonts are bundled in assets/fonts/ and declared in theme.json (settings.typography.fontFamilies), so no request goes to Google: Gilda Display (regular; preset display) for headings and Rosario (400 to 700, plus italic; preset sans) for everything else. To use other fonts, install them in Styles > Typography > Fonts (the Font Library) and select them, or replace the fontFace entries in theme.json. The utility classes read the presets (--wp--preset--font-family--display, --sans), so keep those slugs. Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale).

Content#

Edit pages in Pages: click any text to change it, select a button to change its link. Sections are groups; open List View to select a section, move it, duplicate it or delete it. To add a section from another page, use the block inserter (+) > Patterns > Solace.

What the script fills in or drives is in Custom HTML blocks: edit those in the block's HTML view, and keep their ids and data- attributes.

Pattern Custom HTML What it is
header the crisis bar and panel, the menu button see Crisis panel
footer the tide-line edge, the office hours list, the toast see Office hours, open now and holidays
home-team the clinician cards built from CLINICIANS
home-fees groups starting soon built from GROUPS
home-visit, consultation the open-now pill and the hours list see Office hours, open now and holidays
approaches the tab list and each tab's "Who offers it here" list see Approach tabs
service-* "Clinicians who see …" lists built from CLINICIANS
groups the group cards, the interest-list form and its success panel see Groups
directory the filters, the shortlist bar and the cards see Clinician directory and shortlist
profile-* the portrait, pills, buttons, bio, quote, week, facts and related cards see Clinician profiles
session-fees, sliding-scale, insurance the fee table, the calculator and the plan checker see Sliding scale and insurance checker
match-quiz the quiz and the suggestions see Match quiz
faq-header the search box see FAQ
consultation the request form, the success panel and the summary see Forms
several the contour backgrounds and the tide drawing in the crisis section decorative

Some blocks carry classes (Block settings > Advanced > Additional CSS class(es)) that the script reads. Blocks can't store data- attributes, so the script turns classes starting with ffd- into them when the page loads: ffd-next-consult on the hero's floating card becomes data-next-consult, ffd-faq-item on each Details block becomes data-faq-item, ffd-reply-by on the contact page becomes data-reply-by, classes starting with ffa- become other attributes (ffa-role--tabpanel becomes role="tabpanel"), and ff-hidden becomes the hidden attribute. Spans inside text keep their attributes as they are (<strong data-accepting-count> in the hero is filled with "4 of 6 clinicians"). Keep those classes when you restyle a block.

  • reveal fades a block in as it scrolls into view; a class like [--d:120ms] delays it.
  • link-card on a group makes the whole group clickable: the first link inside it stretches over the group (the service cards, the logo in the header and footer).

Everything data-driven is in assets/js/data.js, inside window.SOLACE, the same file as in the HTML version:

Constant What it holds Used on
PRACTICE name, timeZone: 'America/Los_Angeles', phone, email, intake coordinator, refPrefix: 'BBC', replyBusinessDays: 1, consultLeadDays: 2 every page
HOURS session hours by weekday (0 = Sunday, null = closed) open-now pill, today's row
PHONE_HOURS front-desk days; also defines business days for reply-by dates contact, groups
HOLIDAYS, EXTRA_CLOSED federal holiday rules and one-off closures ('YYYY-MM-DD') hours, groups, consultations
CLIENTS, CONCERNS, FORMATS, TIMES labels for who, what, how and when filters, quiz, profiles
CLINICIANS one object per clinician team, profiles, quiz, contact, home
MODALITIES approach names, keyed like the tabs on the approach page profiles
FEES fee rows with CPT codes, three tiers, group and workshop fees, cancellation fees, profiles, quiz
SLIDING poverty guideline base and per-person amount, fee bands fees
PLANS, OON insurance plans and the out-of-network estimate fees, team, quiz, contact
GROUP_RULES, GROUPS groups and their start-date rules services, home
WHO, MATCH quiz answers and points match

Change data.js in a child theme (see Child themes) so an update doesn't overwrite it. The office hours lists (in the footer and the home-visit and consultation patterns) and the fees repeated in copy ($110, $165, $190 on the home page; $55 groups; $75 late cancellation) are written in the blocks; keep them in step with HOURS and FEES. "Six therapists" appears in the home and team copy.

Adding a clinician

Add an entry to CLINICIANS and a 320 x 400 portrait to assets/img/ (in a child theme, see Child themes for the image folder). The key is used in URLs (/clinician/?id=underhill), so use lowercase letters only.

js
underhill: {
  name: 'Sorrel Underhill', first: 'Sorrel', short: 'Sorrel', credential: 'LMHC',
  role: 'Licensed mental health counselor', pronouns: 'they/them', tier: 'licensed',
  clients: ['adults'], concerns: ['anxiety', 'grief'], modalities: ['act', 'cbt'],
  formats: ['inperson', 'video'], days: { 1: ['09:00', '17:00'], 4: ['12:00', '20:00'] },
  accepting: 'yes', insurance: ['soundshore'], sliding: false,
  languages: ['English'], since: 2020,
  focus: 'One line for cards', summary: 'Two sentences for the directory.',
  bio: ['Paragraph one.', 'Paragraph two.'], words: 'A sentence in their own words.',
  training: ['MA, Counseling', 'LMHC, Washington State'], art: 'portrait-underhill.svg'
}
  • tier picks the row of FEES.tiers (psychologist, licensed or associate).
  • days are session hours. A shift that starts before DAYTIME_UNTIL ('15:00') counts as daytime, one that ends after EVENING_FROM ('17:00') counts as evenings, and day 6 counts as Saturdays.
  • accepting is 'yes' or 'waitlist'; a waitlisted clinician needs wait: 'about 6 weeks'.
  • insurance lists keys of PLANS; sliding: true means they hold sliding-scale spots.

The directory, profile, quiz, contact form, approach tabs and service lists all pick the new clinician up.

Fees and the sliding scale

FEES.tiers holds the fee per row id (consult, intake, individual, couples, extended); the table on the fees page is built from it. The sliding scale works out income as a share of the federal poverty guideline: guideline = base + perPerson × (household − 1), then the first band whose upTo percentage is not exceeded sets the fee. Update base and perPerson with each year's guidelines.

Household Income Share of guideline Fee
1 $15,000 96% $40
1 $30,000 192% $60
3 $45,000 169% $60
4 $120,000 373% $125
2 $100,000 473% Standard fee

PLANS entries have a type: 'in' (in network with the clinicians who list the plan), 'eap' (an employee assistance program with sessions free sessions) or 'oon' (out of network: superbill). The plan names are fictional placeholders; use the plans your practice really works with. The out-of-network estimate is OON.allowed × share: with the defaults, 60% of $140 is about $84 back, so a $165 session costs about $81.

Images and artwork#

All artwork is SVG drawn for this template; there are no photos. The hero, service, page and map illustrations are Image blocks, so you can replace any of them with a photo of the same aspect ratio (select the block, Replace):

Image Size Notes
hero-sound.svg 560 x 640 shown in an arch; keep the subject centered
svc-*.svg 360 x 220 cropped to 5:4 on the services page
approach-stones.svg, fees-shells.svg, heron-404.svg about 400 x 440 page heroes
map.svg 640 x 440 replace with a map image or an embedded map
portrait-*.svg 320 x 400 used by the script (art in CLINICIANS); keep faces in the upper middle

The portraits are loaded by the script from the theme's assets/img/ folder (their URLs are passed in window.freeframeSolace.images by freeframe_solace_image_urls() in inc/setup.php). The social image is assets/img/og-image.png (1200 x 630, source og-image.svg); the favicon assets/img/favicon.svg is used until you set a Site Icon in Settings > General.

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 pattern starts with the crisis bar (a Custom HTML block); keep the crisis routes visible, and check the numbers if you adapt the theme for another country.

The header has two Navigation blocks showing the same menu: the desktop one (from 1280 px) and the one in the mobile menu, which has its own Home link above it. Edit the menu once in Appearance > Editor > Navigation (the setup creates "Main menu": Approach, Services, Our team, Fees & insurance, Find a therapist, FAQ, Contact). WordPress marks the current page's link with aria-current="page", and the script marks the mobile menu's Home link on the home page (the page URLs are in freeframe_solace_page_urls() in inc/setup.php). A Navigation block that doesn't point at a menu shows the most recently created one; to pin a menu, select the Navigation block and choose it in the block settings, for both blocks.

The footer's "Start here" links are a List block: click a link to change it, press Enter to add one. The copyright year is written by PHP. The phone number and email appear in the mobile menu, the footer and several pages; search for them when you change them (and in PRACTICE in data.js).

Features#

Crisis panel#

Every page starts with a bar: "In crisis right now? Call or text 988, any time. In an emergency, call 911." The "More ways to get help now" button (data-crisis-toggle) opens a panel (#crisis-panel) with call buttons and the note that the practice can't respond to emergencies by form or email. It is a disclosure button with aria-expanded; Escape closes it while focus is inside. The home page, footer and contact form repeat the same routes.

Office hours, open now and holidays#

renderHours() in main.js highlights today's row in every data-hours list, fills the open-now pills (data-open-status), shows the next holiday within 14 days (data-holiday-note, in the footer and on the contact page) and the reply-by line (data-reply-by). It runs every minute. On Sunday, Nov 1, 2026 the holiday note reads "Closed Wednesday, November 11 for Veterans Day." and the reply-by line "Requests sent now get a reply by 5 pm on Monday, November 2."

Holidays are the 11 US federal holidays, worked out for any year from the rules in HOLIDAYS. Fixed-date holidays move to the Friday before when they fall on a Saturday, and to the Monday after on a Sunday. Add one-off closures to EXTRA_CLOSED. All dates use PRACTICE.timeZone. For review, add ?now=2027-01-04T10:00 to any page URL to see it at that Pacific wall-clock time.

Next free consultation and reply-by dates#

nextConsult(id) returns the first day at least consultLeadDays (2) days ahead when an accepting clinician works and it isn't a holiday; waitlisted clinicians show their wait instead. The home hero shows the earliest across the team (on Sunday, Nov 1, 2026: "Tue, Nov 3 with Eamon"). Reply-by dates are the next business day (Monday to Friday, not a holiday) after today.

Groups#

Each group in GROUPS starts on the nth weekday (0 = Sunday) of each month in months. The date shown is the first one at least GROUP_RULES.minLeadDays (7) days from today that isn't a holiday, so the list never shows a past date. Opened on Nov 1, 2026 the demo shows Slack Tide from Thursday, Dec 10; Little Hours from Saturday, Jan 2, 2027; Steady Ground from Tuesday, Jan 5, 2027; Tide Pool from Wednesday, Jan 6, 2027; and Two Oars on Saturday, Mar 20, 2027. "Join the interest list" on a card picks that group in the form below. To use fixed dates on a real site, replace the rule with your own logic in nextGroupStart() in main.js, or edit months each term.

Clinician directory and shortlist#

The team page filters the cards by concern, client group, in-network plan, format, "accepting new clients" and "sliding-scale spots". Filters are written to the URL (/team/?concern=trauma&format=video&accepting=1), so filtered views can be shared; values are checked against the data before use. Concern trauma and format video shows Eamon only; plan Cascadia Mutual with accepting shows Eamon and Nadira.

The "Save" buttons (data-save-id) are toggle buttons with aria-pressed that add a clinician to the shortlist bar ("1 saved clinician", "Request a consultation with Eamon"), which links to the consultation form with the first saved clinician. The profile page has the same button.

Clinician profiles#

/clinician/?id=<key> renders one clinician from CLINICIANS: name, role, pronouns, status, formats, bio, "in their own words", a Monday to Saturday hours grid, the next free consultation ("Next free consultation: Tuesday, November 3" for Eamon on Nov 1, 2026), an "at a glance" list and three related clinicians. The approach links in the list open that tab on the approach page.

Approach tabs#

The approach page uses the ARIA tabs pattern (role="tablist", arrow keys, Home and End). The URL hash selects a tab (/approach/#emdr), and profile pages link there. "Who offers it here" lists clinicians whose modalities include the tab's key. The tab panels are blocks: edit their text in the editor. The tab buttons are one Custom HTML block; to add an approach, add a button with data-tab and aria-controls matching a new panel group's HTML anchor (panel-<key>).

Match quiz#

The match page asks five questions: who it's for, up to three concerns (MATCH.maxConcerns), format, times and payment. matchClinicians(answers) in main.js (also available as window.SOLACE.match) keeps clinicians who see the chosen client group, drops those who don't offer the chosen format (unless that leaves nobody), scores 3 points per matching concern, 1 per matching time, 2 for payment and 2 for accepting new clients (MATCH.points), and shows the top three with their reasons and a link to /contact/?clinician=<key>&from=match.

Answers Suggestions (score)
Me · anxiety, life changes · video · evenings · sliding scale Eamon (11), Nadira (8), Iver (8)
Me and my partner · relationships · in person · Saturdays · Sound Shore Health Isaura (8)
My teen · intense emotions, school stress · video · evenings · Cascadia Mutual Nadira (11), Dr. Ostrander (3)
Me · trauma, grief · in person · daytime, evenings · EAP Eamon (12), Iver (9), Isaura (4)
My child · anxiety · video · daytime · paying myself Coralie (4), shown for in-person sessions

Sliding scale and insurance checker#

On the fees page, the calculator updates as you type and highlights the matching band; /fees/?household=3&income=45000 prefills it. The plan checker (?plan= is kept in the URL) shows in-network clinicians, EAP details or the out-of-network explanation with a refund estimate by share (50–80%).

FAQ#

The answers are Details blocks with the classes faq (which draws the round plus) and ffd-faq-item, so they open with the keyboard and without JavaScript; add one by duplicating an existing answer. The search box filters questions and answers as you type (?q=insurance prefills it) and announces the count. "Open all answers" toggles every item, and linking to a topic (/faq/#money) opens its first answer. The topic groups have the class ffd-faq-group and an HTML anchor.

Mobile menu, reveals, back to top and toast#

Below 1280 px the menu button (data-menu-button, aria-expanded, aria-controls="mobile-menu") opens the mobile menu; Escape or a click outside closes it. Blocks with reveal fade in once as they scroll into view (off with reduced motion). A back-to-top button appears after scrolling; toast() announces shortlist changes through a polite live region in the footer.

Block styles#

In the block settings' Styles panel:

Block Styles
Button Fill (default, the clay button), Fir green, Outline (fir), Light (on dark), Outline (on dark)

The classes is-small and is-large on a Button block make it the smaller or larger size. Icons before button text are drawn with CSS (icon-before-calendar, icon-before-phone, icon-before-compass, icon-before-map-pin, from src/icons.css).

Forms#

There are two forms, and neither sends anything until you connect it. They are in Custom HTML blocks.

Form Pattern Validates Success state
Consultation request (data-intake-form) consultation who, format and payment (plan if insurance); times and contact method; first and last name, email, a 10-digit US phone when contact is by phone or text, and age confirmation "Thank you, Sam. Your request is in.", a reference like BBC-7K2QH, who will contact you and by when, and what happens next
Group interest list (data-group-form) groups group, first name, email "You're on the list for Steady Ground", the start date, the reply-by date and a reference

The consultation form reads ?clinician=, ?who=, ?format=, ?pay= and ?plan= from the URL and, with ?from=match, the saved quiz answers. Choose a clinician and it says whether they are accepting, their next free consultation, and whether they work in the chosen format or with the chosen client group.

To connect a form service such as Formspree, post the form data in the submit handler and show the success panel only when the service answers OK. In assets/js/main.js (in a child theme), find the comment "Connect your form service here" in the consultation handler:

js
fetch('https://formspree.io/f/your-id', {
  method: 'POST',
  body: new FormData(intake),
  headers: { Accept: 'application/json' }
}).then(function (r) {
  if (r.ok) { /* the existing success code */ }
  else toast('Sorry, that didn’t send. Please call us on (360) 555-0142.');
});

Do the same in the group form's submit handler. You can also replace a form's Custom HTML block with a form plugin's block; add the classes label, field and btn btn-primary to its labels, fields and button so it matches. Choose a form service that offers a signed business associate agreement (BAA) if you might receive health information, and keep the form copy asking people not to include health details.

Saved data (localStorage)#

Key Shape Validation on read Cleared
solace-shortlist array of clinician keys, e.g. ["delacorte"] unknown keys and duplicates dropped "Clear" on the shortlist bar
solace-match { who, concerns[], format, times[], pay, plan, done } every value checked against the data "Start over"
solace-intake { who, format, clinician, pay, plan, times[], contact, voicemail } every value checked when the request is sent

These are the HTML version's keys. Names, email addresses, phone numbers and the free-text note are never stored. All access goes through safeStorage in main.js: when localStorage is blocked (sandboxed iframes, strict privacy settings), it falls back to memory, so the shortlist, quiz and form keep working for the page view. To reset everything, clear the site's data in the browser.

SEO and sharing#

Page titles come from WordPress: the page title, an em dash and the site name (for example "Fees and insurance — Budd Bay Counseling"); the home page shows the site name and tagline (Settings > General). The profile page also sets document.title for the clinician shown. 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 x 630 image) as the default social image. WordPress already generates a sitemap at /wp-sitemap.xml. If you add structured data, the MedicalBusiness or Psychologist schema.org types fit.

Accessibility#

Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks (header, labelled navigation, main, footer), one h1 per page, labelled sections, visible focus outlines, a keyboard-accessible menu, crisis disclosure, tabs, Details answers, filters and toggle buttons with aria-pressed, radio and checkbox cards that are real inputs grouped in <fieldset> with <legend>, inline errors tied to their fields with aria-describedby and aria-invalid, focus moved to the first problem and to each new step's heading, results and counts announced with role="status", and reduced-motion support for every animation. The demo passes axe (WCAG 2.1 A and AA).

When you edit: keep one Heading 1 per page, give images alt text (or mark them decorative), label every field you add, keep aria-controls and aria-describedby ids in step, and keep the crisis routes visible.

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/:

sh
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, template or the script's markup, otherwise the class has no style; keep each class name whole in the script (not built from pieces). Classes you type in the editor's Additional CSS class(es) field are only styled if the stylesheet already contains them.

To deploy, zip the theme folder (named freeframe-solace, without node_modules/) and upload it in Appearance > Themes, or copy the folder to wp-content/themes/ on your server. Use HTTPS, and connect the forms before you go live.

Translations#

The theme is translation-ready with the text domain freeframe-solace. All text in patterns, templates and PHP goes through WordPress's translation functions; the script's own labels and messages (day names, form labels, statuses, validation errors) are translated in PHP (freeframe_solace_script_strings() in inc/setup.php). Some sentences the script builds from pieces (dates, reply-by lines, quiz reasons) are written in English in main.js; translate them there. The practice's content in assets/js/data.js (clinicians, approaches, plans, groups) is edited there directly. Create a translation with Loco Translate, or with WP-CLI and Poedit:

sh
wp i18n make-pot wp-content/themes/freeframe-solace wp-content/themes/freeframe-solace/languages/freeframe-solace.pot

Save freeframe-solace-es_ES.mo (for example) in wp-content/languages/themes/ or in the theme's languages/ folder. Page content created by Solace setup is copied into your pages in the site's language at that moment; translate it in the editor like any other content.

Child themes#

Use a child theme for changes you want to keep when Solace is updated (your clinicians, fees and groups in particular). Create wp-content/themes/solace-child/style.css:

css
/*
Theme Name: Solace Child
Template: freeframe-solace
*/

Templates, parts, patterns and theme.json in the child theme override the parent's files of the same name. To use your own data.js, replace the parent's data script (freeframe-solace-data) under the same handle in the child's functions.php:

add_action( 'wp_enqueue_scripts', function () {
	wp_deregister_script( 'freeframe-solace-data' );
	wp_register_script( 'freeframe-solace-data', get_stylesheet_directory_uri() . '/data.js', array(), '1.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
}, 15 );

Do the same for assets/js/main.js (handle freeframe-solace), keeping the inline window.freeframeSolace strings and URLs. images there lists the portrait URLs by file name; add your own portraits to it. The parent's stylesheet only contains the classes the parent uses: if your child theme uses 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.

Troubleshooting#

  • The pages look unstyled or new classes do nothing. The stylesheet only contains classes it found when it was built. Run pnpm build after 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 > Solace.
  • Times, "open now" or group dates look wrong. Everything uses PRACTICE.timeZone; check for a leftover ?now= in the URL, which freezes the date. If your practice isn't in Pacific time, change it and the "Pacific time" wording in the blocks.
  • Profile or consultation links go to the wrong page. The script links to /clinician/, /contact/, /team/, /fees/ and /approach/. If you renamed those pages, change their URLs in freeframe_solace_page_urls() in inc/setup.php (in a child theme, filter the inline script).
  • A new clinician doesn't appear in the quiz. Check that their clients include the answer's client group (WHO), and that their formats include the chosen format.
  • The shortlist or quiz answers don't survive a reload. The browser is blocking storage (private mode, a sandboxed preview); they still work for the page view.
  • Solace 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 > Solace pages.

License#

Split license: the PHP code and block markup are licensed under the GNU GPL, version 2 or later, and the CSS, JavaScript, images and design under the Freeframe Commercial License (unlimited sites for you and your clients; no resale or redistribution as a theme). The full terms, with your licensee details, are in LICENSE.txt and at https://freeframe.dev/license#commercial. Third-party components keep their own licenses. Fonts: Gilda Display and Rosario are licensed under the SIL Open Font License 1.1 (OFL.txt in each folder of assets/fonts/). Budd Bay Counseling, its clinicians, insurance plans, groups, address, phone number and email address are fictional placeholders.

Search documentation

Search the guides and every template's documentation