This document defines the canonical UI system for Requo. Use shadcn/ui primitives, Requo design tokens, and shared layout wrappers before introducing page-level styling.
- Build on the existing system. Prefer
components/ui/*,components/shared/*, and semantic tokens fromapp/globals.cssover custom markup. - Use semantic styling. Reach for
bg-primary,text-muted-foreground,surface-card,control-surface, and existing variants before raw palette utilities. - Keep primitives stable. Use
classNamefor layout, width, placement, and composition. Do not restyle button, input, card, or overlay visuals per feature. - Favor reusable patterns. Forms, list pages, dashboards, and empty states should share the same composition and spacing rules.
- Accessibility is default behavior. Maintain contrast, visible focus states, semantic headings,
aria-invalid, disabled states, and accessible labels for icon-only actions.
Use semantic tokens, not raw color classes, for all product UI.
| Token | Role | Light | Dark |
|---|---|---|---|
background |
App background | #f7f9f7 |
#121212 |
foreground |
Primary text | #172b24 |
#ededed |
primary |
Primary actions and emphasis | #008060 |
#008060 |
primary-foreground |
Text on primary surfaces | #f4fffb |
#f4fffb |
secondary |
Subtle surface/action | #f0f3f1 |
#1f1f1f |
muted |
Muted surface | #eef2ef |
#171717 |
muted-foreground |
Supporting text | #5f756c |
#8b9092 |
accent |
Hover/selected accent surface | #f0f3f1 |
#242424 |
destructive |
Error and destructive actions | #c9372c |
#c9372c |
border |
Borders and dividers | #d6ddd8 |
rgb(255 255 255 / 0.07) |
input |
Control border base | #d6ddd8 |
rgb(255 255 255 / 0.08) |
ring |
Focus ring | #1b9b79 |
#1b9b79 |
Extended token groups:
surface-*: elevated cards, soft panels, section shells, footers, and muted surfaces.control-*: interactive controls, hover states, primary/destructive control shadows.overlay-*: dialog, sheet, select, and popover surfaces.table-*: table header, footer, hover, and selected row states.sidebar-*: sidebar background, text, border, accent, and ring tokens.radius-*: derived from--radius; use the existing scale instead of hard-coded radii.motion-*: shared durations, easing, and lift distance for subtle motion.
State rules:
- Focus uses
ring. - Error uses
destructive. - Disabled uses lower emphasis, muted surfaces, and reduced opacity.
- Success, warning, and info are not global product tokens yet. Use centralized component patterns only; do not introduce raw
emerald,amber,blue, or similar utility colors in feature code.
Fonts:
font-sans: Geist Sansfont-heading: Geist Sansfont-mono: Geist Mono
Use role-based typography instead of ad hoc text sizes.
| Role | Standard treatment | Use |
|---|---|---|
| Display hero | font-heading text-5xl leading-[0.96] font-semibold tracking-tight |
Marketing and auth hero only |
| Page title | font-heading text-[2rem] sm:text-[2.3rem] leading-tight font-semibold tracking-tight |
PageHeader, detail headers |
| Section title | font-heading text-lg leading-tight font-semibold tracking-tight |
CardTitle, DashboardSection |
| Form section title | text-[0.95rem] font-semibold tracking-tight |
FormSection titles |
| Body | text-sm leading-6 |
Default UI copy |
| Supporting body | text-sm leading-6 text-muted-foreground |
Descriptions and help text |
| Long-form supporting | text-sm leading-7 text-muted-foreground |
Page descriptions and detail copy |
| Field label | text-sm leading-[1.35] font-medium |
FieldLabel |
| Meta label | text-[0.68rem] font-medium uppercase tracking-[0.14em] text-muted-foreground |
Filters, eyebrow labels, meta chips |
| Caption/code | font-mono text-[0.72rem] |
IDs, tokens, technical metadata |
Use a compact spacing scale consistently: 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10, 12, 16.
Spacing rules:
- Prefer
gap-*overspace-y-*andspace-x-*. - Use
gap-2togap-3for tight control groups and inline actions. - Use
gap-4togap-6for card bodies, form sections, and stacked content. - Use
gap-6togap-8for page sections and dashboard page flow. - Use
px-4 sm:px-6 xl:px-8and matchingpy-*for page shells and major surfaces. - Use
px-6 py-6as the default card section rhythm, withsize="sm"only for compact surfaces.
Radius rules:
- Controls:
rounded-lg - Cards, tables, sections, empty states:
rounded-xl - Large overlays:
rounded-2xl - Avoid arbitrary radius values in feature code unless the value is promoted into tokens or shared classes.
- Use the shared motion tokens:
--motion-duration-fast,--motion-duration-base,--motion-duration-slow,--motion-ease-standard,--motion-ease-emphasized. - Motion should clarify interaction, not decorate the UI.
- Use subtle lift, shadow, opacity, and panel transitions. Avoid flashy animation or large movement on core product pages.
Variant names should stay consistent across primitives:
default: standard or primary presentationsecondary: subdued emphasisoutline: bordered neutral actionghost: low-emphasis actiondestructive: irreversible or error-related actionlink: text-only action
Size names should stay consistent where supported:
xs,sm,default,lgicon,icon-xs,icon-sm,icon-lgfor square icon actions
Do not invent new variant or size names in feature code. Add a new name only if it will be shared across the system.
- Always use
Button. - Use
variant="default"for the primary action in a section or dialog. - Use
outlineorsecondaryfor secondary actions,ghostfor low-emphasis actions,destructivefor irreversible actions, andlinkfor inline navigation. - Place icons with
data-icon="inline-start"ordata-icon="inline-end". - Loading state is
disabled+Spinner; keep the text label visible. - Icon-only buttons must include an accessible label with visible text or
sr-onlycontent.
- Use
Input,Textarea,Select,SelectTrigger, andSelectContentfromcomponents/ui/*. - These controls share the same
control-surfacevisual language. Do not override their color, border, or focus styling in page code. - Use
FieldGroup,Field,FieldLabel,FieldContent,FieldDescription, andFieldErrorfor form structure. - Validation rules:
data-invalidonFieldaria-invalidon the controldisabledon the control anddata-disabledon the field wrapper when needed
- Keep labels above controls by default. Use responsive horizontal field layouts only through the shared field orientation APIs.
- Use full card composition:
CardHeader,CardTitle,CardDescription,CardContent,CardFooter. - Use
DashboardSectionfor standard dashboard content blocks instead of rebuilding card structure. - Use
Emptyfor empty states,Alertfor inline notice or error messaging,Badgefor compact status/metadata,Skeletonfor loading placeholders, andSpinnerfor active async work. - Keep badges semantic. Prefer
default,secondary,outline, orghostover raw status colors.
- Use
Dialogfor blocking flows and focused modal tasks. - Use
Sheetfor contextual side panels, mobile filters, and temporary secondary workflows. DialogTitleorSheetTitleis required, even if visually hidden.- Keep overlay content within the shared header/body/footer structure. Preserve close controls and focus-visible states.
- Use
Tabs,TabsList, andTabsTriggerwithvariant="default"orvariant="line"only.
- Standard list pattern:
DashboardToolbar+DashboardTableContainer+Table. - Use table helper classes and shared text treatments for row titles, supporting text, and metadata.
- Empty state uses
Emptyor the shared list empty wrapper. - Loading state uses
Skeleton; async filtering/search usesSpinner. - Selected, hovered, and expanded row states should come from the existing
table-*tokens and classes.
- Standard form composition:
FormSection+FieldGroup+Field+FormActions. - Group related controls into sections with a clear title and short description.
- Default to one-column forms. Use responsive splits only when it improves scanning and does not hurt completion.
- Keep form actions at the end of the section or page, aligned with the shared
FormActionspatterns.
- Standard page composition:
DashboardPage+PageHeader+ shared section wrappers. - Use
DashboardSectionfor bounded content areas. - Use
DashboardDetailLayoutfor two-column detail pages andDashboardSidebarStackfor supporting side content. - Use
DashboardActionsRowfor clusters of page or section actions. - Keep dashboard spacing consistent with the shared page rhythm: section gaps at
gap-6togap-8, action gaps atgap-2.5.
- Use
DashboardToolbarfor search, filters, result counts, and clear actions. - Keep filters in the toolbar on desktop and in a
Sheeton mobile. - Wrap desktop tables in
DashboardTableContainer. - For mobile list views, keep the same content hierarchy and token usage as the desktop table shell.
- Auth, marketing, and public pages still use the same semantic color and surface system.
- Reuse existing shared classes such as
hero-panel,section-panel,soft-panel,eyebrow, andmeta-label. - Avoid one-off gradients, shadows, radii, or raw palette utilities unless they are promoted into shared tokens or classes first.
- Use semantic tokens and shared utility classes.
- Reuse
components/ui/*andcomponents/shared/*before creating custom markup. - Prefer
gap-*for spacing and shared wrappers for layout. - Keep variants and sizes aligned with the documented names.
- Preserve focus states, contrast, and accessible labels.
- Use inline
styleonly for structural CSS variable plumbing, not visual styling.
- Do not use inline visual styles for colors, shadows, radii, spacing, or typography.
- Do not apply raw palette utilities like
text-emerald-*orbg-amber-*in feature code. - Do not bypass
Field*,Card*,Dialog*,Sheet*, or table composition when a shared pattern exists. - Do not use
space-y-*orspace-x-*for new layout work. - Do not introduce new visual variants casually or restyle primitives page by page.
<Button disabled={isSaving} type="submit">
{isSaving ? <Spinner aria-hidden="true" /> : null}
Save changes
</Button>const nameError = "";
<FormSection
title="Business details"
description="Use the same naming and contact details customers will see."
>
<FieldGroup>
<Field data-invalid={Boolean(nameError)}>
<FieldLabel htmlFor="business-name">Business name</FieldLabel>
<FieldContent>
<Input id="business-name" aria-invalid={Boolean(nameError)} />
</FieldContent>
<FieldError>{nameError}</FieldError>
</Field>
</FieldGroup>
<FormActions>
<Button variant="outline" type="button">
Cancel
</Button>
<Button type="submit">Save</Button>
</FormActions>
</FormSection><DashboardPage>
<PageHeader
eyebrow="Inquiries"
title="Customer requests"
description="Review, filter, and follow up from one place."
/>
<DataListToolbar
description="Search by customer, request, or form."
resultLabel={`${count} results`}
{...toolbarProps}
/>
<DashboardTableContainer>
<Table>{/* rows */}</Table>
</DashboardTableContainer>
</DashboardPage>- Replace remaining
space-y-*andspace-x-*stacks withflex/gridplusgap-*. - Replace raw status color utilities with centralized badge, alert, or shared status patterns.
- Reduce repeated arbitrary radii, shadows, and hard-coded visual values by promoting reusable classes or tokens when a pattern repeats.