Appearance
cricnepal Design System — Styleguide
The single source of truth for the cricnepal design system. If a decision about brand, color, type, spacing, components, or patterns isn't answered here (or in a layer this document names as authority), it is not yet decided — decide it here, not in a one-off file.
This is the spine. It defines the system, including where each kind of truth lives, so "where is this defined?" always has exactly one answer.
0. The model — one system, three layers
We run this the way Uber (Base), Apple (HIG), and Wise run theirs: one written system that declares its authorities. Each layer owns one kind of truth and never repeats another.
| Layer | Owns | Authority for | Rule |
|---|---|---|---|
This document (STYLEGUIDE.md) | rules, rationale, decision tests, the index | how the system works | the law — read first, links to the other two |
Figma (cricnepal-v2026, file 3OFyWSbBLptoxwkZt0FKcf) | visual specs, swatches, component anatomy | what it looks like | designers edit here, nowhere else |
Token package (packages/tokens/tokens.css) | the values that ship | what code reads | one file, imported everywhere; no copies |
The discipline that makes it one source, not three:
- This doc holds rules + token names — never a raw hex, px, or shadow value.
- Figma holds pixels — it never becomes a second value store code reads.
- The package holds values — once. Every app imports it; nobody copies it.
A hex in this document is a bug. A token value in Figma that code can't read is a bug. A second copy of the token file is a bug.
There is no /design route. Rendered swatch pages are a maintenance burden that duplicate Figma — Figma is the visual showroom. (The old cn-engine / apps/web /design routes are retired.)
How to change something (the only contribution flow)
- A value (color, weight, spacing) → edit Figma Variables → sync into the token package → every app gets it on next build. Never hand-edit an app's local copy.
- A rule (when to use which card, the weight ceiling) → edit this doc.
- A component's anatomy → edit Figma + the canonical component code; note the rule here if it's new.
1. Principles & Brand
cricnepal v2026 is the Nepal cricket team jersey identity: jersey navy + jersey red + white, cool-slate neutrals. Editorial-flat, not Material-heavy: near-zero shadow, restrained corners, hierarchy from color and size before weight and borders.
- Brand name casing: always lowercase
cricnepal/cricnepal.com. NeverCricNepal. (titles, meta, schemaname,og:site_name, UI, docs.) - Logo, variations, misuse, gradients, voice → Figma 🪪 Brand page (node
6364:3016). Master logo nodes6364:3017 / 3031 / 3153. - Principles we build on (applied as principles, not specs): Material (elevation as hierarchy), NN/g (truthful signifiers), Refactoring UI (hierarchy via size/weight/color first), Gestalt (common region / proximity).
2. Foundations
2.1 Color
Primitives (the brand palette — never used directly in components): --cn-rhinox-* (jersey navy, anchored #2B2F4B), --cn-crimson-* (jersey red, #EC3339), --cn-neutral-* (cool slate), --cn-info-* (peak blue / ODI), --cn-success-* (pitch green / Test), --cn-warning-* (amber). Figma Foundations / Colors node 5892:2.
Semantic roles (what components consume — Figma Semantic node 5893:2):
| Group | Roles | Notes |
|---|---|---|
| Surface | --surface-default (page), --surface-raised (card), --surface-sunken, --surface-overlay, --surface-brand / -deep / -gradient (navy chrome), --surface-inverse | |
| Text | --text-heading, --text-body, --text-muted, --text-subtle, --text-link / -hover, --text-on-brand*, --text-danger/success/warning | pair text with a surface that passes WCAG AA |
| Border | --border-default, --border-strong, --border-subtle, --border-focus, --border-on-brand* | |
| Action | --action-primary* (crimson CTA), --action-secondary*, --action-ghost*, --action-danger* | |
| Status | --status-live, -upcoming, -completed, -abandoned, -rain, -toss, -wicket (+ -bg / -text) | match lifecycle |
Rules:
- Components use semantic roles only, never primitives (
--cn-rhinox-500) and never raw hex. - Dark mode is the
.darkclass on<html>; roles self-flip — never hardcode a dark value in a component. - Reach for color before weight to build hierarchy.
2.2 Typography
Figma Font weights node 5894:2. Encoded as --cn-fw-* tokens.
The rule (one line): Bold (700) is for <h1>–<h6> only. Everything else caps at semibold (600). Extrabold (800) is reserved for the broadcast hero score only (--cn-fw-display, via .cn-display).
| Token | Weight | For |
|---|---|---|
--cn-fw-display | 800 | broadcast/hero DISPLAY only (giant match-header team name). The single sanctioned 800. |
--cn-fw-heading | 700 | h1–h6 only. The one 700. |
--cn-fw-stat | 600 | numerical data: rating, runs, wickets, score, % |
--cn-fw-subhead | 600 | table <th>, kickers, section eyebrows |
--cn-fw-action | 600 | buttons, CTAs, primary nav, tab/pill labels |
--cn-fw-label | 600 | chip/badge labels (T20I, LIVE, team abbr) |
--cn-fw-identifier | 600 | proper nouns in rows: player, team, venue |
--cn-fw-body | 400 | paragraphs, descriptions, commentary |
--cn-fw-meta | 400 | dates, captions, "Last updated" |
Anti-patterns (banned):
- ❌
font-boldon non-headings (player names, table cells) - ❌ raw
font-extrabold(800) anywhere except.cn-display - ❌ bolding identifier and stat in the same row (both shout, neither wins)
- ❌ using weight to signal hover/active — use color + underline
Size: use the Tailwind scale (text-xs…text-3xl). Never text-[Npx] or inline font-size. Weight via .cn-* role classes / --cn-fw-*, never raw font-bold in app code.
Never apply tabular-nums to cricket data cells (scores, overs, runs, stats).
2.3 Spacing
Tailwind spacing scale (p-*, gap-*, m-*). Never hardcode a container width — reuse the layout container (.rhx-archive-container). No <p> subtitle inside a heading/hero.
2.4 Radius
Scale lives in tw-theme.css (@theme). Do not define a parallel --radius-* in tokens.css (it shadows the @theme scale).
| Token | utility | px | For |
|---|---|---|---|
--radius-xs | rounded-xs | 2 | tab accent bar |
--radius-sm | rounded-sm | 4 | buttons, inputs, badges |
--radius-md | rounded-md | 6 | dropdowns, tooltips, alpha tiles |
--radius-lg | rounded-lg | 8 | primary card (--radius-card) |
--radius-xl | rounded-xl | 12 | modals, hero cards |
--radius-2xl | rounded-2xl | 16 | hero blocks (+ documented cn-news-card exception) |
--radius-full | rounded-full | ∞ | pills, toggles |
No rounded-[10px] arbitraries; no raw border-radius: <rem>.
2.5 Elevation
Encodes hierarchy, not decoration. One line: list/feed item → --elevation-flat · grouping panel → --elevation-raised · modal/popover → --elevation-overlay. One radius (--radius-card) for every card.
- Never write a raw
box-shadowon a card shell — consume--elevation-*. - Never patch elevation/radius per-surface (
[data-surface] .card {…}). - Raised vs flat = the count test: one of it on the surface → raised panel; many of it → flat list item.
2.6 Motion
Use the motion tokens (durations / easings); honor prefers-reduced-motion. Figma Guidelines node 5899:2.
2.7 Format bridge
Taxonomy owns the slug; tokens own the color. Components emit data-format="<slug>"; var(--format-accent) reads from the bridge. Never hardcode a format hex. Figma Format bridge node 5895:2.
3. Components
One canonical component per purpose. Pick by what it represents, not how it looks. Deprecated components are never used for new work — migrate on touch. Figma Components / v2026 node 6261:2, UI Kit node 6403:108.
Cards — article → FeatureCard · entity → EntityCard · match → MatchCard · data → cn-panel + cn-data-table · debut → cn-debut-card · container → rhx-card
| Purpose | Canonical | Notes |
|---|---|---|
| Article in a grid | FeatureCard (cn-home-feature-card) | dense rail → cn-home-list-card; hero-only → side/mobile story card |
| Unified news/team-news card | NewsCard | caption/media-top, brand aesthetic |
| Player / team / tournament | EntityCard | hydrated via /entity-cards-data |
| Match (static/SSR/crawlable) | StaticMatchCard (.mcard) | |
| Match (live/hydrated) | MatchCard | fed by /match/{id}/card |
| Data / stats panel | cn-panel (shell) + cn-data-table (list-flex table) | STRICT canonical for any data/panel surface — tables, stats, standings, rankings, polls, filter panels. Replaces rhx-stats-card + 8 ad-hoc systems. (cn-data-panel is a separate Data-Hub namespace.) |
| Empty state (200) | EmptyState | not a 404 — that's NotFoundView |
Deprecated (never for new work): post-card (removed), ad-hoc card, and the old inline mcard/MatchCard dups (now single-owned). Not deprecated — scoped-canonical for their own surfaces: quiz-card (quiz play states), arc-card (venue/season/grounds archives), squad-card (squad rosters).
Navigation affordances
Choose by intent — at most one tabs system per surface (mixing creates ambiguous hierarchy):
| Affordance | Use | Look |
|---|---|---|
.cn-tabs | PRIMARY switch between mutually-exclusive views (2–7), the most-used axis | flat label + underline accent |
.cn-toggle | BINARY mode switch (Men/Women, Light/Dark) | segmented pill capsule, both visible |
.cn-segment | SECONDARY axis of 3+ sub-categories under a primary tabs row | neutral pill track, raised white active chip (no red); --scroll for overflow |
.cn-pills | SECONDARY filter/refinement, multi- or single-select, lower-importance | loose rounded chips, red fill on active |
| Dropdown | long lists (>7) or rare access | — |
SecondaryNav = navy brand band + outline pills (active = white fill).
Buttons
.cn-btn / --action-* roles. Primary = crimson. Text weight ≤ 600.
Shape comes from tokens (
--radius-card,--elevation-*), not per-component values. Detailed component decision tests: see the deep-dive sections this doc supersedes (cards / elevation / radius) — being folded in as appendices.
4. Patterns (cricket domain)
Rules with no generic-UI equivalent — specific to how cricket data renders.
- All-out score format: render
264, not264/10, when all out (pair with an "all out" affordance). System-wide. - Match-card footer (toss / result / lead / chase): always team abbreviation (ESPNcricinfo / Cricbuzz convention) via
cricnepal_mc_normalize_footer_team_names. - Entity names from canonical post, never slugs: match =
Team A vs Team B(cricnepal_match_teams_label); tournament = post title viatournament_id. Never the matchcentre post title or*_slugfor display. - No
tabular-numson score/overs/runs/stat cells. - Result source = cn-engine (single system for result/winner/status). Don't re-introduce ACF into the read path; never auto-infer a DLS result.
5. Accessibility
- Text/surface pairs pass WCAG 2.1 AA contrast. Status/foreground variants exist where the base role fails on a surface (e.g.
--text-successvs the success fill). - Honor
prefers-reduced-motion. - Visible focus via
--border-focus; never remove focus rings. - Min tap target (
--cn-tap-target) on interactive controls.
6. Governance
- Scope — stay in lane. The design system owns tokens, components, a11y. It does not own header layout, IA, or content placement. Token/component work never silently changes navigation or page structure.
- Enforcement > documentation. The drift-killer is the pre-commit lint gate (
.githooks/design-lint.mjs), not goodwill. Live fortext-[Npx]hardcoded sizes +font-extrabold; it checks only newly-added lines so legacy is grandfathered. A rule nobody can merge past beats a doc nobody reads. (Hexfont-bold-on-non-heading are v2 — see Roadmap.)
- No copies. Three drifting
.csstoken files = zero sources of truth. Done: all three surfaces (apps/web, cn-engine, rhinox2) import the onepackages/tokens/tokens.css; their local copies are deleted. - A component isn't canonical until it's here. Promoting a component to canonical/STRICT (e.g.
cn-data-table,cn-segment) is not done until its styleguide entry ships in the same change. This doc trails code = the doc is wrong. (This rule exists because both were canonized in code but missing here.)
Roadmap to fully-single-source
- Done: canonical tokens in
packages/tokens/tokens.css— all three surfaces (apps/web, cn-engine, rhinox2) import it directly (outside-root@import); every local copy deleted. - Done: pre-commit lint gate (
.githooks/design-lint.mjs) — v1 banstext-[Npx]+font-extraboldon newly-added lines. - Next: lint v2 — raw hex in className +
font-boldon non-headings (need false-positive tuning). - Later: Figma → package export (Style Dictionary / Figma API) so value sync is mechanical, not manual.
Appendix — authority index
| Need | Go to |
|---|---|
| A value (hex, weight, spacing) | token package (packages/tokens/tokens.css) |
| What it looks like | Figma cricnepal-v2026 (3OFyWSbBLptoxwkZt0FKcf) |
| A rule / when to use what | this document |
| Color swatches | Figma Colors 5892:2 · Semantic 5893:2 |
| Type | Figma Font weights 5894:2 |
| Format bridge | Figma 5895:2 |
| Guidelines / motion | Figma 5899:2 |
| Brand / logo | Figma 6364:3016 |
| Components | Figma 6261:2 · UI Kit 6403:108 |
| Deep dives (being folded in) | cards.md, elevation.md, radius.md |