This is an example brief, not a published client project: a booking page for a therapist in solo practice. The page needs to explain the service, introduce the practitioner, and help someone find and request an appointment. The people visiting are often anxious and reading carefully, so the page should be calm, specific, and easy to act on.
The steps below use GradientDeck's website tools where they help and plain HTML and CSS everywhere else. Every contrast ratio quoted was measured, not estimated.
List the content before choosing colors
Someone deciding whether to book a therapist needs facts more than atmosphere. Collect these first, from the practitioner, because they decide the page structure:
- Who the practitioner is: name, qualifications and registration, a real photo, and a few plain sentences on how they work.
- What a session is: length, online or in person (with the address), and who they work with.
- Fees, including any reduced-fee places, and accepted payment or insurance.
- How booking works: what happens after a request, how soon they reply, whether there is a free first call.
- Cancellation policy and how personal information is handled.
- A clear note that the service is not for emergencies, with what to do instead in a crisis.
Choose a direction from the audience
For someone anxious and reading carefully, calm comes from restraint more than from any particular color: slate neutrals, a single accent, soft low shadows, and short eased motion with no bounce. Decide those principles first, then choose one accent for the practice. This example uses a calm teal, #14b8a6.
Turn the accent into accessible tokens
Build a tonal scale around the seed, then measure every pair you plan to use before trusting a suggested button color. Teal makes this easy to get wrong. White on #18ddc7, one of the lighter steps, measures 1.72:1, far below the 4.5:1 needed for button text. White on the seed itself is 2.49:1, which also fails.
Teal is a light hue, so it needs to be quite dark before white text works on it. Moving down the scale, white on #0a5c53 measures 7.87:1. The lighter teals stay useful for backgrounds and decoration, where no text sits on them. The resulting tokens, with each pair measured:
:root {
--surface: #f0fdfa; /* page background */
--text: #0f172a; /* 17.1:1 on --surface */
--text-muted: #475569; /* 7.3:1 on --surface */
--action: #0a5c53; /* button fill; white text is 7.9:1 */
--action-hover: #073c36;
--field-border: #0f766e; /* 5.2:1 on --surface (needs 3:1) */
--accent-soft: #14b8a6; /* decoration only: 2.4:1 on --surface */
}The input border is the pair most often missed. A border in the seed teal looks clearly colored but measures 2.39:1 against the page, under the 3:1 that WCAG's non-text contrast criterion asks for the visual boundary of a field.
Before writing any components, paste the colors into the palette visualizer. It shows five roles (text, background, primary, secondary, accent) on a sample website and dashboard, in light and dark themes, and copies them out as CSS variables, a Tailwind config, or a theme for libraries such as shadcn/ui.
Order the page around booking
The opening section pairs a short introduction with the booking action and a real photo of the practitioner. The facts someone needs to decide come next, then the person, then the form. A row of abstract feature cards (Compassionate, Confidential, Flexible) says less than one line stating the fee.
<header>…name, Book a first call link…</header>
<main>
<section aria-labelledby="intro">
<h1 id="intro">Counselling for adults in Leeds and online</h1>
<p>I'm Sam Okafor, a registered counsellor. I work with anxiety, low mood, and life changes.</p>
<a href="#book">See available times</a>
<img src="/sam.jpg" alt="Sam Okafor in the counselling room" width="800" height="1000">
</section>
<section aria-labelledby="sessions">
<h2 id="sessions">Sessions and fees</h2>
<dl>
<dt>Length</dt><dd>50 minutes, weekly or fortnightly</dd>
<dt>Where</dt><dd>Online, or in person at 12 Park Row</dd>
<dt>Fee</dt><dd>£60; a few reduced-fee places are available</dd>
<dt>Cancellation</dt><dd>Free up to 48 hours before</dd>
</dl>
</section>
<section aria-labelledby="approach">…how I work, qualifications…</section>
<section id="book" aria-labelledby="book-title">…request form…</section>
<aside>Not an emergency service. If you are in crisis, contact your local emergency number.</aside>
</main>The practitioner name, location, and fee here are placeholders for the example. On a real page every one of them comes from the practitioner, and there are no invented testimonials or outcome claims. If the practitioner wants to quote clients, check their professional body's rules on testimonials first.
Write the request form
Keep the form short: what is needed to reply, nothing more. Every field gets a visible label, autocomplete hints help people fill it in quickly, and the button says what happens.
<form class="space-y-5" action="/request" method="post">
<div>
<label for="name" class="block font-medium text-(--text)">Your name</label>
<input id="name" name="name" autocomplete="name" required
class="mt-1 block min-h-11 w-full rounded-lg border border-(--field-border)
bg-white px-3 focus-visible:outline-2 focus-visible:outline-offset-2
focus-visible:outline-(--action)">
</div>
<div>
<label for="email" class="block font-medium text-(--text)">Email</label>
<p id="email-hint" class="text-sm text-(--text-muted)">I reply within two working days.</p>
<input id="email" name="email" type="email" autocomplete="email" required
aria-describedby="email-hint"
class="mt-1 block min-h-11 w-full rounded-lg border border-(--field-border) bg-white px-3">
</div>
<fieldset>
<legend class="font-medium text-(--text)">Preferred format</legend>
<label class="mr-6"><input type="radio" name="format" value="online"> Online</label>
<label><input type="radio" name="format" value="in-person"> In person</label>
</fieldset>
<button class="min-h-11 rounded-lg bg-(--action) px-5 font-medium text-white
hover:bg-(--action-hover)">
Request a first call
</button>
</form>- Labels sit outside the field, so they remain visible while typing. Placeholder text is not a label.
- The hint is linked with aria-describedby, so screen readers read it with the field.
- Radio buttons are grouped in a fieldset with a legend, so the question is announced with each option.
- min-h-11 makes fields and the button 44px tall, comfortably above the 24 by 24 CSS pixel minimum target size in WCAG 2.2.
- The (--token) syntax is Tailwind v4 shorthand for var(--token), so the utilities read the measured tokens above.
Add texture sparingly
A quiet pattern can give an otherwise plain page some character. Keep it behind the opening section or a quote, away from dense text and the form, and compare it with the plain surface before keeping it.
Here is a dots pattern in plain CSS: the page background with the seed teal at 8% opacity on a 24px grid.
.dots-pattern {
background-color: #f0fdfa;
background-image: radial-gradient(rgba(20, 184, 166, 0.08) 1.5px, transparent 1.5px);
background-size: 24px 24px;
background-position: 0 0;
}At 8% opacity the dots are barely visible, which is the point: they add texture without competing with text. For the form card, a soft, low shadow separates it from the page:
.shadow-custom {
box-shadow: 0 3px 6px -1px rgba(0,0,0,0.1);
}To compare other options, the pattern backgrounds page copies each pattern as a wrapper div with Tailwind classes, and the box shadow generator copies CSS or a Tailwind shadow-[…] value.
Review the finished page
Review it in a browser, not only in code:
- Read the page top to bottom as someone deciding whether to book. Are the fee, format, and next step clear before the form?
- Tab through it. Focus should be visible on every link, field, and button, and reach the form in a sensible order.
- At 320px wide, the booking link should appear on the first screen and the form should not need horizontal scrolling.
- Submit the form with an empty required field and confirm the error is announced and explained in text, not only shown as a red border.
- If a decorative layer makes an important detail harder to find, remove it.