design system · phase 2
Tier 0 & Tier 1 — live.
Every clickable thing composes Pressable. Buttons never move — they change state. Toggle the theme above; the whole page should cross-fade in 160ms.
Tokens
Live values pulled from globals.css.
Pressable
The canonical recipe. Compose this — never reinvent a hover/active rule.
Button
6 intents × 3 sizes. Hover/press change colour and shadow, never position.
IconButton
Same intents — round, label required. `xs` for dense toolbars.
LinkButton
Inline navigational links.
ButtonGroup
Spaced (default) and attached.
Chip & ChipToggle
Static badges and interactive toggles.
Card
Flat for inline regions, raised for cards/panels. Add interactive for clickable cards.
Flat tone
inlineRaised tone
defaultbg-surface-elev + border-border-strong + shadow-md with the inset top highlight that separates premium from flat web.Feature / hero card
--radius-card-lg = 2rem) and roomier padding for hero surfaces.Fab
Floating action. Mount = soft overshoot.
Foundation atoms
Spinner, Skeleton, Divider, Icon.
TextField — draft
Pill, fluid, hairline border on a distinct surface. Solid pink ring on focus. No icon / left / right / both / disabled.
Shown on your public profile.
Enter a valid Ghanaian phone number.
PasswordField
Composes TextField. Eye toggle in the right slot reveals the password. Defaults to autocomplete='current-password'.
Use letters, numbers, and one symbol.
Password must be at least 8 characters.
Textarea
Multi-line. Same surface / border / focus language as TextField, but rounded-card and padded so it reads as a panel.
Keep it short. Two sentences max.
74/240
Please give us at least one sentence.
NumberInput (Stepper)
Pill, –/+ tiles, centered tabular value. ArrowUp / ArrowDown / Home / End on the input. Clamps to min/max.
Tap the tiles or use Arrow keys.
Select (custom dropdown)
Custom-built. Trigger uses the TextField pill recipe. Popover matches trigger width. Per-option leading slot supports position badges, dots, numbers, icons — anything.
Type to search by name.
Please pick a priority.
CountrySelector
Preset of Select with the bundled country list. Flag + name + dial code. Searchable.
We use this for fixture eligibility and zone mapping.
DateField
Pill trigger + calendar in a Popover that matches the trigger's width. Pink-filled selected day, Today / Close footer.
Used for age-group eligibility.
Please pick a date.
DateTimeField
Pill + calendar + 24h time row. One Date in, one Date out.
Pick a date, then dial the time below the calendar.
Kickoff is required.
SearchField
TextField preset: magnifier left, clear × right when value is non-empty.
OtpInput
Six pill boxes. Auto-advance on type, backspace returns, paste fills left-to-right.
We texted a 6-digit code to your phone.
That code didn't match. Try again.
PhoneInput
Country chip + format-as-you-type. Value is digits-only; the dial code is tracked separately.
We'll send a verification code by SMS.
That number doesn't look right.
Checkbox
Boolean + tri-state. The square fills brand pink when on.
RadioGroup
Card-row options. Selected row gains a primary border + raised surface.
Switch
On/off control. Settings-row variant when label + description are present.
Slider
Single-thumb range. ←/→ step, Shift+←/→ jump by 10×, Home/End to bounds.
RangeSlider
Two thumbs with enforced minimum gap. Click anywhere on the track to move the nearest thumb.
Combobox
Multi-select tag picker. Backspace on empty input removes the last chip. ↑↓ + Enter to commit.
Up to 4 positions.
Pick at least one position.
FileUpload
Dashed dropzone. Drag in or tap to open the native picker.
Up to 5 files, 5 MB each.
ImageUploader
Single-image preview. Square (crest), circle (avatar), or banner (16:5).
PNG or JPG, max 2 MB.
Tabs
Pill-shaped segmented control. Active tab is a lifted card — no translate motion. Arrow keys navigate; Home/End jump to ends.
Progress
Linear progress bar. Smooth width transition for determinate; shimmer overlay for indeterminate.
Toast
Notification card. Five tones, auto-dismiss with a thin progress bar, hover or focus to pause. Entrance is opacity-only — no translate motion.
BottomSheet
Mobile-first modal surface. Bottom-anchored with drag-to-dismiss on phones; centered dialog on tablets and up. Fade-in only — drag is the one place real translateY is allowed because it follows the finger 1:1.
BottomNav
Thumb-zone tab bar. Visible only on <lg viewports; hidden on desktop where top/side nav takes over. 44×44 tap targets, safe-area-padded. Try resizing the window.
The nav is mounted below — look at the bottom of your viewport. Active item is home.
KeyboardFooter
Sticky CTA bar that floats above the on-screen keyboard. Required for OTP / login / single-step forms. The container is sticky-positioned and safe-area-padded; pair with interactive-widget=resizes-content viewport meta.
Filler line 1 — scroll this container to see the footer stay anchored.
Filler line 2 — scroll this container to see the footer stay anchored.
Filler line 3 — scroll this container to see the footer stay anchored.
Filler line 4 — scroll this container to see the footer stay anchored.
Filler line 5 — scroll this container to see the footer stay anchored.
Filler line 6 — scroll this container to see the footer stay anchored.
Filler line 7 — scroll this container to see the footer stay anchored.
Filler line 8 — scroll this container to see the footer stay anchored.
Filler line 9 — scroll this container to see the footer stay anchored.
Filler line 10 — scroll this container to see the footer stay anchored.
Filler line 11 — scroll this container to see the footer stay anchored.
Filler line 12 — scroll this container to see the footer stay anchored.
Tooltip
Hover/focus label for icon-only or terse controls. Opacity fade only. Use for *supplementary* hints — the child still needs its own aria-label.
Dialog
Centered modal for all viewports. Heavy backdrop blur — the world outside the card is unmistakably out of focus. No drag, no bottom anchoring — that's BottomSheet's job.
Skeleton
Cold-load placeholder. Animated background-position only — no transform. Pauses for reduced-motion. Use composite recipes (Text, Avatar, Button, Card) instead of stacking primitives by hand.
LiveSurface
Opt-in ambient surface treatment. Four variants — tinted (static wash), aurora (drifting blobs), mesh (multi-stop radial), glass (frosted). Decorative; not used by default primitives. Drift pauses under prefers-reduced-motion.
Single corner radial. Calmest.
intensity=18 — stronger tint.
intensity=10 — barely there.
Two soft blobs drift slowly. Default pairing.
Tonal mixing isn't restricted — but use sparingly.
Two layers in opposite drift. The richest variant — save it for hero surfaces, premium cards, the one wow moment.
Translucent fill + backdrop-blur + hairline top edge. Best for chrome floating over rich backgrounds.
Form atoms
Field (label + hint + error + required/optional). Form (RHF + Zod + scroll-to-first-error). FormSection. FormFooter. FormSubmitButton.
We'll never share it.
Must be at least 8 characters.
Compact density.
Cannot edit right now.
List & ListItem
Vertical record collection. ListItem auto-composes the pressable recipe when interactive (button or anchor). Three variants: plain, separated, surface.
- Match 12Hearts of Oak vs KotokoSat · 4:00 PM · Accra Sports StadiumVerified
- Match 13Aduana Stars vs MedeamaSun · 3:30 PM · Dormaa ParkPending
- Match 14King Faisal vs Berekum ChelseaSun · 6:00 PM · Baba Yara StadiumTBD
EmptyState & ErrorState
Fallback surfaces for empty lists and failed fetches. Never silent — every list slot gets one.
No matches scheduled yet
When your club adds a fixture, it'll appear here with kick-off time and venue.
No notifications
You're all caught up.
Something went wrong
We couldn't reach the fixtures service. Check your connection and try again.
Couldn't load standings
The competition service returned an unexpected response.
AppShell & SiteHeader
Page layout chassis. Sticky header + main content + optional mobile BottomNav. SiteHeader is the pre-composed default; AppShell takes any header node.
Page content
Everything below the sticky header renders here. On mobile, the optional BottomNav slot floats above this area with a safe-area-aware bottom inset.
ScrollTo & ScrollControls
Floating utility buttons that smooth-scroll to top, middle, or bottom. Auto-show/hide based on scroll distance. A live cluster is already attached to this page — scroll to see it appear at the bottom-right.
<ScrollTo to="top|middle|bottom|<number>" /> — single floating button.
<ScrollControls targets={["top", "bottom"]} position="bottom-right" /> — stacked cluster.
Both use native window.scrollTo({behavior: "smooth"}) which respects prefers-reduced-motion.
✨ A <ScrollControls targets={["top", "middle", "bottom"]} /> is currently mounted on this showcase page. Try scrolling — buttons fade in with a soft pop when you're away from the target.
Badge
Non-interactive status indicator. Use Chip for clickable tags.
Avatar
Identity thumbnail. Derives initials from name; falls back to placeholder.
NotificationBell
Four badge states: empty · single-digit · double-digit · 99+. Badge uses brand pink (--primary), not danger-red.