Resolved: light

Spacing, radius, elevation & motion

The structural side of the Save the Dogs token layer — the rhythm, corners, depth and movement that everything else sits on. Flip the theme in the top bar: elevation and the inner glow are skin-specific, so they re-shape between light and dark.

Structural tokens

Color aside, the system is built from four token families: spacing (Tailwind's 4px scale), radius (--radius-*), elevation (--fx-elev-* + the dark-skin --fx-inner-glow) and motion (--dur-* + --ease-brand). Components reference these — never raw px — so the whole UI re-skins and stays consistent.

spacing
No --space-* tokens; spacing is Tailwind's default 4px-based scale used directly (p-*, gap-*, space-y-*).
--radius-*
xs · sm · md · lg · xl · pill, plus the semantic aliases --radius-control (=md) and --radius-card (=lg).
--fx-elev-* / --fx-inner-glow
Skin-specific shadows. Dark cards use border + surface tone (elev-card is none) and an inset --fx-inner-glow for a top-light bevel.
--fx-focus-ring / motion
A two-layer focus ring (offset bg + accent halo) and the --dur-* durations driven by the --ease-brand easing curve.

Spacing scale

Save the Dogs has no dedicated --space-* variables — spacing is Tailwind's default scale (a 4px base unit, so step n = n × 4px). These are the steps the app leans on, as a visual ruler. Use them via padding/margin/gap/space utilities (e.g. p-5, gap-3, space-y-8).

p-0.5 / gap-0.5
2px · 0.125rem
Hairline nudges (chip inset, icon offset).
p-1 / gap-1
4px · 0.25rem
Tightest gap — inline icon ↔ label.
p-1.5 / gap-1.5
6px · 0.375rem
Dense list / chip internal padding.
p-2 / gap-2
8px · 0.5rem
Compact control padding, small gaps.
p-3 / gap-3
12px · 0.75rem
Default row gap (DsRow), control padding.
p-4 / gap-4
16px · 1rem
Default grid gap (DsGrid), card body gap.
p-5 / gap-5
20px · 1.25rem
Card padding (DsExample tile is p-5).
p-6 / gap-6
24px · 1.5rem
Section inner spacing (DsSection mt-6).
p-8 / gap-8
32px · 2rem
Between demos inside a section (space-y-8).
p-12 / gap-12
48px · 3rem
Major page-level block separation.
base unit
4px. Tailwind step n resolves to n × 0.25rem (n × 4px) — e.g. gap-3 = 12px, p-5 = 20px.
where used
p-* / px-* / py-* for padding, gap-* for flex/grid gaps, space-y-* / space-x-* for stacks, m-* for margins.
rhythm
Stick to the scale (no arbitrary px) so vertical rhythm and gutters stay aligned across cards, rows and sections.

Radius scale

The --radius-* corner scale, smallest to fully-round. Each box below has exactly one radius token applied. Reach for the semantic aliases (--radius-control / --radius-card) in components so the meaning travels, not the raw step.

Tiniest — inline tags, nested inset corners.
--radius-xs = 2px
Small chips, dense controls.
--radius-sm = 4px
Buttons & inputs (alias of --radius-control).
--radius-md = 6px
Cards & surfaces (alias of --radius-card).
--radius-lg = 8px
Large panels, hero cards, modals.
--radius-xl = 12px
Pills / chips / avatars — fully rounded.
--radius-pill = 999px

Semantic aliases — prefer these in components:

Buttons, inputs, selects, toggles — anything you click/type into. The global *:focus-visible rule rounds to this.
--radius-control (= --radius-md (6px))
Cards, surfaces, list tiles, panels — the default container radius.
--radius-card (= --radius-lg (8px))
--radius-xs
2px — tiniest inset corners / inline tags.
--radius-sm
4px — small chips, dense controls.
--radius-md
6px — buttons & inputs; aliased by --radius-control.
--radius-lg
8px — cards & surfaces; aliased by --radius-card.
--radius-xl
12px — large panels, hero cards, modals.
--radius-pill
999px — pills, chips and avatars (full rounding).
--radius-control
= --radius-md. The clickable/typeable radius; the global *:focus-visible rule rounds the ring to this.
--radius-card
= --radius-lg. The default container radius for cards, tiles and panels.

Elevation

Depth is a three-step shadow scale — but it is skin-specific. In the light skin elevation is literal drop shadows; in the default dark skin cards stay flat (--fx-elev-card is none) and lift instead via surface tone + border, while overlays/modals add a 1px ring plus a deep layered shadow. Flip the theme to see each card change. The accent panel also shows the dark-only inner glow.

Resting card / surface.

Light: Soft two-layer ambient shadow.

Dark: none — dark cards lift via surface tone + border, not shadow.

--fx-elev-card
Popovers, dropdowns, tooltips, toasts.

Light: Medium drop shadow.

Dark: 1px ring + deep layered shadow (separates from the near-black bg).

--fx-elev-overlay
Dialogs / modal sheets — the highest surface.

Light: Large, soft drop shadow.

Dark: 1px ring + deepest layered shadow.

--fx-elev-modal
Accent surface + inner glow

An inset top-highlight / bottom-seat bevel. It is none in the light skin and the Button primary variant carries it via shadow-[var(--fx-inner-glow)]. Flip to dark to see the bevel appear.

--fx-inner-glow on an accent surface (dark-skin only)

The real primary Button applies the same --fx-inner-glow; in dark it gets the bevel, in light it is flat.

primary button — inner glow in context
--fx-elev-card
Resting cards/surfaces. Light = soft ambient shadow; dark = none (lift via surface tone + border).
--fx-elev-overlay
Popovers, dropdowns, tooltips, toasts. Dark adds a 1px border ring so the surface reads against near-black.
--fx-elev-modal
Dialogs / modal sheets — the deepest shadow. Dark adds the 1px ring + the heaviest layered shadow.
--fx-inner-glow
Dark-skin inset bevel (top highlight + bottom seat); none in light. Applied via box-shadow to accent surfaces / primary buttons.
apply via
shadow-[var(--fx-elev-card)] etc., or the inline boxShadow used here. Don't hand-roll shadows — use the tokens.

Focus ring

The Save the Dogs focus indicator is a single token — --fx-focus-ring — a two-layer box-shadow: an inner ring in the page background (a gap) then an outer halo in the accent ring color. The global *:focus-visible rule applies it to every focusable element. Tab into or click the controls below to see it (it's keyboard/intent-aware, so it shows on Tab and click, not idle).

Uses the global input:focus-visible rule → --fx-focus-ring.

live — Tab into / click this input (real *:focus-visible)

The same ring; rounded to --radius-control by the global focus rule.

live — Tab to / click these buttons
Element with the ring applied
Layer 1: 2px ring in --color-bg (a visual gap). Layer 2: 2px halo in --color-accent-ring.
--fx-focus-ring rendered statically (so you can read it without focusing)
--fx-focus-ring
0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-accent-ring) — a two-layer box-shadow (offset gap + accent halo).
--color-accent-ring
The semi-transparent accent halo color (beryl @ ~45–60% alpha); theme-aware so the ring reads on both skins.
applied automatically
The global *:focus-visible (and input/select/textarea:focus-visible) rules set it — you rarely apply it by hand.
:focus-visible
Intent-aware: shows on keyboard Tab and click, not on idle / programmatic focus. Never remove it without a replacement.

Motion

Duration tokens (--dur-*) paired with one shared easing curve, --ease-brand (a gentle ease-out). Hover each tile below — they all animate the same property change at different durations so you can feel the scale. The default UI transition (buttons, links, inputs) is --dur-fast on --ease-brand.

Hover me →

Micro feedback — pressed states.

--dur-instant = 80ms
Hover me →

Default UI transition (buttons/links/inputs).

--dur-fast = 0.14s
Hover me →

Standard — hovers, color/shadow changes.

--dur-base = 0.2s
Hover me →

Larger surfaces — cards, panels.

--dur-slow = 0.28s
Hover me →

Deliberate — overlays/modals entering.

--dur-slower = 0.52s
--ease-brand
linear (for contrast)
--ease-brand vs linear (same --dur-slow)

Save the Dogs is dark by default; every structural value is a CSS variable. Motion respects prefers-reduced-motion: the global rule collapses all transition / animation durations to ~0 when the user requests reduced motion.

--dur-instant
80ms — micro feedback (pressed / active states).
--dur-fast
0.14s — the DEFAULT UI transition; the global rule animates button/a/input color, bg, border, shadow & opacity at this speed.
--dur-base
0.2s — standard hovers and color/shadow changes.
--dur-slow
0.28s — larger surfaces (cards, panels).
--dur-slower
0.52s — deliberate overlay / modal entrances.
--ease-brand
cubic-bezier(0.22, 1, 0.36, 1) — the single brand easing (a soft ease-out); pair it with every --dur-* token.
prefers-reduced-motion
A global @media rule zeroes out durations/animations when the user opts out — don't rely on motion to convey meaning.