KalaanbaShowcase

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.

bg
--bg
surface
--surface
surface-2
--surface-2
fg
--fg
fg-muted
--fg-muted
primary
--primary
accent
--accent
success
--success
warning
--warning
danger
--danger

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.

sm
md
lg
states
full

IconButton

Same intents — round, label required. `xs` for dense toolbars.

xs
sm
md
lg

LinkButton

Inline navigational links.

ButtonGroup

Spaced (default) and attached.

Chip & ChipToggle

Static badges and interactive toggles.

static
NeutralPrimaryAccentVerifiedPendingDisputed
toggle

Card

Flat for inline regions, raised for cards/panels. Add interactive for clickable cards.

flat

Flat tone

inline
Use flat for inline regions, list rows, and dividers. No shadow, no inset highlight — just a hairline border on `bg-surface`.
raised

Raised tone

default
Default surface for cards & panels: bg-surface-elev + border-border-strong + shadow-md with the inset top highlight that separates premium from flat web.
interactive
size=lg

Feature / hero card

Larger radius (--radius-card-lg = 2rem) and roomier padding for hero surfaces.

Fab

Floating action. Mount = soft overshoot.

Foundation atoms

Spinner, Skeleton, Divider, Icon.

spinner
icon
skeleton
divider

TextField — draft

Pill, fluid, hairline border on a distinct surface. Solid pink ring on focus. No icon / left / right / both / disabled.

no icon
left icon
right icon
both sides
disabled
label + hint

Shown on your public profile.

error

Enter a valid Ghanaian phone number.

PasswordField

Composes TextField. Eye toggle in the right slot reveals the password. Defaults to autocomplete='current-password'.

default
with label + lock icon

Use letters, numbers, and one symbol.

new password
error

Password must be at least 8 characters.

disabled

Textarea

Multi-line. Same surface / border / focus language as TextField, but rounded-card and padded so it reads as a panel.

default
label + hint + counter

Keep it short. Two sentences max.

74/240

error

Please give us at least one sentence.

disabled

NumberInput (Stepper)

Pill, –/+ tiles, centered tabular value. ArrowUp / ArrowDown / Home / End on the input. Clamps to min/max.

player age
yrs
squad size

Tap the tiles or use Arrow keys.

disabled

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.

position (badges)

Type to search by name.

priority (dots)
error

Please pick a priority.

CountrySelector

Preset of Select with the bundled country list. Flag + name + dial code. Searchable.

default (Ghana)
empty + hint

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.

match date
empty + hint

Used for age-group eligibility.

error

Please pick a date.

DateTimeField

Pill + calendar + 24h time row. One Date in, one Date out.

kickoff
empty

Pick a date, then dial the time below the calendar.

error

Kickoff is required.

disabled

SearchField

TextField preset: magnifier left, clear × right when value is non-empty.

with value
empty
disabled

OtpInput

Six pill boxes. Auto-advance on type, backspace returns, paste fills left-to-right.

6 digit

We texted a 6-digit code to your phone.

error

That code didn't match. Try again.

disabled

PhoneInput

Country chip + format-as-you-type. Value is digits-only; the dial code is tracked separately.

GH default
empty

We'll send a verification code by SMS.

error

That number doesn't look right.

Checkbox

Boolean + tri-state. The square fills brand pink when on.

off
on
mixed
error
disabled

RadioGroup

Card-row options. Selected row gains a primary border + raised surface.

club tier
Club tier
horizontal

Switch

On/off control. Settings-row variant when label + description are present.

block
block off
inline
disabled

Slider

Single-thumb range. ←/→ step, Shift+←/→ jump by 10×, Home/End to bounds.

skill
72
stake
500 RP
disabled
30

RangeSlider

Two thumbs with enforced minimum gap. Click anywhere on the track to move the nearest thumb.

age band
Age band16y24y
disabled
Locked2030

Combobox

Multi-select tag picker. Backspace on empty input removes the last chip. ↑↓ + Enter to commit.

positions
Centre MidWinger

Up to 4 positions.

error

Pick at least one position.

FileUpload

Dashed dropzone. Drag in or tap to open the native picker.

multi
or drag & drop

Accepted: .png,.jpg,.pdf · up to 5.0 MB each

Up to 5 files, 5 MB each.

single
or drag & drop

Accepted: .pdf

ImageUploader

Single-image preview. Square (crest), circle (avatar), or banner (16:5).

square crest

Upload image

PNG or JPG, max 2 MB.

circle avatar

Upload image

banner

Upload banner

Tabs

Pill-shaped segmented control. Active tab is a lifted card — no translate motion. Arrow keys navigate; Home/End jump to ends.

default
with icons
disabled item

Progress

Linear progress bar. Smooth width transition for determinate; shimmer overlay for indeterminate.

determinate
40%
sizes
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.

static — tones
Match saved
Lineups locked and synced to all participants.
Tournament posted
Eastern Belt Round 3 brackets are live.
Result confirmed
Both clubs have verified the scoresheet.
Lineup conflict
Two players are listed for both squads.
Stake forfeited
No-show penalty applied — 250 RP locked.
queue (live)

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.

basic

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.

preview (live, fixed bottom)

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.

sides
on an icon button

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.

basic

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.

primitives
text — 3 lines
avatar sizes
button
card composite

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.

tinted (static)
Primary wash

Single corner radial. Calmest.

Accent wash

intensity=18 — stronger tint.

Success wash

intensity=10 — barely there.

aurora (animated)
Primary × Accent

Two soft blobs drift slowly. Default pairing.

Warning × Danger

Tonal mixing isn't restricted — but use sparingly.

mesh (animated, premium)
Multi-stop radial mesh

Two layers in opposite drift. The richest variant — save it for hero surfaces, premium cards, the one wow moment.

glass (frosted, static)
Frosted glass

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.

field — variants

We'll never share it.

Optional

Must be at least 8 characters.

Compact density.

Cannot edit right now.

form — sign in
form — sectioned

Identity

Public to your club staff.

Used for OTP.

Optional. Shows on your profile.

List & ListItem

Vertical record collection. ListItem auto-composes the pressable recipe when interactive (button or anchor). Three variants: plain, separated, surface.

surface · interactive
separated · trailing
plain · presentational
  • Match 12Hearts of Oak vs KotokoSat · 4:00 PM · Accra Sports Stadium
    Verified
  • Match 13Aduana Stars vs MedeamaSun · 3:30 PM · Dormaa Park
    Pending
  • Match 14King Faisal vs Berekum ChelseaSun · 6:00 PM · Baba Yara Stadium
    TBD

EmptyState & ErrorState

Fallback surfaces for empty lists and failed fetches. Never silent — every list slot gets one.

empty · md

No matches scheduled yet

When your club adds a fixture, it'll appear here with kick-off time and venue.

empty · sm

No notifications

You're all caught up.

error · with retry

Something went wrong

We couldn't reach the fixtures service. Check your connection and try again.

error · with details

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.

default
Kalaanba

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.

header-only
Kalaanba

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.

api

<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.

live

✨ 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.

intents
NeutralPrimaryVerifiedPendingDisputed
sizes
smmd

Avatar

Identity thumbnail. Derives initials from name; falls back to placeholder.

sizes
interactive

NotificationBell

Four badge states: empty · single-digit · double-digit · 99+. Badge uses brand pink (--primary), not danger-red.

count
labels
empty
single
double
99+