Layouts that adapt on their own
Resize the preview — this card switches to a row when its container is wide enough.
Add ShailStack with a single <link> — no npm, no bundler, no config.
<!-- Roboto is optional; ShailStack falls back to system-ui -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.shailstack.com/shailstack.css">Add the same <link> to index.html (or your root layout), or download shailstack.css into your project and import it once:
// main.jsx / index.js
import './styles/shailstack.css';<div class="ss-card ss-stack ss-text-center ss-p-6">
<span class="ss-section-label">ShailStack</span>
<h2 class="ss-text-2xl ss-font-bold">Hello, World!</h2>
<p class="ss-text-muted">Built with utility classes.</p>
<div class="ss-cluster ss-justify-center">
<button class="ss-btn ss-btn-primary ss-btn-pill">Get started</button>
</div>
</div>Output
Built with utility classes.
ss-, so ShailStack can sit next to Bootstrap, Tailwind or your own CSS without collisions.New layout primitives and container queries, plus a round of accessibility, dark-mode and layout fixes. Every 3.1 class still works.
| Feature | Classes |
|---|---|
Smart Stack | .ss-stack, .ss-push |
Smart Cluster | .ss-cluster, .ss-push |
Smart Split | .ss-split, .ss-split-2-1, .ss-split-1-2 |
Auto Grid | .ss-auto-grid |
Smart Sidebar | .ss-with-sidebar, .ss-sidebar-end |
Content width | .ss-measure |
Content grid & full-bleed | .ss-content-grid, .ss-full-bleed |
Scroller | .ss-scroller |
Container queries | .ss-cq, .ss-cq-row, .ss-cq-grid, .ss-cq-text, .ss-cq-hide-sm |
Modern utilities | .ss-text-balance, .ss-text-pretty, .ss-line-clamp, .ss-accent, .ss-overflow-clip, .ss-overflow-x-clip, .ss-min-h-dvh |
Accessibility | .ss-skip-link, aria-invalid styling, --ss-primary-text token |
.ss-btn-dark and .ss-badge-dark no longer turn into white text on a light background. --ss-border and shadows now update, and color-scheme themes native scrollbars and form controls..ss-text-muted, success/warning/info/danger buttons, solid badges, links, tabs and section labels now meet WCAG AA (4.5:1).:focus-within; inputs keep a focus indicator in Windows High Contrast; accordion focus rings are no longer clipped..ss-grid-2…6 columns can’t be stretched by long words; .ss-grid-auto* no longer overflows narrow containers; the mobile navbar menu and bottom-sheet modal display correctly.html now uses scrollbar-gutter: stable instead of overflow-y: scroll, which could stop position: sticky working..ss-sr-only uses clip-path..ss-text-muted is now gray-500 and.ss-text-subtle gray-600 (both darker); links, .ss-text-primary and outline/ghost/link buttons use the darker brand shade for text; success, danger and info buttons use deeper shades; warning buttons use dark text. Fills such as .ss-btn-primary and .ss-bg-primary are unchanged.All values are CSS custom properties on :root. Override a token to re-theme the whole library.
:root {
--ss-primary: #FF1654; /* fills: buttons, badges, borders */
--ss-primary-dark: #CC0F40; /* hover shade; text on --ss-primary-light */
--ss-primary-light: #ffe4ec; /* tints and backgrounds */
--ss-primary-rgb: 255, 22, 84; /* for rgba() focus rings and shadows */
--ss-primary-text: var(--ss-primary-dark); /* brand text on the page (#ff6b8f in dark mode) */
}--ss-space-*| Token | Value |
|---|---|
--ss-space-1 | 4px |
--ss-space-2 | 8px |
--ss-space-3 | 12px |
--ss-space-4 | 16px |
--ss-space-5 | 20px |
--ss-space-6 | 24px |
--ss-space-8 | 32px |
--ss-space-10 | 40px |
--ss-space-12 | 48px |
--ss-space-16 | 64px |
--ss-space-24 | 96px |
| Token | Value |
|---|---|
--ss-radius-sm / md / lg / xl | 4px / 8px / 12px / 16px |
--ss-radius-2xl / 3xl / full | 24px / 32px / 9999px |
--ss-shadow-xs … --ss-shadow-xl | five elevation levels (darker in dark mode) |
--ss-z-dropdown / sticky / modal | 1000 / 1020 / 1050 |
--ss-z-toast / tooltip | 1080 / 1090 |
The layout primitives read optional custom properties with sensible defaults — set them inline or in your CSS.
| Property | Used by · default |
|---|---|
--ss-gap | stack 16px · cluster 12px · split / sidebar 24px · auto grid, scroller, cq 16px |
--ss-min | .ss-auto-grid · 16rem minimum column width |
--ss-split-at | .ss-split · 40rem — narrower containers stack |
--ss-sidebar | .ss-with-sidebar · 16rem sidebar width |
--ss-content-min | .ss-with-sidebar · 55% — content never gets narrower |
--ss-measure | .ss-measure · 65ch |
--ss-content / --ss-gutter | .ss-content-grid · 72ch / 16px |
--ss-item | .ss-scroller · 80% item width |
--ss-lines | .ss-line-clamp · 2 lines |
Layouts that respond to their content and the space they’re given — no breakpoints, no wrapper soup.
:where(), so it has zero specificity: .ss-gap-*,.ss-items-* and .ss-justify-* always override it. Use --ss-gap for any other value..ss-stackVertical flow with even spacing. Put .ss-push on a child to send it (and everything after it) to the bottom — handy for card footers.
<div class="ss-stack" style="height: 220px">
<h3>Title</h3>
<p>Body text</p>
<button class="ss-btn ss-btn-primary ss-push">Always at the bottom</button>
</div>Short body text.
Gap utilities override the default.
A second paragraph.
.ss-clusterInline items that wrap onto new lines as needed — tags, button groups, toolbars. .ss-push pushes the rest to the end of the line.
<div class="ss-cluster">
<span class="ss-chip">Design</span>
<span class="ss-chip">CSS</span>
<button class="ss-btn ss-btn-primary ss-btn-sm ss-push">Follow</button>
</div>Drag the corner to resize
.ss-splitChildren sit side by side and all stack at once when the container is narrower than--ss-split-at (default 40rem) — never an awkward 2-and-1 wrap. .ss-split-2-1 and.ss-split-1-2 make one side twice as wide.
<div class="ss-split ss-split-2-1" style="--ss-split-at: 36rem">
<div>Main (2 parts)</div>
<div>Aside (1 part)</div>
</div>Drag the corner — stacks below 36rem
.ss-auto-gridAs many columns as fit, each at least --ss-min wide (default 16rem). Unlike.ss-grid-auto it uses auto-fit, so a few items stretch to fill the row, and it never overflows.
<div class="ss-auto-grid" style="--ss-min: 10rem">
<div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div>
</div>Drag the corner
.ss-with-sidebarThe first child is the sidebar (--ss-sidebar, default 16rem); the second fills the rest. When the content would get narrower than --ss-content-min (55%), they stack. Add .ss-sidebar-end to put the sidebar second. For a sticky sidebar, add .ss-sticky .ss-top-0 .ss-self-start to it.
<div class="ss-with-sidebar">
<aside class="ss-sticky ss-top-0 ss-self-start">Filters</aside>
<main>Results</main>
</div>Drag the corner
.ss-scrollerA horizontal, snapping row for cards, logos or galleries. Items are --ss-item wide (default 80%).
<div class="ss-scroller" style="--ss-item: 14rem">
<div class="ss-card">…</div>
<div class="ss-card">…</div>
</div>Scroll sideways
Readable line lengths and edge-to-edge sections, without extra wrapper elements.
.ss-measureCaps line length at --ss-measure (default 65ch), the comfortable range for reading. Combine with .ss-mx-auto to centre it.
This paragraph stops at about 65 characters per line, however wide the page gets. Long lines are tiring to read because the eye has to travel too far back to find the start of the next line.
.ss-content-grid + .ss-full-bleedEvery child sits in a centred column (--ss-content, default 72ch) with a gutter on small screens (--ss-gutter, 16px). Children with .ss-full-bleed break out to the full width — ideal for articles with wide images or coloured bands.
<article class="ss-content-grid">
<h1>Article title</h1>
<p>Text stays at a readable width…</p>
<img class="ss-full-bleed" src="hero.jpg" alt="">
<p>…and continues here.</p>
</article>Text sits in the centred content column.
…and the text continues in the column.
.ss-full-bleed uses width: 100vw. On desktops with visible scrollbars that is slightly wider than the page, so add .ss-overflow-x-clip to <body> or a wrapper. Inside .ss-content-grid there’s no such issue.Components that respond to the width of their container instead of the viewport — the same card works in a sidebar and in the main column.
| Class | CSS |
|---|---|
.ss-cq | container-type: inline-size — mark the container |
.ss-cq-row | column → row when the container is ≥ 36rem |
.ss-cq-grid | 1 column → 2 at ≥ 36rem → 3 at ≥ 56rem |
.ss-cq-hide-sm | display: none when the container is ≤ 30rem |
.ss-cq-text | font-size: clamp(18px, 5cqi, 36px) — scales with the container |
<div class="ss-cq">
<article class="ss-card ss-cq-row">
<img src="thumb.jpg" alt="" class="ss-rounded ss-cq-hide-sm" width="120">
<div>
<h3 class="ss-cq-text">Card title</h3>
<p>Becomes a row when this card’s container is wide enough.</p>
</div>
</article>
</div>Drag the corner — the layout follows the box, not the window
.ss-cq on a parent of the element using.ss-cq-*. Browsers without container-query support (pre-2023) keep the stacked, single-column layout.Fixed column grids from 1 to 6 columns, plus auto-fill grids. Combine with gap utilities.
| Class | CSS |
|---|---|
.ss-grid-1 … .ss-grid-6 | grid-template-columns: repeat(n, minmax(0, 1fr)) |
.ss-grid-auto-sm | repeat(auto-fill, minmax(min(140px, 100%), 1fr)) |
.ss-grid-auto | repeat(auto-fill, minmax(min(200px, 100%), 1fr)) |
.ss-grid-auto-lg | repeat(auto-fill, minmax(min(280px, 100%), 1fr)) |
.ss-col-span-2 / -3 | grid-column: span 2 / span 3 |
.ss-col-full | grid-column: 1 / -1 |
.ss-place-center | place-items: center |
.ss-grid-3 .ss-gap-3
.ss-col-span-2 + .ss-col-full
Direction, wrapping, alignment, ordering and gaps.
| Class | CSS |
|---|---|
.ss-flex-row / -col / -row-rev / -col-rev | flex-direction |
.ss-flex-wrap / -nowrap | flex-wrap |
.ss-flex-1 / -auto / -none | flex: 1 1 0% / 1 1 auto / none |
.ss-flex-grow / .ss-flex-shrink-0 | flex-grow: 1 / flex-shrink: 0 |
.ss-justify-start / end / center / between / around / evenly | justify-content |
.ss-items-start / end / center / baseline / stretch | align-items |
.ss-self-start / end / center / auto | align-self |
.ss-order-first / last / 1 / 2 / 3 | order |
| Class | CSS |
|---|---|
.ss-gap-0 / 1 / 2 / 3 / 4 / 5 / 6 / 8 / 10 / 12 | gap: 0 – 48px (4px scale) |
.ss-gap-x-2 / 4 / 6 | column-gap: 8 / 16 / 24px |
.ss-gap-y-2 / 4 / 6 | row-gap: 8 / 16 / 24px |
Breakpoint classes apply only inside their own width range — they are ranges, not mobile-first “and up” rules.
| Prefix | Applies when the viewport is |
|---|---|
ss-xs-* | ≤ 480px |
ss-sm-* | 481px – 768px |
ss-md-* | 769px – 1024px |
ss-lg-* | ≥ 1025px |
| Class | CSS |
|---|---|
.ss-xs-hidden / -block / -flex / -flex-col / -w-full / -text-center / -grid-1 | ≤ 480px |
.ss-sm-hidden / -block / -flex / -flex-col / -grid-1 / -grid-2 / -w-full / -text-center | 481–768px |
.ss-md-hidden / -flex / -flex-row / -grid-2 / -grid-3 / -w-full / -w-1-2 | 769–1024px |
.ss-lg-hidden / -flex / -grid-3 / -grid-4 / -w-1-2 / -w-1-3 | ≥ 1025px |
<!-- 1 column on phones, 2 on small tablets, 3 on tablets, 4 on desktop -->
<div class="ss-grid-4 ss-xs-grid-1 ss-sm-grid-2 ss-md-grid-3 ss-gap-4">…</div>!important so they beat the base class. For components that should react to their own space rather than the screen, prefer layout primitives or container queries.Font size, weight, line height, alignment and text transform.
.ss-text-*.ss-text-2xs — 10px
.ss-text-xs — 11px
.ss-text-sm — 12px
.ss-text-base — 14px
.ss-text-md — 16px
.ss-text-lg — 18px
.ss-text-xl — 20px
.ss-text-2xl — 24px
.ss-text-3xl — 30px
.ss-text-4xl — 36px
Also .ss-text-5xl (48px) and .ss-text-6xl (60px).
| Class | CSS |
|---|---|
.ss-text-fluid-sm | clamp(14px, 2vw, 16px) |
.ss-text-fluid-md | clamp(16px, 2.5vw, 20px) |
.ss-text-fluid-lg | clamp(20px, 3vw, 28px) |
.ss-text-fluid-xl | clamp(28px, 5vw, 48px) |
.ss-text-fluid-hero | clamp(32px, 6vw, 64px) |
.ss-cq-text | clamp(18px, 5cqi, 36px) — scales with its container |
| Class | CSS |
|---|---|
.ss-font-thin / light / normal / medium / semibold / bold / extrabold / black | font-weight: 100 – 900 |
.ss-font-sans / mono / serif | font-family |
.ss-text-left / center / right / justify | text-align |
.ss-uppercase / lowercase / capitalize / normal-case | text-transform |
.ss-underline / line-through / no-underline | text-decoration |
.ss-italic / not-italic | font-style |
.ss-lh-none / tight / snug / normal / relaxed / loose | line-height: 1 – 2 |
.ss-tracking-tight / normal / wide / wider / widest | letter-spacing |
.ss-truncate | single line with an ellipsis |
.ss-nowrap / .ss-break-word / .ss-break-all | wrapping control |
.ss-text-balance / .ss-text-pretty | text-wrap: balance / pretty (see Modern Utilities) |
Text, background and border colours built from the design tokens.
| Class | CSS |
|---|---|
.ss-text-primary | var(--ss-primary-text) — readable brand text, lighter in dark mode |
.ss-text-primary-dark | var(--ss-primary-dark) |
.ss-text-success / warning / danger / info | the -dark shade of each status colour |
.ss-text-muted / subtle / body / heading | gray-500 / 600 / 700 / 900 |
.ss-text-gray-400 … -900 | gray scale |
.ss-text-white / black | #fff / #000 (fixed) |
Also .ss-bg-warning-light, .ss-bg-info, .ss-bg-white, .ss-bg-black,.ss-bg-transparent, .ss-bg-gray-50 / 200 / 900. Gray and white backgrounds follow the theme: in dark mode .ss-bg-white is the dark surface.
Padding and margin on the 4px scale: ss-p-1 = 4px, ss-p-4 = 16px, ss-p-8 = 32px. Inline/side classes are logical, so they flip in right-to-left layouts.
| Class | Available steps |
|---|---|
.ss-p-{n} | 0 1 2 3 4 5 6 8 10 12 |
.ss-px-{n} (padding-inline) | 0 2 3 4 5 6 8 10 |
.ss-py-{n} (padding-block) | 0 2 3 4 5 6 8 10 12 16 |
.ss-pt-{n} | 0 1 2 3 4 6 8 10 12 16 |
.ss-pb-{n} | 0 1 2 3 4 6 8 10 12 |
.ss-pl-{n} / .ss-pr-{n} (inline-start / end) | 0 2 4 6 8 |
.ss-m-{n} | 0 2 4 auto |
.ss-mx-auto / .ss-mx-0 | margin-inline: auto / 0 |
.ss-my-{n} | 0 2 4 6 8 10 |
.ss-mt-{n} | 0 1 2 3 4 5 6 8 10 12 16 auto |
.ss-mb-{n} | 0 1 2 3 4 5 6 8 10 12 auto |
.ss-ml-{n} / .ss-mr-{n} (inline-start / end) | 0 2 4 auto |
Padding scale
How elements render in the layout.
| Class | CSS |
|---|---|
.ss-block / inline / inline-block | display |
.ss-flex / inline-flex / grid / inline-grid / contents | display |
.ss-hidden | display: none !important |
.ss-invisible / .ss-visible | visibility: hidden / visible |
.ss-sr-only / .ss-not-sr-only | visually hidden but read by screen readers / undo |
Width, height and min/max sizes.
| Class | CSS |
|---|---|
.ss-w-full / auto / fit / min / max | width: 100% / auto / fit-content / min-content / max-content |
.ss-w-1-2 / 1-3 / 2-3 / 1-4 / 3-4 / 1-5 / 4-5 | percentage widths |
.ss-w-screen | width: 100vw |
.ss-h-full / auto / fit | height: 100% / auto / fit-content |
.ss-h-screen / .ss-h-dvh | height: 100vh / 100dvh (follows mobile browser bars) |
.ss-min-h-screen / .ss-min-h-dvh | min-height: 100vh / 100dvh (with 100vh fallback) |
.ss-max-w-xs / sm / md / lg / xl / 2xl | max-width: 320 / 480 / 640 / 768 / 1024 / 1280px |
.ss-max-w-full / none | max-width: 100% / none |
.ss-min-w-0 | min-width: 0 — lets flex children shrink |
.ss-container (-xs/-sm/-md/-lg/-xl) | centred, 1200px (480–1280px) with 16px side padding |
.ss-measure | max-inline-size: 65ch (see Content Width) |
Border sides, colours and radius.
| Class | CSS |
|---|---|
.ss-border-0 / -x / -y | none / inline / block |
.ss-border-primary / success / warning / danger / info / white / gray | border-color |
.ss-rounded-0 / -sm / (default) / -lg / -xl / -2xl / -3xl / -full | border-radius: 0 – 9999px |
.ss-circle | border-radius: 50% |
.ss-rounded-t / .ss-rounded-b | top / bottom corners only |
Elevation from subtle to dramatic; shadows deepen automatically in dark mode.
.ss-shadow-xsXS
.ss-shadow-smSmall
.ss-shadowMedium
.ss-shadow-lgLarge
.ss-shadow-xlXL
.ss-shadow-innerInner
.ss-shadow-noneNone
Positioning, stacking and overflow control.
| Class | CSS |
|---|---|
.ss-static / relative / absolute / fixed / sticky | position |
.ss-inset-0 / .ss-top-0 / .ss-bottom-0 | inset: 0 / top: 0 / bottom: 0 |
.ss-left-0 / .ss-right-0 | inset-inline-start / end: 0 |
.ss-z-0 / 10 / 20 / 30 / 50 / auto | z-index |
.ss-z-dropdown / .ss-z-modal | z-index: 1000 / 1050 |
.ss-overflow-hidden / auto / scroll / visible | overflow |
.ss-overflow-x-hidden / -x-auto / -y-auto | overflow per axis |
.ss-overflow-clip / .ss-overflow-x-clip | overflow: clip — new, keeps position: sticky working |
.ss-opacity-0 / 10 / 25 / 50 / 75 / 90 / 100 | opacity |
.ss-overflow-x-clip over .ss-overflow-x-hidden:hidden turns the element into a scroll container, which silently disables position: stickyfor everything inside it.Small, useful additions built on newer CSS. Each degrades gracefully in older browsers.
| Class | CSS |
|---|---|
.ss-text-balance | text-wrap: balance — even line lengths for headings |
.ss-text-pretty | text-wrap: pretty — avoids a lone last word |
.ss-line-clamp | cut text to --ss-lines lines (default 2) with an ellipsis |
.ss-accent | accent-color: var(--ss-primary) — brand native checkbox, radio, range, progress |
.ss-overflow-clip / .ss-overflow-x-clip | overflow: clip (sticky-safe) |
.ss-min-h-dvh | min-height: 100dvh with a 100vh fallback |
.ss-object-contain / cover / fill / none, .ss-object-center / top | object-fit / object-position |
.ss-ratio-square / video / 4-3 / 3-2 | aspect-ratio |
.ss-isolate | isolation: isolate — new stacking context |
.ss-text-balance vs default
A headline that wraps onto two lines in an awkward way
A headline that wraps onto two lines in an awkward way
.ss-line-clamp with style="--ss-lines: 2"
ShailStack keeps your pages light: one stylesheet, no build step and no JavaScript for most components. This paragraph is longer than two lines, so everything after the second line is hidden and an ellipsis is shown — useful for card descriptions and previews where heights must stay even.
.ss-accent on native controls
Animations only run when the user hasn’t asked for reduced motion (prefers-reduced-motion).
.ss-hover-lift.ss-hover-scale.ss-hover-shadow.ss-hover-opacity| Class | CSS |
|---|---|
.ss-hover-lift / scale / shadow / opacity / primary | hover effect — animates in and out |
.ss-transition / -none / -color / -shadow | transition presets |
.ss-duration-150 / 300 / 500 | transition-duration |
.ss-scale-90 / 95 / 105 / 110 | transform: scale() |
.ss-rotate-45 / 90 / 180 / 270 | transform: rotate() |
.ss-translate-center | translate(-50%, -50%) |
.ss-animate-fade-in | fade in (opacity) |
.ss-animate-slide-up / -slide-down | fade + slide 16px |
.ss-animate-zoom-in | fade + scale from 95% |
.ss-animate-spin / -pulse / -bounce / -shake | loader and attention effects |
.ss-delay-100 / 200 / 300 / 500 | animation-delay |
Ready-made UI built from the tokens. Most need no JavaScript; the ones that toggle state are noted.
Also .ss-btn-xl, .ss-btn-block (full width) and .ss-btn-icon (square, for icon-only buttons — give them an aria-label).
Default
.ss-card
Flat
.ss-card-flat
Hover
.ss-card-hover
Card parts: .ss-card-header, .ss-card-footer, .ss-card-media (edge-to-edge image), .ss-card-clickable.
Tabs show the panel with .ss-tab-panel.ss-active — switch the class with a little JavaScript and keeparia-selected in sync. Dropdowns open on hover, on keyboard focus (:focus-within) or with.ss-open. Pill tabs: add .ss-tabs-pill.
Saved
Toasts stay dark in both themes.
| Component | Classes |
|---|---|
Avatar | .ss-avatar (-xs … -2xl), .ss-avatar-group, .ss-avatar-wrapper + .ss-avatar-status .ss-avatar-online / offline / busy / away |
Divider | .ss-divider (-thick, -dashed, -dotted), .ss-divider-text |
Table | .ss-table-wrap > .ss-table (-striped, -bordered, -primary) |
Code | .ss-code-block > code, .ss-code-copy |
Section | .ss-section (-sm, -lg), .ss-section-header, .ss-section-label, .ss-section-title, .ss-section-sub |
Navbar | .ss-navbar, .ss-navbar-brand, .ss-navbar-links (.ss-open on mobile), .ss-navbar-end |
Modal | .ss-modal > .ss-modal-box, .ss-modal-header / -title / -body / -footer / -close (bottom sheet ≤ 768px) |
Toast | .ss-toast-container (.ss-top-right / top-left / bottom-right / bottom-left) > .ss-toast (-success / -warning / -danger / -info) |
Stepper | .ss-stepper > .ss-step (.ss-active / .ss-complete) > .ss-step-circle, .ss-step-label, .ss-step-line |
Stat | .ss-stat, .ss-stat-label, .ss-stat-value, .ss-stat-change .ss-stat-up / down |
Hero | .ss-hero, .ss-hero-title, .ss-hero-sub, .ss-hero-cta |
Skeleton | .ss-skeleton + -text / -title / -avatar / -btn / -card |
Spinner | .ss-spinner (-sm, -lg, -xl, -success, -danger, -white) |
Progress | .ss-progress (-xs … -xl, -success / -warning / -danger) > .ss-progress-bar |
Tooltip | data-ss-tooltip="Text", data-ss-tooltip-pos="bottom" |
Inputs, selects, checkboxes, toggles and ranges with visible focus states.
<label class="ss-label ss-label-required" for="email">Email</label>
<input class="ss-input" id="email" type="email" aria-invalid="true" aria-describedby="email-error">
<p class="ss-form-error" id="email-error">Enter a valid email address.</p>As it appears on your ID.
Enter a valid email address.
| Class | CSS |
|---|---|
.ss-input / .ss-select / .ss-textarea | text fields; -sm / -lg sizes via .ss-input-sm / -lg |
.ss-input-success / .ss-input-error | validation colours |
[aria-invalid="true"] | new — styled like .ss-input-error automatically |
.ss-label / .ss-label-required | label; adds a red * for required |
.ss-form-group / -hint / -error | spacing, help text, error text |
.ss-input-group + .ss-input-group-icon | leading icon inside an input |
.ss-checkbox / .ss-radio | custom checkbox and radio |
.ss-toggle > input + .ss-toggle-track | switch |
.ss-range | range slider (Chrome, Safari, Edge and Firefox) |
Accessible defaults are built in; a few things still depend on your markup.
:focus-visible) and none on mouse click. Form fields keep a transparent outline so Windows High Contrast still shows focus.--ss-primary-text (or .ss-text-primary) for brand-coloured text.prefers-reduced-motion: no-preference; with reduced motion, transitions and smooth scrolling are switched off.inline-start / inline-end).<details>.| Class | CSS |
|---|---|
.ss-sr-only | hide visually, keep for screen readers |
.ss-not-sr-only | undo .ss-sr-only |
.ss-skip-link | new — “Skip to content” link, visible only on keyboard focus |
.ss-focus-ring | brand focus outline for custom elements |
<a class="ss-skip-link" href="#main">Skip to content</a>
…
<main id="main">…</main>.ss-btn-icon, close buttons) an aria-label.aria-label or visible text.role="progressbar" with aria-valuenow to .ss-progress, and role="status" + a label to spinners.aria-selected, aria-modal, aria-expanded) and focus management.aria-invalid="true" and link the message with aria-describedby.#FF1654) is 3.8:1. That passes AA only for large text (24px, or about 19px bold) — not for the default 14px button label. If you need AA everywhere, set--ss-primary: #e0003c (4.9:1) in your :root.Three ways to switch. Tokens swap automatically and native controls follow via color-scheme.
<html data-ss-theme="dark"> <!-- force dark -->
<html data-ss-theme="light"> <!-- force light, ignoring the OS -->
<script>
// toggle
const root = document.documentElement;
root.dataset.ssTheme = root.dataset.ssTheme === 'dark' ? 'light' : 'dark';
</script>.ss-dark.ss-dark paints its own dark surface and text, so it works inside a light page. Inside it, use the normal surface classes — .ss-bg-white and .ss-card become dark automatically.
<section class="ss-dark ss-p-6 ss-rounded-lg">
<h3>Dark section</h3>
<div class="ss-card">This card is dark too.</div>
</section>With no data-ss-theme, ShailStack follows the visitor’s OS setting (prefers-color-scheme: dark). Set data-ss-theme="light" on <html> to opt out.
--ss-* token inside your own [data-ss-theme="dark"] rule.Edit HTML with ss-* classes on the left and see the result on the right. Drag the preview’s corner to try the container query.
Resize the preview — this card switches to a row when its container is wide enough.