docs/NAMING.md

Naming Conventions

Extracted from: Token-Architecture.pdf, A-Shared-Language-for-Design-and-Engineering.pdf, Design-System-Guide.pdf, Component-Catalogue.pdf — DEPARTMNT UK Ltd, Brandon Eddy, v2.0, 2026-02-03


The Formula

Every component, variant, size, and state follows one scannable pattern:

{component}/{variant}/{size}/{state}

Examples:

  • button/primary/md/default
  • button/secondary/sm/disabled
  • input/text/md/error
  • avatar/circle/lg/default
  • badge/success/sm/default

The goal is a "barcode" — a designer and a developer should be able to map between Figma and code instantly, with zero interpretation.


Component Names

PascalCase in code. Plain noun in Figma. No suffixes like "Component" or "Element".

Figma name Code name
Button Button
Input Input
Card Card
Avatar Avatar
Badge Badge
Checkbox Checkbox
Switch Switch
Select Select
Dialog Dialog
Tooltip Tooltip
Tabs Tabs
Accordion Accordion
Toast Toast

Variant Names

Lowercase. Describes visual style, not hierarchy.

Preferred variant vocabulary:

  • primary secondary ghost destructive outline solid
  • default success warning error info
  • elevated outlined filled

Do not use 1, A, blue, or brand names as variant values.


Size Names

Always use words, never numbers as prop values.

Token px value
xs 24px
sm 32px
md 40px
lg 48px
xl 64px

Components may not support every size — document which sizes a component accepts in its props type definition.


State Names

Four states are expressed as props (code handles them):

  • default — no prop needed, implicit
  • disabled — disabled boolean prop
  • loading — loading boolean prop
  • error — error string prop (carries the message)

Three states are NOT expressed as props (CSS handles them):

  • hover — :hover pseudo-class only
  • focus — :focus-visible pseudo-class only
  • active — :active pseudo-class only

Never create Figma variants or React props for hover, focus, or active.


Layer / Anatomy Names (Radix components)

Figma layer names for Radix-based components must exactly match the Radix anatomy. This is a hard contract — no synonyms, no paraphrasing.

Radix component Required layer names
Dialog Overlay Content Close Title Description Body
AlertDialog Overlay Content Title Description Cancel Action
Select Trigger Value Icon Content Viewport Item Separator
Tooltip Trigger Content Arrow
Tabs List Trigger Content
Accordion Item Header Trigger Content
Toast Root Title Description Action Close Viewport
Popover Trigger Content Arrow Close Body
DropdownMenu Trigger Content Label Item Separator
Avatar Image Fallback
Checkbox Root Indicator
Switch Root Thumb

Colour Profiles

A colour profile decides where a brand's accent lands in the UI. Each brand declares one at Tier 2 via colorProfile, and DepartmntTheme stamps it on the themed subtree as data-color-profile, which the generated DEPARTMNT layer reads.

colorProfile Display label Accent paints… How it's applied
default Default (action / accent) Interactive controls — buttons, links, states Radix Themes' native behaviour (no override)
inverted Inverted (chrome / surface) Structural frame — panels, surfaces, chrome [data-color-profile='inverted'] .radix-themes repaints --color-panel-solid / --color-surface with the accent scale
custom Custom (saved profile) A per-role mapping you define Per-role --var: var(--source) overrides set on the Theme element

The two named slots Chrome / Surface and Action / Accent still exist as Tier-2 tokens (--brand-chrome-bg/-fg, --brand-action-bg/-fg) describing each region; the profile chooses which one the accent flows into.

Rules:

  • A brand sets colorProfile in tokens.json; default is default.
  • inverted is for brands like H.LLS where the signature colour is the surface (orange chrome on a dark page), not the buttons.
  • custom is authored in Portal Design Studio's Theme Lab (tiered token dropdown per role) and saved as a reusable mapping; use it to design a mapping before baking it into a brand.
  • Always alias brand/role tokens through Tier 3 in component CSS — never hardcode a brand hex.

Module Names

Domain prefix + PascalCase name. Domain is always lowercase.

{domain}/{ComponentName}

Examples:

  • auth/LoginForm
  • auth/RegisterForm
  • dashboard/StatsWidget
  • product/ProductCard
  • checkout/PaymentCard
  • shared/Header
  • shared/Footer
  • shared/Sidebar

Module components are suffixed Module (HeroModule, EventModule) and live in /modules. Each is built on Radix Themes + our token layer and laid out with auto layout (Flex/Grid, fluid widths, no fixed pixel frames) so it reflows at any container size.

Standard vs Advanced

Every module is one of two classes. The distinction is behaviour, not visual weight.

Class What it is Client effort Examples
Standard Mostly static content modules — a client just fills in copy/media and posts it. No data wiring, no internal state. Post-and-go HeroModule, RichTextModule, GalleryModule, QuoteModule
Advanced Complex modules with additional functionality — data, state, or commerce behaviour (tickets, carts, feeds). Configured / connected EventModule, ProductModule, FeedModule, TicketModule

Rule of thumb: if a content editor can fully use it by typing text and dropping an image, it's Standard. If it needs data, options, or wired actions to function, it's Advanced. The class is recorded as tier: 'standard' | 'advanced' in studio/lib/modules.ts and drives the Standard/Advanced tabs in Portal Design Studio → Module Preview.


Screen Names

Number + flow + screen name + device.

{NN}_{flow}/{screen}/{device}

Examples:

  • 01_onboarding/splash/mobile
  • 02_auth/login/mobile
  • 02_auth/login/desktop
  • 03_dashboard/home/desktop

File & Folder Names

Thing Convention Example
Component folder PascalCase Button/ ProductCard/
Component file PascalCase Button.tsx
CSS Module PascalCase + .module.css Button.module.css
Barrel export lowercase index.ts
Hook camelCase with use prefix useTheme.ts
Utility camelCase cn.ts formatDate.ts
Token file kebab-case tokens.json tokens.css
Page routes kebab-case reset-password/

CSS Class Names

camelCase within CSS Modules. No BEM, no utility prefixes.

/* correct */
.button {}
.buttonPrimary {}
.loadingSpinner {}

/* incorrect */
.Button {}
.button--primary {}
.loading-spinner {}
.btn {}

Token Names

Three-tier structure. See /tokens for the full reference.

--{tier}-{category}-{scale}

Tier 1 (global):   --global-spacing-4
Tier 2 (brand):    --brand-color-primary
Tier 3 (component): --button-bg, --button-padding-x

Components reference Tier 3 only. Never reference Tier 1 directly inside a component file.

The brand tier is also mapped onto Radix Themes' variables (--accent-1…12, --gray-1…12, contrast/surface/indicator/track) so stock Radix Themes components pick up the brand automatically. This mapping is generated — never hand-edit Radix variables.


Customization Spectrum

This system is Radix Themes-first. Build every component at the lowest-effort tier that achieves the design — default to Tier 1, escalate only when the design demands it. See it demonstrated live in Portal Design Studio → Theme Lab.

Tier When Mechanism
1 — Token override Standard component, brand restyle Override Radix Themes CSS variables per [data-brand] (accent/gray scale, radius, fonts)
2 — Wrap + CSS Module A Themes component needs visual tweaks Render the Themes component, pass className, layer a CSS Module
3 — Headless + CSS Module Bespoke / signature component Build on a headless @radix-ui/react-* primitive, style fully from tokens

Apply a brand with the <DepartmntTheme> provider (wraps Radix <Theme>, maps the brand to its appearance, and scopes the brand's tokens). Standard layout/typography comes from Radix Themes (Box, Flex, Grid, Text, Heading) — /primitives is for bespoke base elements only.


Preview Examples

Components appear in Portal Design Studio's Lookbook via a hybrid convention:

  • Auto-extract by default — Studio renders every variant × size × state from the prop union types. No extra file needed.
  • Co-located <Name>.preview.tsx — add one only when a component needs bespoke setup (specific children, context, or data) that auto-extraction can't capture.

Portal Design Studio — layout regions

These names apply only to the internal Studio app (not the published @departmnt/ui library). The Studio shell is a three-region layout:

Region Where What it holds
Nav Rail Left shell <aside> Wordmark, brand switcher, and the view navigation (Foundations, Theme Lab, …).
Main Centre, between the rails The active view's content (e.g. the Module Preview device frame).
Control Rail Right shell <aside> The global Appearance Toggle plus the active view's contextual controls/info, which each view pushes up to the shell via the useRail() hook.
  • Appearance Toggle — the light/dark switch (headless @radix-ui/react-switch with a stateful sun/moon knob icon) that lives at the top of the Control Rail and drives the shared appearance context consumed by the preview views.
  • The Nav Rail and Control Rail are siblings of Main at the shell level — both <aside> elements, sticky and full height. View-specific panels (Theme Lab options, Module Preview module info) render inside the Control Rail via useRail(), never as their own page-level column.