Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 92 additions & 43 deletions docs/navui-design-system.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,121 @@
# NavUI design-system contract
# NavUI Default design system

NavUI separates visual decisions from primitive implementation. Radix UI, Base UI, native HTML,
and any future React Aria usage are implementation choices; they are not themes.

`NavUI Default` is an optional, neutral visual preset for modern product, SaaS, developer-tool, and
professional website blocks. It favors clear hierarchy, quiet surfaces, visible focus, restrained
radius, and borders before shadows.

## The boundary

The consumer's shadcn theme owns the standard semantic contract:
Every NavUI block uses the standard shadcn semantic contract: `background`, `foreground`, `card`,
`popover`, `primary`, `secondary`, `muted`, `accent`, `border`, `input`, `ring`, and `destructive`.
Installing a block alone never replaces those consumer values.

Installing `@navui/navui-default` is an explicit choice to apply NavUI's values for that standard
contract. The preset also adds the shared layout decisions that shadcn does not define:

| Foundation | CSS variable | Default value |
| --------------------- | -------------------------- | -------------------------- |
| Display font | `--navui-font-display` | consumer `--font-sans` |
| Wide container | `--navui-container-wide` | `80rem` |
| Page container | `--navui-container` | `72rem` |
| Content container | `--navui-content` | `48rem` |
| Narrow content | `--navui-content-narrow` | `36rem` |
| Horizontal gutter | `--navui-gutter` | `clamp(1rem, 4vw, 1.5rem)` |
| Section spacing | `--navui-section-space` | `clamp(4rem, 8vw, 6rem)` |
| Large section spacing | `--navui-section-space-lg` | `clamp(5rem, 10vw, 8rem)` |

Blocks use NavUI variables with an explicit fallback, for example
`max-w-[var(--navui-container,72rem)]`. This keeps the same block functional when the optional
preset is not installed.

## Color and surfaces

The palette is almost neutral, with only enough cool chroma to avoid a flat grayscale appearance.
Light mode uses a soft off-white page and white cards. Dark mode uses a near-black blue-neutral page
with progressively lighter card, popover, muted, and accent surfaces instead of inverting light
mode or using pure black.

The surface hierarchy is intentionally small:

- colors and surfaces: `background`, `foreground`, `card`, `popover`, `primary`, `secondary`,
`muted`, `accent`, and their foreground pairs
- controls and focus: `border`, `input`, `ring`, and `destructive`
- the base `radius`
1. `background` for the page
2. `muted` or `secondary` for quiet section separation
3. `card` for contained content
4. `popover` for floating content

Blocks use these through normal shadcn utilities such as `bg-background`, `text-foreground`,
`text-muted-foreground`, `border-border`, and `ring-ring`. Installing a block must not replace these
consumer values.
Use `border` or a low-opacity foreground ring for normal separation. Reserve shadows for elements
that genuinely sit above another surface, such as menus, dialogs, and featured cards. Tailwind's
existing `shadow-sm`, `shadow-md`, and overlay shadows are sufficient; NavUI does not add a separate
elevation scale.

NavUI owns only decisions shared across multiple blocks that shadcn does not define:
Important text and control combinations meet WCAG AA contrast targets. Muted text is deliberately
darker in light mode and lighter in dark mode, while `ring` remains strong enough for visible focus.

| Foundation | CSS variable | Initial value |
| --------------------- | -------------------------- | ---------------------- |
| Display font | `--navui-font-display` | consumer `--font-sans` |
| Page container | `--navui-container` | `72rem` |
| Content container | `--navui-content` | `48rem` |
| Narrow content | `--navui-content-narrow` | `36rem` |
| Horizontal gutter | `--navui-gutter` | `1.5rem` |
| Section spacing | `--navui-section-space` | `5rem` |
| Large section spacing | `--navui-section-space-lg` | `7rem` |
## Typography

The default sans family remains the body and display family so consumers do not need another font.
Future blocks should use these role conventions rather than a typography component abstraction:

| Role | Tailwind convention |
| --------------- | --------------------------------------------------------------------------------- |
| Display / hero | responsive `text-4xl` through `text-7xl`, `font-semibold`, tight leading/tracking |
| Section heading | responsive `text-3xl` through `text-5xl`, `font-semibold`, `tracking-tight` |
| Normal heading | `text-lg` through `text-2xl`, `font-medium` or `font-semibold` |
| Body | `text-base leading-7` |
| Supporting text | `text-sm` or `text-base`, relaxed leading, `text-muted-foreground` |
| Small text | `text-sm leading-6` |
| Label | `text-sm font-medium` with normal or slightly tight tracking |

Hero size remains block-level because a compact product hero and an editorial hero should not be
forced into the same scale. The shared `--navui-font-display` role lets a future system change the
family without changing block markup.

Blocks that adopt these foundations use Tailwind arbitrary-value classes with an explicit fallback,
for example `max-w-[var(--navui-container,72rem)]`. The fallback keeps the same block functional
when the optional system is not installed.
## Rhythm, containers, and radius

Grid structure, responsive column counts, one-off gaps, and component-specific sizes stay local to
the block. NavUI does not create per-block variables.
Use `--navui-gutter` for horizontal page padding and one of the container widths for the outer
layout. Use `--navui-section-space` for normal marketing sections and the large value only when a
hero or major transition needs more breathing room.

Inside a block, start with `gap-4` between a heading and description, `gap-6` or `gap-8` between
content groups, and `p-5` or `p-6` for ordinary cards. These are conventions, not global variables;
content density should determine the final local value.

The base shadcn radius is `0.5rem`. Buttons and controls generally use the derived medium/large
radius, cards use large/x-large, and only major panels or overlays should go beyond that. Pills stay
appropriate for badges, segmented controls, and deliberately capsule-shaped actions.

## Consumer modes

### Consumer shadcn theme

Blocks work with the consumer's standard shadcn semantic tokens and ordinary Tailwind utilities.
They must not require `navui-default` merely to render correctly.
Install a block without `navui-default`. It uses the consumer's semantic colors and the fallback
values embedded in NavUI variable references.

### NavUI Default

Consumers can install `@navui/navui-default`. It is a shadcn `registry:style` item, so the CLI merges
its CSS variables into the consumer's configured CSS file. This checkpoint provides only the
shared contract; Phase 4.2 can refine the default palette and visual values without changing block
APIs.
Install the preset explicitly:

## Typography
```bash
npx shadcn@latest add @navui/navui-default
```

The contract adds a display-family role because it is a meaningful cross-block choice. Heading
sizes remain responsive Tailwind classes local to each block: a hero and a card heading should not
share one forced scale. Body, small, and label text continue to use the standard Tailwind scale,
weight, leading, and tracking utilities until real blocks demonstrate a repeated system-level need.
The shadcn CLI merges the light, dark, radius, and NavUI layout variables into the consumer's
configured CSS file. Consumers can adjust those values afterward without changing block code.

## Layout and motion
## What remains block-level

Containers and section rhythm are tokens rather than a `<Section>` component. The markup remains
visible and flexible, and blocks can vary grids or alignment without an adapter API.
Responsive columns, local grid geometry, one-off gaps, component sizes, media treatment, and
content-specific type scale remain inside each block. NavUI does not create per-block variables,
wrapper components, providers, token resolvers, or primitive adapters.

Motion does not have a Phase 4.1 token. Existing interactions already own their durations, easing,
and reduced-motion behavior. A shared motion convention belongs in the design-system layer only
after real blocks reveal stable repetition.
Motion is also local for now. Any existing motion must remain purposeful and respect
`prefers-reduced-motion`; a shared motion system will wait until repeated block behavior proves one
is needed.

## Source of truth

`src/registry/design-systems/navui-default.item.ts` is canonical. Registry generation derives both
the public install artifact and the local Tailwind CSS consumed by NavUI previews from that item.
Generated files must not be edited directly.
the public install artifact and the local CSS consumed by NavUI previews from that item. Generated
files must not be edited directly.
48 changes: 44 additions & 4 deletions public/r/navui-default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,57 @@
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "navui-default",
"title": "NavUI Default",
"description": "The optional default NavUI visual foundations for consistent block typography and layout.",
"description": "An optional neutral NavUI preset for semantic color, restrained radius, typography, and shared layout rhythm.",
"files": [],
"cssVars": {
"light": {
"accent": "oklch(0.945 0.005 264)",
"accent-foreground": "oklch(0.22 0.006 264)",
"background": "oklch(0.99 0.002 264)",
"border": "oklch(0.9 0.006 264)",
"card": "oklch(1 0 0)",
"card-foreground": "oklch(0.18 0.006 264)",
"destructive": "oklch(0.56 0.22 28)",
"foreground": "oklch(0.18 0.006 264)",
"input": "oklch(0.9 0.006 264)",
"muted": "oklch(0.965 0.003 264)",
"muted-foreground": "oklch(0.5 0.012 264)",
"navui-container": "72rem",
"navui-container-wide": "80rem",
"navui-content": "48rem",
"navui-content-narrow": "36rem",
"navui-font-display": "var(--font-sans)",
"navui-gutter": "1.5rem",
"navui-section-space": "5rem",
"navui-section-space-lg": "7rem"
"navui-gutter": "clamp(1rem, 4vw, 1.5rem)",
"navui-section-space": "clamp(4rem, 8vw, 6rem)",
"navui-section-space-lg": "clamp(5rem, 10vw, 8rem)",
"popover": "oklch(1 0 0)",
"popover-foreground": "oklch(0.18 0.006 264)",
"primary": "oklch(0.205 0.006 264)",
"primary-foreground": "oklch(0.985 0.002 264)",
"radius": "0.5rem",
"ring": "oklch(0.55 0.025 264)",
"secondary": "oklch(0.965 0.003 264)",
"secondary-foreground": "oklch(0.24 0.006 264)"
},
"dark": {
"accent": "oklch(0.28 0.008 264)",
"accent-foreground": "oklch(0.96 0.003 264)",
"background": "oklch(0.18 0.006 264)",
"border": "oklch(0.96 0.006 264 / 12%)",
"card": "oklch(0.215 0.007 264)",
"card-foreground": "oklch(0.96 0.003 264)",
"destructive": "oklch(0.7 0.18 25)",
"foreground": "oklch(0.96 0.003 264)",
"input": "oklch(0.96 0.006 264 / 16%)",
"muted": "oklch(0.245 0.007 264)",
"muted-foreground": "oklch(0.72 0.012 264)",
"popover": "oklch(0.225 0.007 264)",
"popover-foreground": "oklch(0.96 0.003 264)",
"primary": "oklch(0.93 0.004 264)",
"primary-foreground": "oklch(0.18 0.006 264)",
"ring": "oklch(0.7 0.025 264)",
"secondary": "oklch(0.26 0.008 264)",
"secondary-foreground": "oklch(0.94 0.003 264)"
}
},
"meta": {
Expand Down
48 changes: 44 additions & 4 deletions public/r/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1127,17 +1127,57 @@
"name": "navui-default",
"type": "registry:style",
"title": "NavUI Default",
"description": "The optional default NavUI visual foundations for consistent block typography and layout.",
"description": "An optional neutral NavUI preset for semantic color, restrained radius, typography, and shared layout rhythm.",
"files": [],
"cssVars": {
"light": {
"accent": "oklch(0.945 0.005 264)",
"accent-foreground": "oklch(0.22 0.006 264)",
"background": "oklch(0.99 0.002 264)",
"border": "oklch(0.9 0.006 264)",
"card": "oklch(1 0 0)",
"card-foreground": "oklch(0.18 0.006 264)",
"destructive": "oklch(0.56 0.22 28)",
"foreground": "oklch(0.18 0.006 264)",
"input": "oklch(0.9 0.006 264)",
"muted": "oklch(0.965 0.003 264)",
"muted-foreground": "oklch(0.5 0.012 264)",
"navui-container": "72rem",
"navui-container-wide": "80rem",
"navui-content": "48rem",
"navui-content-narrow": "36rem",
"navui-font-display": "var(--font-sans)",
"navui-gutter": "1.5rem",
"navui-section-space": "5rem",
"navui-section-space-lg": "7rem"
"navui-gutter": "clamp(1rem, 4vw, 1.5rem)",
"navui-section-space": "clamp(4rem, 8vw, 6rem)",
"navui-section-space-lg": "clamp(5rem, 10vw, 8rem)",
"popover": "oklch(1 0 0)",
"popover-foreground": "oklch(0.18 0.006 264)",
"primary": "oklch(0.205 0.006 264)",
"primary-foreground": "oklch(0.985 0.002 264)",
"radius": "0.5rem",
"ring": "oklch(0.55 0.025 264)",
"secondary": "oklch(0.965 0.003 264)",
"secondary-foreground": "oklch(0.24 0.006 264)"
},
"dark": {
"accent": "oklch(0.28 0.008 264)",
"accent-foreground": "oklch(0.96 0.003 264)",
"background": "oklch(0.18 0.006 264)",
"border": "oklch(0.96 0.006 264 / 12%)",
"card": "oklch(0.215 0.007 264)",
"card-foreground": "oklch(0.96 0.003 264)",
"destructive": "oklch(0.7 0.18 25)",
"foreground": "oklch(0.96 0.003 264)",
"input": "oklch(0.96 0.006 264 / 16%)",
"muted": "oklch(0.245 0.007 264)",
"muted-foreground": "oklch(0.72 0.012 264)",
"popover": "oklch(0.225 0.007 264)",
"popover-foreground": "oklch(0.96 0.003 264)",
"primary": "oklch(0.93 0.004 264)",
"primary-foreground": "oklch(0.18 0.006 264)",
"ring": "oklch(0.7 0.025 264)",
"secondary": "oklch(0.26 0.008 264)",
"secondary-foreground": "oklch(0.94 0.003 264)"
}
},
"meta": {
Expand Down
48 changes: 44 additions & 4 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1199,17 +1199,57 @@
"name": "navui-default",
"type": "registry:style",
"title": "NavUI Default",
"description": "The optional default NavUI visual foundations for consistent block typography and layout.",
"description": "An optional neutral NavUI preset for semantic color, restrained radius, typography, and shared layout rhythm.",
"files": [],
"cssVars": {
"light": {
"accent": "oklch(0.945 0.005 264)",
"accent-foreground": "oklch(0.22 0.006 264)",
"background": "oklch(0.99 0.002 264)",
"border": "oklch(0.9 0.006 264)",
"card": "oklch(1 0 0)",
"card-foreground": "oklch(0.18 0.006 264)",
"destructive": "oklch(0.56 0.22 28)",
"foreground": "oklch(0.18 0.006 264)",
"input": "oklch(0.9 0.006 264)",
"muted": "oklch(0.965 0.003 264)",
"muted-foreground": "oklch(0.5 0.012 264)",
"navui-container": "72rem",
"navui-container-wide": "80rem",
"navui-content": "48rem",
"navui-content-narrow": "36rem",
"navui-font-display": "var(--font-sans)",
"navui-gutter": "1.5rem",
"navui-section-space": "5rem",
"navui-section-space-lg": "7rem"
"navui-gutter": "clamp(1rem, 4vw, 1.5rem)",
"navui-section-space": "clamp(4rem, 8vw, 6rem)",
"navui-section-space-lg": "clamp(5rem, 10vw, 8rem)",
"popover": "oklch(1 0 0)",
"popover-foreground": "oklch(0.18 0.006 264)",
"primary": "oklch(0.205 0.006 264)",
"primary-foreground": "oklch(0.985 0.002 264)",
"radius": "0.5rem",
"ring": "oklch(0.55 0.025 264)",
"secondary": "oklch(0.965 0.003 264)",
"secondary-foreground": "oklch(0.24 0.006 264)"
},
"dark": {
"accent": "oklch(0.28 0.008 264)",
"accent-foreground": "oklch(0.96 0.003 264)",
"background": "oklch(0.18 0.006 264)",
"border": "oklch(0.96 0.006 264 / 12%)",
"card": "oklch(0.215 0.007 264)",
"card-foreground": "oklch(0.96 0.003 264)",
"destructive": "oklch(0.7 0.18 25)",
"foreground": "oklch(0.96 0.003 264)",
"input": "oklch(0.96 0.006 264 / 16%)",
"muted": "oklch(0.245 0.007 264)",
"muted-foreground": "oklch(0.72 0.012 264)",
"popover": "oklch(0.225 0.007 264)",
"popover-foreground": "oklch(0.96 0.003 264)",
"primary": "oklch(0.93 0.004 264)",
"primary-foreground": "oklch(0.18 0.006 264)",
"ring": "oklch(0.7 0.025 264)",
"secondary": "oklch(0.26 0.008 264)",
"secondary-foreground": "oklch(0.94 0.003 264)"
}
},
"meta": {
Expand Down
Loading