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.5p-1 / gap-1p-1.5 / gap-1.5p-2 / gap-2p-3 / gap-3p-4 / gap-4p-5 / gap-5p-6 / gap-6p-8 / gap-8p-12 / gap-12- 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.
Semantic aliases — prefer these in components:
- --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.
Light: Soft two-layer ambient shadow.
Dark: none — dark cards lift via surface tone + border, not shadow.
Light: Medium drop shadow.
Dark: 1px ring + deep layered shadow (separates from the near-black bg).
Light: Large, soft drop shadow.
Dark: 1px ring + deepest layered shadow.
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.
The real primary Button applies the same --fx-inner-glow; in dark it gets the bevel, in light it is flat.
- --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.
The same ring; rounded to --radius-control by the global focus rule.
--color-bg (a visual gap). Layer 2: 2px halo in --color-accent-ring.- --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.
Micro feedback — pressed states.
Default UI transition (buttons/links/inputs).
Standard — hovers, color/shadow changes.
Larger surfaces — cards, panels.
Deliberate — overlays/modals entering.
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.