Designing Solace: A Therapy Practice Website Template
How the Solace template makes a counseling practice site feel warm and safe, from a therapist match quiz to a sliding-scale fee calculator.
By Paul Peery
· 8 min read

Solace is a website template for a therapy and counseling practice. Someone looking for a therapist is often anxious, short on energy and unsure what they need, so the design tries to lower the effort of every step: a warm clay-and-sage palette on cream paper, soft arched shapes, Puget Sound landscapes instead of stock photos, and tools that answer the practical questions first. Who might fit me? What will it cost? When could I start? This post walks through how each part is built and how to make it your own.
The demo practice is Budd Bay Counseling, a fictional group practice in Olympia, Washington, with six clinicians. Its clinicians, insurance plans, groups and address are all made up.
Two fonts, one quiet voice#
Every page loads two Google Fonts families in one <link>:
href="https://fonts.googleapis.com/css2?family=Gilda+Display&family=Rosario:ital,wght@0,400..700;1,400&display=swap"Gilda Display sets the headings. It's an editorial, high-contrast serif with only a regular weight, so headlines feel considered rather than loud. Rosario does everything else: body text, labels, buttons and forms. It's a humanist sans serif with soft, open shapes that stays readable in long paragraphs about what a first session is like.
Both are Tailwind tokens in the @theme block of src/input.css:
--font-display: "Gilda Display", ui-serif, Georgia, "Times New Roman", serif;
--font-sans: "Rosario", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;Headings use font-weight: 400 throughout. There's no bold display type anywhere, which keeps the tone gentle.
Clay, sage, fir and oat#
The palette is drawn from the shoreline: terracotta clay, sage green, a deep fir green and a soft tide blue, on oat and cream neutrals. Each color has a comment in the source saying what it's for:
/* Clay: the brand color. 600 for buttons and links on light backgrounds, 700/800 for hover and text. */
--color-clay-600: #a04e32;
/* Sage: calm secondary fills. 700 for text on the 50/100 fills. */
--color-sage-100: #e2e9dc;
/* Fir: the deep ink green for panels, the footer and headings. */
--color-fir-800: #243d35;
/* Neutrals: oat paper and warm cream. */
--color-cream: #fbf8f2;The page background is cream rather than white, and text is a dark green-gray (--color-ink: #1d2f2a) rather than black, which softens the contrast without losing readability. Clay is kept for actions: primary buttons, links and the small-caps labels above sections. Sage marks good news, like the "Accepting new clients" pill, while a waitlist pill uses a pale clay fill instead of a warning red.
Arches, pebbles and tide lines#
The shapes do a lot of the work. Images sit in arches, cards have large rounded corners, and a few blobs use an uneven border radius:
.arch { border-radius: 999px 999px 1.75rem 1.75rem; }
.pebble { border-radius: 62% 38% 55% 45% / 48% 58% 42% 52%; }
.card { border: 1px solid var(--color-line); border-radius: 1.75rem; background: #fff; }A wavy tide line appears in small places: before every section label, under the current page in the menu, and as a divider between sections. They're all inline SVG data URIs in the CSS. The section label, for example, draws its wave with a CSS mask, so it takes the label's own color through currentColor:
.eyebrow::before { content: ""; width: 1.6rem; height: .5rem; background: currentColor;
mask: url("data:image/svg+xml,…M1 5c4-4 6-4 10 0s6 4 10 0 6-4 10 0…") center / contain no-repeat; }Motion is slow and small. The two floating cards in the home hero drift six pixels up and down over nine and twelve seconds, and sections fade in once as they scroll into view. With prefers-reduced-motion set, all of that turns off.
Puget Sound artwork and drawn portraits#
All the artwork is SVG drawn for the template: a dusk view over Budd Inlet with cedars and a heron in the home hero, a cairn of stones on the approach page, shells and a sand dollar on the fees page, a heron on the 404 page, five service illustrations and an illustrated map of downtown Olympia. The hero's sky is a three-stop gradient from tide blue through pale clay, using the same hex values as the theme, so the art and the interface feel like one piece.
The six clinicians have illustrated portraits (320 x 400, shown in arches) instead of photos. That's partly honest about the demo being fictional, and partly practical: each portrait is an <img>, so a real practice can swap in a photo of the same shape without touching the layout.
A crisis bar on every page#
Every page starts with a dark fir bar: "In crisis right now? Call or text 988, any time. In an emergency, call 911." Both numbers are tel: links. A "More ways to get help now" button opens a panel underneath with call buttons and a plain note that the practice can't respond to emergencies by form, email or voicemail.
The button is a standard disclosure: it sets aria-expanded, changes its own label, and Esc closes the panel while focus is inside it:
var setCrisis = function (open) {
crisisBtn.setAttribute("aria-expanded", String(open));
crisisPanel.hidden = !open;
$("[data-crisis-label]", crisisBtn).textContent = open ? "Hide crisis options" : "More ways to get help now";
};The home page, footer, quiz and contact form repeat the same routes. Two other content choices go with it: the template shows no client testimonials, and the forms ask people not to include health details.
A match quiz that explains itself#
The "Find a therapist" page asks five questions: who the therapy is for, up to three concerns, in person or video, preferred times, and how they'll pay. matchClinicians() in assets/js/main.js keeps clinicians who see that client group, drops those who don't offer the chosen format (unless that leaves nobody, in which case it says so), then adds up points from one small table in data.js:
var MATCH = {
results: 3,
maxConcerns: 3,
points: { concern: 3, time: 1, payment: 2, accepting: 2 },
};Each point also pushes a plain sentence into a list of reasons:
if (hit.length) reasons.push("Works with " + concernsText(hit));
if (c.accepting === "yes") { score += pts.accepting; reasons.push("Accepting new clients"); }
else reasons.push("Waitlist: " + c.wait);The top three are shown with those reasons ("Works with anxiety and life changes", "Has evening times", "Holds sliding-scale spots, from $40"), so the result reads as a suggestion with its working shown, not a verdict. Each links to the consultation form with that clinician already chosen.
A team directory with shareable filters#
The clinicians page filters the cards by concern, client group, in-network plan, format, "accepting new clients" and "sliding-scale spots". The filters are written to the URL, such as team.html?concern=trauma&format=video&accepting=1, so a filtered view can be shared or bookmarked, and every value is checked against the data before it's used. A Save button on each card adds that clinician to a shortlist bar, kept in the browser.
Each profile (clinician.html?id=delacorte) is built from the same data: bio, a paragraph in their own words, a Monday to Saturday hours grid, fees, plans and three related clinicians with the most shared concerns.
A sliding-scale calculator#
Cost is one of the biggest barriers to starting therapy, so the fees page has a calculator rather than a "contact us for rates" line. It works out household income as a share of a poverty guideline for the household size, then picks the first fee band that fits:
function slidingFee(size, income) {
var S = D.SLIDING, guideline = S.base + S.perPerson * (size - 1);
var pct = (income / guideline) * 100;
for (var i = 0; i < S.bands.length; i++)
if (pct <= S.bands[i].upTo) return { guideline: guideline, pct: pct, band: i, fee: S.bands[i].fee };
return { guideline: guideline, pct: pct, band: -1, fee: null };
}The base, perPerson and bands are in SLIDING in data.js, with a comment to update them each year. Next to it, an insurance checker shows which clinicians are in network for a plan, or explains a superbill and estimates the refund for an out-of-network plan.
Dates that never go stale#
A demo that stays online for years can't show a group that started last spring. Solace works out every date from rules, in Pacific time. Each therapy group starts on the nth weekday of certain months:
{ id: "steady", name: "Steady Ground", topic: "CBT skills for anxiety", weekday: 2, nth: 1, months: [1, 3, 5, 9], … },nextGroupStart() picks the first of those dates at least seven days away that isn't a holiday. Holidays are the 11 US federal holidays, computed for any year from rules, with Saturday dates moved to the Friday before and Sunday dates to the Monday after. The same rules drive the open-now pill, the next free consultation in the home hero and the reply-by date on the forms. Add ?now=2027-01-04T10:00 to any page to preview it at another date and time.
Making it yours#
- Colors: replace the
clayscale insrc/input.cssand rebuild withnpm run build. A few colors also live inside the SVG artwork and the logo mark. The Tailwind guide covers rebuilding. - Clinicians, fees, plans, groups and the quiz: all in
assets/js/data.js. A new entry inCLINICIANSshows up in the directory, profiles, quiz and contact form. - Country and time zone: change
PRACTICE.timeZoneand the "Pacific time" wording, and check the crisis numbers and holidays if you're outside the US. - Forms: the consultation request and group interest list don't send anything until you connect them. If you might receive health information, choose a form service that will sign a business associate agreement (BAA). See forms for services that work with static sites.
The Solace documentation has the full list of data, storage keys and URL parameters, and suggests having your own clinicians review the wording. Solace's HTML version is a free download, and it's also available for Next.js, Astro and WordPress with the same design and interactions. For other calm healthcare sites, see Mend, for a physiotherapy clinic, and Clinic, for a dental or medical practice.

About the author
Paul Peery
Founder of Freeframe
Paul Peery runs Freeframe, which publishes complete multi-page website templates in HTML, Next.js, Astro and WordPress, and writes about getting small websites built, launched and kept up to date.


