Integration · Setup

Tailwind theme An accessible starter, built and checked

The token studio’s Tailwind v4 export, made into a starter: seven components that use only its role colors, a setup pinned to the versions it was built with, and a checklist for your own pages. Every component was built with Tailwind and checked in both themes.

Version
Tailwind 4.3.3
Needs
Node.js and npm; Tailwind 4.3.3
Cost
Free, nothing to sign up for
Checked

Before you start

What you need

  • A project with Node.js and npm. The starter was built on Node.js 24.11.0.
  • Templates where you can add classes: HTML, or the components of any framework.
  • Nothing from this site to install: the theme is one CSS file.

Setup

Step by step

  1. Download the theme

    theme.css is the token studio’s Tailwind v4 export of its default system: the token variables for both themes, then an @theme inline block that makes each role a Tailwind color, the four status colors included.

    theme.css

    @import "tailwindcss";
    
    /* … the token variables for both themes … */
    
    @theme inline {
      --color-page: var(--color-canvas);
      --color-panel: var(--color-surface);
      --color-raised: var(--color-surface-raised);
      --color-ink: var(--color-text);
      --color-muted: var(--color-text-muted);
      --color-action: var(--color-primary);
      --color-action-hover: var(--color-primary-hover);
      --color-on-action: var(--color-on-primary);
      --color-focus: var(--color-ring);
      --color-boundary: var(--color-border);
      --color-success: var(--color-success-text);
      --color-success-soft: var(--color-success-surface);
      --color-warning: var(--color-warning-text);
      --color-warning-soft: var(--color-warning-surface);
      --color-danger: var(--color-danger-text);
      --color-danger-soft: var(--color-danger-surface);
      --color-info: var(--color-info-text);
      --color-info-soft: var(--color-info-surface);
      /* … */
    }

    Download theme.css

  2. Install the tested versions

    These are the exact versions the starter was built and checked with. A newer 4.x release should work; these are the ones that were run.

    Terminal

    npm install [email protected] @tailwindcss/[email protected]
  3. Build

    Build from the theme. Tailwind finds the classes in your templates and writes only those.

    Terminal

    npx @tailwindcss/cli -i ./theme.css -o ./dist/styles.css
  4. Copy the components

    Paste the components below into your templates and change the words. Keep the focus, error and status markup: the checklist depends on it. Set data-theme="dark" on a wrapper for the dark theme.

    You should see: the built example below: seven components in each theme, with no axe violations in either.

Components

Seven components, each with its states

Each uses Tailwind’s own utilities and the theme’s role colors only, so it follows the theme. The text of each is a placeholder; the structure is the part that matters.

The built example

Every component in the light and dark themes, built with Tailwind 4.3.3 from theme.css. The example is in English, as the code is. Open the built example in its own tab

Buttons

Primary and secondary, 44 pixels tall. Focus draws a 2-pixel outline in the theme’s focus color. The primary button keeps a transparent border, so forced colors can show its edge.

HTML

<div class="flex flex-wrap gap-3">
  <button type="button" class="inline-flex min-h-11 items-center rounded-lg border-2 border-transparent bg-action px-4 font-medium text-on-action motion-safe:transition-colors hover:bg-action-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">
    Save changes
  </button>
  <button type="button" class="inline-flex min-h-11 items-center rounded-lg border-2 border-boundary bg-panel px-4 font-medium text-ink motion-safe:transition-colors hover:border-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">
    Cancel
  </button>
</div>

A field with an error

A real label. The hint and the error are tied to the field with aria-describedby, and aria-invalid marks it. The message has an icon and words, not a red border alone.

HTML

<div class="grid max-w-sm gap-2">
  <label for="email" class="font-medium text-ink">Email</label>
  <p id="email-hint" class="text-sm text-muted">We send the receipt here.</p>
  <input id="email" type="email" autocomplete="email" value="sam.mail" aria-invalid="true" aria-describedby="email-hint email-error" class="min-h-11 w-full rounded-lg border-2 border-danger bg-panel px-3 text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">
  <p id="email-error" class="flex gap-2 text-sm font-medium text-danger">
    <svg class="mt-0.5 size-5 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm3.3 10.2-1.1 1.1L10 11.1l-2.2 2.2-1.1-1.1L8.9 10 6.7 7.8l1.1-1.1L10 8.9l2.2-2.2 1.1 1.1L11.1 10l2.2 2.2Z"/></svg>
    Enter an email address with an @ sign.
  </p>
</div>

Status messages

Success, note, warning and error, each with an icon, a word that names it and a live-region role. Use role="alert" only for a message that appears because of something the reader did.

HTML

<div class="grid gap-3">
  <div role="status" class="flex gap-3 rounded-lg border border-success bg-success-soft p-4 text-success">
    <svg class="mt-0.5 size-5 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm3.7 6.2-4.5 4.6a.8.8 0 0 1-1.1 0L6.3 11a.8.8 0 1 1 1.1-1.1l1.3 1.2 3.9-4a.8.8 0 1 1 1.1 1.1Z"/></svg>
    <p><strong class="font-semibold">Saved.</strong> Your changes are live.</p>
  </div>
  <div role="status" class="flex gap-3 rounded-lg border border-info bg-info-soft p-4 text-info">
    <svg class="mt-0.5 size-5 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm.9 12H9.1V9h1.8v5Zm0-6.5H9.1V6h1.8v1.5Z"/></svg>
    <p><strong class="font-semibold">Note:</strong> Reports update every hour.</p>
  </div>
  <div role="status" class="flex gap-3 rounded-lg border border-warning bg-warning-soft p-4 text-warning">
    <svg class="mt-0.5 size-5 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10.9 2.6a1 1 0 0 0-1.8 0L1.6 16a1 1 0 0 0 .9 1.5h15a1 1 0 0 0 .9-1.5L10.9 2.6Zm-.1 12.4H9.2v-1.6h1.6V15Zm0-3H9.2V7h1.6v5Z"/></svg>
    <p><strong class="font-semibold">Warning:</strong> Your plan renews in three days.</p>
  </div>
  <div role="alert" class="flex gap-3 rounded-lg border border-danger bg-danger-soft p-4 text-danger">
    <svg class="mt-0.5 size-5 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm3.3 10.2-1.1 1.1L10 11.1l-2.2 2.2-1.1-1.1L8.9 10 6.7 7.8l1.1-1.1L10 8.9l2.2-2.2 1.1 1.1L11.1 10l2.2 2.2Z"/></svg>
    <p><strong class="font-semibold">Error:</strong> The payment did not go through.</p>
  </div>
</div>

Badges

Short labels on a soft fill. The words carry the meaning; the color only supports it.

HTML

<p class="flex flex-wrap gap-2">
  <span class="inline-flex items-center rounded-full bg-info-soft px-3 py-1 text-sm font-medium text-info">Beta</span>
  <span class="inline-flex items-center rounded-full bg-success-soft px-3 py-1 text-sm font-medium text-success">Paid</span>
  <span class="inline-flex items-center rounded-full bg-warning-soft px-3 py-1 text-sm font-medium text-warning">Due soon</span>
</p>

A card

A raised surface with a heading, muted text and a link that is a 44-pixel target.

HTML

<article class="max-w-sm rounded-xl border border-boundary bg-raised p-6 shadow-sm">
  <h3 class="text-lg font-semibold text-ink">October report</h3>
  <p class="mt-2 text-muted">Twelve pages, updated on 1 October.</p>
  <a href="#card" class="mt-2 inline-flex min-h-11 items-center font-medium text-action underline underline-offset-4 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">Open the report</a>
</article>

Navigation

The current page has aria-current, a heavier weight and a bar under it, so it is not marked by color alone.

HTML

<nav aria-label="Account">
  <ul class="flex gap-6 border-b border-boundary">
    <li><a href="#profile" aria-current="page" class="-mb-px inline-flex min-h-11 items-center border-b-2 border-action font-semibold text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">Profile</a></li>
    <li><a href="#billing" class="-mb-px inline-flex min-h-11 items-center border-b-2 border-transparent text-muted hover:border-boundary hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">Billing</a></li>
    <li><a href="#security" class="-mb-px inline-flex min-h-11 items-center border-b-2 border-transparent text-muted hover:border-boundary hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus">Security</a></li>
  </ul>
</nav>

Verification

A checklist for your own pages

Run these on your build, in every theme, after you change anything. The last two columns are what each check found on the built example.

The checklist, with what each check found on the built example
CheckHow to check itLight themeDark theme
Text contrastEvery text color on its background reaches 4.5:1, or 3:1 for large text. Measure each pair in each theme, error and hover states included.

6.51:1 at its lowest

Passes

7.66:1 at its lowest

Passes
BoundariesThe field’s border, the secondary button’s border, the current tab’s bar and the card’s edge reach 3:1 against what is next to them.

3.32:1 at its lowest

Passes

5.78:1 at its lowest

Passes
Keyboard focusTab through the page. Every control shows an outline at least 2 pixels wide, at 3:1 or more against the color behind it.

8 of 8 controls; outline 7.25:1

Passes

8 of 8 controls; outline 7.66:1

Passes
Target sizeButtons, fields and navigation links are at least 24 by 24 pixels. These are taller, at 44.

43 px at the smallest

Passes

43 px at the smallest

Passes
Forced colorsWith Windows high contrast on (forced colors), every button and field keeps a visible border.

3 of 3 keep a border

Passes

3 of 3 keep a border

Passes
Automated rulesRun axe or Lighthouse on each theme. They catch missing labels, names and roles, not whether the words make sense.

0 violations

Passes

0 violations

Passes
Not color aloneEach message carries an icon and a word that names it, and each badge a text label.

4 of 4 messages

Passes

4 of 4 messages

Passes
Errors tied to the fieldThe field in error has aria-invalid, and aria-describedby names its hint and its error, so a screen reader reads both.

3 of 3 references found

Passes

3 of 3 references found

Passes
Reduced motionTransitions run only under motion-safe:, so a reader who asks for reduced motion gets none.

2 of 2 transitions

Passes

2 of 2 transitions

Passes

Limits

What it does not do

  • The checks cover the default token system. If you change the seed or a role in the studio, export again and run the checklist on your own build.
  • Automated checks cannot judge meaning. Whether a message is clear, or the reading order works, needs a person; the starter has not yet been tested with a screen reader.
  • The starter is for Tailwind v4. The studio also exports a Tailwind v3 configuration, which the starter does not use.
  • Layout, the type scale and responsive behavior are yours: the starter covers color, focus and states.

Go further

The full reference

Checked against

Where these facts come from

Versions, requirements and examples on this page were checked on . The starter was built with Tailwind 4.3.3 and its CLI 4.3.3 on Node.js 24.11.0 from the token studio’s export, then checked in Chromium with axe 4.12.1 in both themes; contrast, focus, target size and forced colors were measured on the built page.

Found a mistake? Write to us and we will correct it.