A modern and opinionated stylesheet

Buttons

<button>Default</button>
<button class="accent">Accent</button>
<button class="soft">Soft</button>
<button class="lg">Large</button>
<button class="lg accent">Large accent</button>
<button class="lg soft">Large soft</button>
<button class="icon" aria-label="Add">
  <svg width="20" height="20" viewBox="0 0 24 24"
       fill="none" stroke="currentColor" stroke-width="2"
       stroke-linecap="round" aria-hidden="true">
    <path d="M12 5v14M5 12h14"/>
  </svg>
</button>

<button class="icon accent" aria-label="Add">…</button>
<button class="icon soft"   aria-label="Add">…</button>
<button disabled>Disabled</button>
<button class="accent" disabled>Disabled</button>
<button class="soft" disabled>Disabled</button>
<button aria-disabled="true">Aria-disabled</button>
<a class="button" href="#">Link button</a>
<a class="button accent" href="#">Link accent</a>
<a class="button soft" href="#">Link soft</a>
<div class="buttons">
  <button>One</button>
  <button class="accent">Two</button>
  <button class="soft">Three</button>
</div>

Badges

<span class="badge">Badge</span>
<span class="badge accent">Accent</span>
<span class="badge success">Success</span>
<span class="badge warning">Warning</span>
<span class="badge danger">Danger</span>
<span class="badge info">Info</span>
Badge Accent Success Warning Danger Info
<!-- Sizes: default (inline) / .normal / .lg -->
<span class="badge sm">Inline</span>
<span class="badge">Inline</span>
<span class="badge lg">Normal</span>
<span class="badge xl">Large</span>
Inline Inline Normal Large
<!-- Count badges: default / .normal / .lg -->
<span class="badge count sm">1</span>
<span class="badge count">2</span>
<span class="badge count lg">3</span>
<span class="badge count xl">3</span>

<!-- Inside a badge -->
<span class="badge">Inbox <span class="badge count">3</span></span>
<span class="badge normal">Inbox <span class="badge count normal">12</span></span>
1 2 3 4 Inbox 3 Inbox 12
<!-- Corner count over a button (parent needs position: relative) -->
<button style="position: relative">
  Inbox <span class="badge count corner">4</span>
</button>

Form fields

<label for="name">Name</label>
<input id="name" type="text" placeholder="Ada Lovelace">
<input type="email" placeholder="you@example.com">
<input type="password" value="hunter2">
<input type="search" placeholder="Search…">
<input type="text" value="Read only" readonly>
<input type="text" value="Disabled" disabled>
<select>
  <option>Design</option>
  <option>Engineering</option>
  <option>Product</option>
</select>
<textarea rows="4" placeholder="Say something…"></textarea>
<label class="center-items" style="gap:.5em;">
  <input type="checkbox" checked> Subscribe
</label>
<label class="center-items" style="gap:.5em;">
  <input type="radio" name="plan" checked> Free
</label>
<form>
  <fieldset class="mb-1">
    <label for="email">Email</label>
    <input id="email" type="email">
  </fieldset>
  <fieldset class="mb-1">
    <label for="msg">Message</label>
    <textarea id="msg" rows="3"></textarea>
  </fieldset>
  <div class="buttons">
    <button class="accent" type="submit">Send</button>
    <button class="soft" type="reset">Reset</button>
  </div>
</form>

Switch

<label class="switch">
  <input type="checkbox">
  <span class="track"></span>
  Dark mode
</label>

<label class="switch">
  <input type="checkbox" checked>
  <span class="track"></span>
  Enabled
</label>

<label class="switch">
  <input type="checkbox" disabled>
  <span class="track"></span>
  Disabled
</label>
<!-- Switch without label text (uses aria-label) -->
<label class="switch" aria-label="Notifications">
  <input type="checkbox">
  <span class="track"></span>
</label>

Avatars

<!-- Default (inline, slightly larger than text) -->
<label class="avatar">JD</label>
<label class="avatar primary">AB</label>
<label class="avatar accent">CD</label>

<!-- Normal (button-sized) -->
<label class="avatar normal">EF</label>

<!-- Large -->
<label class="avatar lg">GH</label>
<label class="avatar lg">
  <img src="me.jpg" alt="">
</label>

Alerts

<div class="alert info" role="status">Info message.</div>
<div class="alert success" role="status">Saved.</div>
<div class="alert warning" role="status">Careful.</div>
<div class="alert danger" role="alert">Something broke.</div>
<div class="alert accent" role="status">Accent.</div>
Info message.
Saved.
Careful.
Accent.
<!-- Block (full width) -->
<div class="alert warning block" role="status">
  Full-width warning.
</div>
Full-width warning.
<!-- Sticky (pins to top of scroll container) -->
<div class="alert info sticky block">
  Sticky to the top of the scrollable area.
</div>
Sticky to top of this scroll box.

Scroll me…











<!-- Fixed positions -->
<div class="alert fixed top-left">top-left</div>
<div class="alert fixed top-center">top-center</div>
<div class="alert fixed top-right">top-right</div>
<div class="alert fixed bottom-left">bottom-left</div>
<div class="alert fixed bottom-center">bottom-center</div>
<div class="alert fixed bottom-right">bottom-right</div>
<div class="alert fixed left-center">left-center</div>
<div class="alert fixed right-center">right-center</div>
Open the full preview to see fixed alerts pinned to the viewport. Classes: .fixed .top-left etc.
<!-- Toast: fixed + timed (add data-timer + JS removes; CSS animates in) -->
<div class="alert success fixed bottom-right timed"
     data-timer="3000" role="status">
  Saved.
</div>
Use .alert.fixed.bottom-right.timed for toasts. Add data-timer="ms" and toggle .leaving in JS to fade out.

Disclosure

<details class="disclosure">
  <summary>Section title</summary>
  <p>Hidden content shown when open.</p>
</details>

<details class="disclosure" open>
  <summary>Already open</summary>
  <p>This one starts open.</p>
</details>
What is included?

This section is hidden until you expand it.

How do I use it?

Wrap a heading in <summary> and put content after it.

Already open

This one starts open by default.

Dialog

<dialog id="demo-dialog">
  <h3>Dialog title</h3>
  <p>Dialog body text.</p>
  <div class="buttons">
    <button class="accent" onclick="this.closest('dialog').close()">Close</button>
  </div>
</dialog>

<button onclick="document.getElementById('demo-dialog').showModal()">Open dialog</button>

Dialog title

Dialog body text. Native <dialog> + ::backdrop.

Panels

<section class="panels">
  <nav>Navigation</nav>
  <main>Main content</main>
  <aside>Aside</aside>
</section>
Main content
<!-- Two columns: no aside -->
<section class="panels">
  <nav>Navigation</nav>
  <main>Main content</main>
</section>
Main content
<!-- Two columns: no nav -->
<section class="panels">
  <main>Main content</main>
  <aside>Aside</aside>
</section>
Main content
<!-- Single column (no nav, no aside) -->
<section class="panels">
  <main>Main content</main>
</section>
Main content

Typography

<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6
<p>
  A paragraph with an <a href="#">inline link</a>
  and some <code>inline code</code>.
</p>
<p class="text-muted">Muted paragraph.</p>

A paragraph with an inline link and some inline code.

Muted paragraph.

<ul>
  <li>Item one</li>
  <li>Item two</li>
  <li>Item three</li>
</ul>
  • Item one
  • Item two
  • Item three
<pre><code>:root {
  --padding: 30px;
  --radius: 30px;
}</code></pre>
:root {
  --padding: 30px;
  --radius: 30px;
}
<hr>

Above the rule.


Below the rule.

Table

<table>
  <thead>
    <tr><th>Token</th><th>Value</th></tr>
  </thead>
  <tbody>
    <tr><td>--padding</td><td>30px</td></tr>
    <tr><td>--radius</td><td>30px</td></tr>
  </tbody>
</table>
TokenValue
--padding30px
--radius30px
--toucharea44px

Utilities

<div class="bg-odd">…</div>
<div class="bg-alt">…</div>
<div class="no-bg">…</div>
.bg-odd
.bg-alt
.no-bg inside
<p class="mb-1">One padding of space below.</p>
<p class="mb-2">Two paddings of space below.</p>

One padding of space below.

Two paddings of space below.

<p class="sr-only">Only screen readers hear this.</p>

Only screen readers hear this.

(hidden visually — inspect the DOM)

Section

<section>
  <h2>Section title</h2>
  <p>Lead paragraph.</p>
  <div class="buttons">…</div>
</section>

Section title

Lead paragraph.

dashed outline = section bounds · padding scales with --section-padding

Theme override

<html data-theme="dark">
<!-- or remove the attribute for light -->
<!-- Theme toggle using the switch component -->
<label class="switch">
  <input type="checkbox" id="theme-switch"
         onchange="document.documentElement.setAttribute('data-theme', this.checked ? 'dark' : 'light')">
  <span class="track"></span>
  Dark mode
</label>

Structure

The sheet is organised into cascade layers, declared in this order: reset, tokens, base, components, utilities.

Theme switching

The whole theme system is driven by color-scheme. Because every colour token uses light-dark(), flipping color-scheme is enough — no token needs to be redefined.

The toggle button sets data-theme on <html>. The choice is remembered in localStorage, and a tiny script in <head> applies it before the first paint to avoid a flash.

Spacing scale

All margins, paddings and gaps come from a single scale. Change a value in the tokens layer and every element that uses it updates at once — buttons, sections, body padding, utilities.

Type scale

Font sizes and line heights are tokens too, so the whole hierarchy can be retuned from one place.

Line heights: --line-height-body (2), --line-height-heading (1.5), --line-height-section-heading (1.2), --line-height-section-body (1.55), --line-height-button (1).

Colour tokens

Colours use oklch() and light-dark(), so light and dark mode come from color-scheme without extra rules. color-mix() derives hover states from the base colour.

To retheme, override these variables — you rarely need to touch the rest of the sheet.

Utilities

Utilities live in the last layer, so they override components and base styles without !important. They use the spacing scale too.

Accessibility

Focus rings use :focus-visible, so mouse clicks do not show an outline while keyboard navigation does. Touch targets are at least 44px tall through --toucharea.

Transitions are disabled under prefers-reduced-motion: reduce, and buttons get a visible border under forced-colors: active so they remain distinguishable in high-contrast mode.

Usage rules of thumb

Nothing else is included yet. New components, layouts, and utilities get added to their matching layer as the project grows.

Contact

Any questions, recommendations, or comments on this stylesheet? Feel free to help me improve.