feat: design system v1.0.0 — reconcile tokens from cross-repo audit
Extracted real production usage from 8 KDC repos and consolidated the consensus (kdcsurveyadd / kdcvault / kdc_void_planner) into the canonical design.md + tokens. Highlights: - brand.ink #000000 -> #1A1530 (purple-tinted, matches real usage) - brand.canvas #F5F4F0 -> #F7F4ED (warm) - neutral ramp rebuilt around ink (purple-tinted, not cold slate) - semantic palette retuned for warm canvas + soft tints - elevation shadows retinted with brand-ink rgb - new accent.gold (product) and accent.mint (marketing) tokens - real themes/light, themes/dark, themes/high-contrast - fleshed out every component + foundation README - docs/consolidation-2026-05.md captures the full audit + drift inventory Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-2
@@ -4,7 +4,29 @@ All notable changes to the KDC Design System will be documented in this file.
|
||||
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
||||
this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
## [1.0.0] — 2026-05-17
|
||||
|
||||
### Changed (breaking)
|
||||
- **Brand ink** `brand.ink` `#000000` → `#1A1530` (purple-tinted; matches all on-brand product surfaces).
|
||||
- **Canvas** `brand.canvas` `#F5F4F0` → `#F7F4ED` (warmer; consensus across surveyadd / vault / void).
|
||||
- **Neutral ramp** swapped from cold slate (`#EDEFF3` / `#6B7280` / `#111827`) to a purple-tinted ramp aligned with `brand.ink`.
|
||||
- **Semantic palette** retuned for the warm canvas — `success` `#16A34A` → `#2F7A5A`, `warning` `#F59E0B` → `#C9892B`, `danger` `#DC2626` → `#B0382F`, `info` `#0EA5E9` → `#5C2D90`. Each now ships a `*Soft` background tint.
|
||||
- **Elevation shadows** retinted with `rgb(26 21 48)` (brand ink) instead of `rgb(17 24 39)` (slate). New keys: `elevation.focusRing`.
|
||||
- **`brand.primaryHover`** `#582A8D` → `#5C2D90` (matches `primary-2` in product CSS).
|
||||
|
||||
### Added
|
||||
- Initial repository scaffold: `design.md`, brand, foundations, components, themes, templates, docs.
|
||||
- `brand.primaryLight` `#8456C2`, `brand.primarySoft` `#F1E9F8`, `brand.canvasCool` `#F7F6FB`.
|
||||
- `accent.gold` `#C9892B` (+ strong/soft), `accent.mint` `#46C194` (workspace marketing).
|
||||
- `scale.brand-50/100/200/500/600/700/800` for Tailwind-style usage.
|
||||
- `neutral.50/150/400/600/700/800` (extra steps).
|
||||
- Semantic-soft tints, role tokens `text.soft`, `text.disabled`, `text.brand`, `border.subtle`.
|
||||
- Component variants in `design.md`: `button.accent`, `button.outline`, `card.elevated`, `badge.success/warning/danger`.
|
||||
- Fleshed-out READMEs for all components (buttons, cards, inputs, badges, modals, forms, navigation, tables, tooltips) and foundations (colors, typography, spacing, radius, elevation, motion, breakpoints, grid).
|
||||
- Real theme files for `themes/light`, `themes/dark`, `themes/high-contrast`.
|
||||
- `docs/consolidation-2026-05.md` — extraction report across the 8 KDC repos.
|
||||
|
||||
### Notes
|
||||
- Reconciliation source: `kdcsurveyadd`, `kdcvault`, `kdc_void_planner` (refined product family) and `k-d-c-workspace` (marketing dashboard). These three product apps share an essentially identical implementation; the design system now reflects their consensus.
|
||||
- Drift identified in `kdcdiary`, `kdctracker`, `kdcdocs`, `kdcmapper` — different purples and typefaces. Tracked in the consolidation report; migration is out of scope for this release.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
@@ -39,7 +39,7 @@ KDCDesignSystem/
|
||||
|
||||
## Status
|
||||
|
||||
Version: **beta**. Tokens and components are still settling — expect breaking changes.
|
||||
Version: **1.0.0** (reconciled 2026-05-17). Canonical palette and components match the production implementations in `kdcsurveyadd`, `kdcvault`, and `kdc_void_planner`. See [`docs/consolidation-2026-05.md`](./docs/consolidation-2026-05.md) for the cross-repo audit and outstanding drift in `kdcdiary` / `kdctracker` / `kdcdocs` / `kdcmapper`.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
+28
-12
@@ -1,25 +1,41 @@
|
||||
# Badges
|
||||
|
||||
Specification for the **badges** primitive.
|
||||
Small inline labels for status, counts, and metadata. Pill-shaped by default. Reference: `kdcvault/frontend/src/components/ui/badge.tsx`.
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
See `anatomy.svg`. Optional leading dot or icon · label · `rounded.full` shape · 22px default height.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
| Token | Use | Background | Text |
|
||||
|-------|-----|------------|------|
|
||||
| `badge.default` | Neutral tag | `brand.primarySoft` | `brand.primaryHover` |
|
||||
| `badge.success` | Positive status | `semantic.successSoft` | `semantic.success` |
|
||||
| `badge.warning` | Caution / attention | `semantic.warningSoft` | `semantic.warning` |
|
||||
| `badge.danger` | Failure / blocked | `semantic.dangerSoft` | `semantic.danger` |
|
||||
| `badge.outline` | Inline tag w/o fill | transparent | `text.muted` + `border.default` |
|
||||
| `badge.solid` | High-emphasis count | `brand.primary` | `text.inverse` |
|
||||
|
||||
## Sizes
|
||||
|
||||
| Size | Height | Padding | Typography |
|
||||
|------|--------|---------|------------|
|
||||
| `sm` | 18px | `0 6px` | `caption` |
|
||||
| `md` (default) | 22px | `2px 10px` | `caption` |
|
||||
| `lg` | 26px | `4px 12px` | `bodyS` |
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.badges.*`.
|
||||
|
||||
`components.badge.*`, `colors.semantic.*Soft`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- Decorative badges don't need ARIA, but status badges should set `role="status"` and include a text label (not colour alone).
|
||||
- Counts greater than 99 should render as `99+`.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Pair colour with text or an icon to convey status.
|
||||
- ❌ Don't stack more than two badges in a row — fold them into a single chip group instead.
|
||||
- ❌ Don't use `badge.solid` for ambient counts; reserve it for active selections.
|
||||
|
||||
@@ -1,25 +1,58 @@
|
||||
# Buttons
|
||||
|
||||
Specification for the **buttons** primitive.
|
||||
Specification for the **buttons** primitive, reconciled from production usage in kdcsurveyadd, kdcvault, and kdc_void_planner (CVA-driven shadcn variants).
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
See `anatomy.svg` — label · optional leading/trailing icon · 8px gap · 40px height (default size).
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
|
||||
| Token | Use | Background | Text |
|
||||
|-------|-----|------------|------|
|
||||
| `button.primary` | Default CTA (one per surface) | `brand.primary` | `text.inverse` |
|
||||
| `button.accent` | Emphasis / submit / commit action | `accent.gold` | `text.inverse` |
|
||||
| `button.secondary` | Neutral default action | `surface.subtle` | `text.default` |
|
||||
| `button.outline` | Lower-emphasis bordered | transparent | `text.default` + `border.strong` |
|
||||
| `button.ghost` | Tertiary, inline within text | transparent | `brand.primary` |
|
||||
| `button.destructive` | Irreversible negative action | `semantic.danger` | `text.inverse` |
|
||||
| `button.link` | Visually a link, behaves as a button | transparent | `brand.primary` (underlined on hover) |
|
||||
|
||||
Reference implementation: `kdcvault/frontend/src/components/ui/button.tsx` (class-variance-authority).
|
||||
|
||||
## Sizes
|
||||
|
||||
| Size | Height | Padding | Typography |
|
||||
|------|--------|---------|------------|
|
||||
| `xs` | 28px | `4px 10px` | `caption` |
|
||||
| `sm` | 32px | `6px 12px` | `bodyS` |
|
||||
| `md` (default) | 40px | `10px 16px` | `bodyM` |
|
||||
| `lg` | 48px | `12px 20px` | `bodyM` |
|
||||
| `icon` | 40×40px | `0` | — |
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
|
||||
`default · hover · focus-visible · active/pressed · disabled · loading`
|
||||
|
||||
- **Hover** swaps `primary` → `primaryHover` (`#5C2D90`), darkens accent by ~12%.
|
||||
- **Focus-visible** applies `elevation.focusRing` (`0 0 0 3px rgba(107,53,167,0.18)`).
|
||||
- **Disabled** sets `opacity: 0.5` and `pointer-events: none`. Keep colour — don't desaturate.
|
||||
- **Loading** swaps the label for a 14px spinner; preserve width to avoid layout shift.
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.buttons.*`.
|
||||
|
||||
See `design.md` → `components.button.*` and `elevation.focusRing`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- All buttons are keyboard-reachable and have a visible focus ring.
|
||||
- Icon-only buttons must carry `aria-label`.
|
||||
- Focus and hover states meet ≥ 3:1 contrast against the button surface.
|
||||
- Touch target ≥ 44×44px on mobile (`md` size satisfies this).
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ One `primary` *or* one `accent` per surface, never both side-by-side.
|
||||
- ✅ Use `accent.gold` for the *forward-moving* action in a multi-step flow.
|
||||
- ❌ Don't hardcode `#6B35A7` — use `colors.brand.primary`.
|
||||
- ❌ Don't put two CTAs of equal emphasis next to each other.
|
||||
|
||||
+23
-11
@@ -1,25 +1,37 @@
|
||||
# Cards
|
||||
|
||||
Specification for the **cards** primitive.
|
||||
A surface for grouping related content. Two canonical variants: `default` (flat, hairline border) and `elevated` (lifted shadow).
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
See `anatomy.svg`. Standard parts: `CardHeader` · `CardTitle` · `CardDescription` · `CardAction` (top-right slot) · `CardContent` · `CardFooter`. Reference: `kdcvault/frontend/src/components/ui/card.tsx`.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
|
||||
| Token | Background | Border | Shadow | Radius | Padding |
|
||||
|-------|------------|--------|--------|--------|---------|
|
||||
| `card.default` | `surface.background` | `border.default` (1px) | `elevation.1` | `rounded.lg` (12px) | 24px |
|
||||
| `card.elevated` | `surface.background` | none | `elevation.3` | `rounded.xl` (16px) | 28px |
|
||||
| `card.muted` | `surface.subtle` | `border.subtle` | none | `rounded.lg` | 24px |
|
||||
| `card.outlined` | `surface.background` | `border.strong` (1px) | none | `rounded.lg` | 24px |
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
|
||||
- **Hover (interactive cards only)** — bump shadow one level (`1` → `2`) and shift `translateY(-2px)` over `motion.duration.fast`.
|
||||
- **Focus-visible** — apply `elevation.focusRing` on the card root.
|
||||
- **Selected** — 2px `brand.primary` left border or full `border.strong` ring.
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.cards.*`.
|
||||
|
||||
`components.card.*`, `elevation.1`–`3`, `colors.surface.*`, `colors.border.*`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- If the card is clickable, the entire card is the hit target; expose it as a `<button>` or `<a>` (not a `<div onClick>`).
|
||||
- Don't rely on hover state alone to indicate interactivity — pair with cursor + raised elevation.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Use `card.default` for list rows and grid tiles. Reserve `card.elevated` for modals, popovers, and feature highlights.
|
||||
- ❌ Don't nest cards more than one level deep — collapse to sections instead.
|
||||
- ❌ Don't mix `card.default` and `card.elevated` in the same grid.
|
||||
|
||||
+37
-13
@@ -1,25 +1,49 @@
|
||||
# Forms
|
||||
|
||||
Specification for the **forms** primitive.
|
||||
Form composition: field stack, label / helper / error pairing, multi-step wizards. Reference: `kdcsurveyadd/components/surveys/SurveyWizard.tsx` (4-step wizard) and `kdcsurveyadd/components/surveys/StepIndicator.tsx`.
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
See `anatomy.svg`. Field row = label + optional helper hint + control + error slot. Sections separated by 32px; fields within a section by 16px.
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
## Field structure
|
||||
|
||||
```
|
||||
<label> field name (bodyM, 600, text.default)
|
||||
<helper> optional hint (bodyS, 400, text.muted)
|
||||
<input> (input.default)
|
||||
<error> message when invalid (bodyS, 500, semantic.danger, role="alert")
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
- **Single column** by default — multi-column only for ≥ 3 closely-related short fields (address, date range).
|
||||
- **Field width** matches expected content: numeric → 120px, short text → 240px, free text → fill container.
|
||||
- **Required indicator** — append `*` to label in `semantic.danger`; never use placeholder text or icon-only.
|
||||
- **Optional fields** — append `(optional)` in `text.muted` `caption`.
|
||||
|
||||
## Wizard pattern (multi-step)
|
||||
|
||||
- Step indicator on top: `step n of N` + horizontal progress (`StepIndicator`).
|
||||
- Context strip below — pills summarising decisions so far (jump-back affordance).
|
||||
- Footer fixed: `Back` (ghost, left) · `Save & exit` (outline) · `Continue` / `Submit` (primary or accent, right).
|
||||
- Validate per-step on `Continue`; never reveal step `n+1` errors prematurely.
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.forms.*`.
|
||||
|
||||
`components.input.*`, `components.button.*`, `colors.semantic.danger`, `typography.bodyM`/`bodyS`/`caption`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- Use a `<form>` element with explicit `<button type="submit">`.
|
||||
- Group related fields with `<fieldset>` + `<legend>`.
|
||||
- Errors are announced via `role="alert"` and referenced by `aria-describedby`.
|
||||
- On submit failure, focus moves to the first invalid field.
|
||||
- Wizard step indicator uses `aria-current="step"` on the active step.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Validate on blur for individual fields; on submit for the whole form.
|
||||
- ✅ Preserve user input across step changes — never silently discard.
|
||||
- ❌ Don't show inline validation on first focus (only on blur or submit attempt).
|
||||
- ❌ Don't disable the submit button to indicate invalid state — show the errors instead.
|
||||
|
||||
+35
-11
@@ -1,25 +1,49 @@
|
||||
# Inputs
|
||||
|
||||
Specification for the **inputs** primitive.
|
||||
Text inputs, textareas, selects. Single visual treatment, semantic states. Reference: `kdcvault/frontend/src/components/ui/input.tsx` and `.kdc-input` / `.kdc-input-underline` utilities in `kdcsurveyadd/app/globals.css`.
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
See `anatomy.svg`. Label · input shell · helper / error text. Label sits above the input; helper text below in `text.muted` `bodyS`.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
|
||||
| Token | Use | Background | Border |
|
||||
|-------|-----|------------|--------|
|
||||
| `input.default` | Standard field | `surface.background` | `border.default` (1px) |
|
||||
| `input.filled` | High-density forms | `surface.subtle` | none |
|
||||
| `input.underline` | Marketing / minimal forms | transparent | bottom 1px `border.default` |
|
||||
|
||||
## Sizes
|
||||
|
||||
| Size | Height | Padding |
|
||||
|------|--------|---------|
|
||||
| `sm` | 32px | `6px 10px` |
|
||||
| `md` (default) | 40px | `8px 12px` |
|
||||
| `lg` | 48px | `12px 16px` |
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
|
||||
- **Hover** — border `border.default` → `border.strong`, `120ms` transition.
|
||||
- **Focus-visible** — apply `elevation.focusRing`, border `brand.primary`.
|
||||
- **Disabled** — `opacity: 0.5`, `cursor: not-allowed`, background `surface.subtle`.
|
||||
- **Error** — border `semantic.danger`, helper text `semantic.danger`, focus ring `rgba(176,56,47,0.18)`.
|
||||
- **Read-only** — background `surface.subtle`, remove border, keep text colour.
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.inputs.*`.
|
||||
|
||||
`components.input.*`, `elevation.focusRing`, `colors.border.*`, `colors.semantic.danger`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- Every input has a programmatically associated `<label>` (visible or `sr-only` only when an adjacent icon supplies meaning).
|
||||
- Error messages reference the input via `aria-describedby` and use `role="alert"` on first appearance.
|
||||
- Placeholder text is decorative — never the only label.
|
||||
- Maintain 4.5:1 contrast on placeholder text (currently `text.muted` `#6B647A`).
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Show errors *under* the input, not as a tooltip.
|
||||
- ✅ Use `tabular-nums` for numeric-only inputs (`.tnum` utility).
|
||||
- ❌ Don't use placeholder text as a label.
|
||||
- ❌ Don't shrink the height below 32px — it breaks touch targets.
|
||||
|
||||
+28
-13
@@ -1,25 +1,40 @@
|
||||
# Modals
|
||||
|
||||
Specification for the **modals** primitive.
|
||||
Overlaid surfaces that interrupt the user — dialogs, confirmations, sheets, drawers. Built on `base-ui` / Radix primitives in the product family (`kdcvault/frontend/src/components/ui/dialog.tsx`, `sheet.tsx`).
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
See `anatomy.svg`. Backdrop · panel · header (title + close) · body · footer (actions, right-aligned).
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
| Token | Use | Width | Position |
|
||||
|-------|-----|-------|----------|
|
||||
| `modal.default` | Confirmations, forms (≤ 5 fields) | 480px | centered |
|
||||
| `modal.wide` | Multi-step wizards, content browsers | 720px | centered |
|
||||
| `modal.sheet` | Slide-in side panel (editing, filters) | 420px | right edge |
|
||||
| `modal.drawer` | Bottom sheet (mobile) | 100% | bottom edge |
|
||||
| `modal.fullscreen` | Immersive flows | 100% | full viewport |
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.modals.*`.
|
||||
## Tokens & layout
|
||||
|
||||
- Radius `rounded.xl` (16px); sheet variants use `rounded.lg` on inner edge only.
|
||||
- Background `surface.background`; backdrop `rgba(26,21,48,0.45)`.
|
||||
- Shadow `elevation.4` (`0 24px 80px -24px rgba(26,21,48,0.22)`).
|
||||
- Padding 24px; footer separated by `1px border.default` top edge.
|
||||
- Open animation: `kdc-fade-up` (360ms cubic-bezier, `translateY(6px)` → 0).
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- `role="dialog"` (or `alertdialog` for destructive confirmations) with `aria-labelledby` / `aria-describedby`.
|
||||
- Trap focus inside the modal; first focusable element receives focus on open.
|
||||
- Close on `Esc`; restore focus to the trigger element on close.
|
||||
- Backdrop click closes only non-destructive modals; confirmations require explicit action.
|
||||
- Disable body scroll while open.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Right-align primary action in the footer (`primary` or `accent`); secondary / cancel on the left.
|
||||
- ✅ Use a `sheet` for editing context that benefits from staying tethered to a list.
|
||||
- ❌ Don't stack modals — open a `sheet` from inside a modal instead.
|
||||
- ❌ Don't put more than one CTA in a single modal footer.
|
||||
|
||||
@@ -1,25 +1,50 @@
|
||||
# Navigation
|
||||
|
||||
Specification for the **navigation** primitive.
|
||||
App shell, sidebar, top bar, tabs, breadcrumbs. Reference: `kdcvault/frontend/src/components/ui/sidebar.tsx`, `kdcsurveyadd/components/sidebar-nav.tsx`, `k-d-c-workspace/dashboard/components/AppShell.tsx`.
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
App shell = top bar (60px, `surface.background`, hairline bottom border) + sidebar (240px collapsed → 72px icon-only, dark `brand.ink` on workspace; canvas-toned in product apps) + main outlet.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
| Pattern | Use |
|
||||
|---------|-----|
|
||||
| **Top bar + sidebar** | Default product layout (vault, void planner, surveyadd) |
|
||||
| **Top bar only** | Simple tools (mapper, tracker) |
|
||||
| **Sidebar collapsible** | `SidebarProvider` controls expanded / icon-only / off-canvas (mobile) |
|
||||
| **Command palette** | ⌘K trigger; `modal.default` shell over backdrop |
|
||||
| **Tabs** | In-page section switching — underline (`border.bottom-2 brand.primary` active) |
|
||||
| **Breadcrumbs** | Hierarchical path; `text.muted` non-active, `text.default` current, `›` separator in `text.disabled` |
|
||||
|
||||
## Sidebar items
|
||||
|
||||
- 40px row height, 12px horizontal padding, 12px gap between icon and label.
|
||||
- Icon 20×20px from the curated Phosphor set (see `assets/icons/`).
|
||||
- Active state: `brand.primarySoft` background, `brand.primary` text, 2px left border in `brand.primary`.
|
||||
- Hover: `surface.subtle` background.
|
||||
- Section labels: `caption` uppercase, `text.muted`, 24px row.
|
||||
|
||||
## Top bar
|
||||
|
||||
- 60px tall, sticky.
|
||||
- Left: brand mark · workspace switcher. Right: search · notifications · user menu.
|
||||
- `backdrop-filter: saturate(180%) blur(14px)` on scroll (translucent over content).
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.navigation.*`.
|
||||
|
||||
`components.button.*` (icon buttons), `colors.surface.*`, `colors.border.*`, `elevation.focusRing`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- Sidebar uses `<nav aria-label="primary">`; in-page tabs use `role="tablist"` / `role="tab"` / `role="tabpanel"`.
|
||||
- Current page link: `aria-current="page"`.
|
||||
- Skip-to-content link as the first focusable element, visible on focus.
|
||||
- Command palette: trap focus, restore on close, announce via `aria-live`.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Always show the active route — left border + tinted background, not colour-shift alone.
|
||||
- ✅ Collapse to icon-only below 1024px, off-canvas below 768px.
|
||||
- ❌ Don't nest more than two sidebar levels — flatten into separate sections.
|
||||
- ❌ Don't put primary actions in the sidebar — they belong in the top bar or page header.
|
||||
|
||||
+39
-13
@@ -1,25 +1,51 @@
|
||||
# Tables
|
||||
|
||||
Specification for the **tables** primitive.
|
||||
Dense data display. Default to flat (no zebra striping) with hairline row borders. Reference: `kdctracker/admin.html` (`.card` data tables), `kdcdocs/public/styles.css` (`.kdc-table-head`, `.kdc-row`).
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
See `anatomy.svg`. Header row · grouped header rows (optional) · data rows · footer summary (optional).
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
## Sizing
|
||||
|
||||
| Density | Row height | Cell padding |
|
||||
|---------|------------|--------------|
|
||||
| `compact` | 36px | `6px 12px` |
|
||||
| `default` | 44px | `10px 16px` |
|
||||
| `comfortable` | 56px | `14px 20px` |
|
||||
|
||||
Drive density with `[data-density="compact|default|comfortable"]` (per the kdcmapper convention).
|
||||
|
||||
## Styling
|
||||
|
||||
- Header: `text.muted` `caption` uppercase, 600 weight, `surface.subtle` background.
|
||||
- Cell text: `bodyM`, `text.default`. Numerics use `tabular-nums` (`.tnum` utility).
|
||||
- Row separator: 1px `border.subtle` bottom border. No vertical lines.
|
||||
- Hover row: `surface.subtle`, `120ms` transition.
|
||||
- Selected row: 2px `brand.primary` left border, `brand.primarySoft` background.
|
||||
- Sticky header sits below any toolbar; sticky first column gets a 4px `elevation.2` shadow on right edge when scrolled.
|
||||
|
||||
## Cell types
|
||||
|
||||
- **Status pill** → `badge.*` variants.
|
||||
- **Identifier / code** → `typography.mono` with subtle background `surface.subtle`.
|
||||
- **Numeric** → right-aligned, `tabular-nums`.
|
||||
- **Action cell** → trailing cell, ghost icon buttons; right-aligned, never wrap.
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.tables.*`.
|
||||
|
||||
`colors.surface.*`, `colors.border.*`, `typography.bodyM`/`caption`/`mono`, `components.badge.*`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- Use proper `<thead>` / `<tbody>` / `<th scope="col">` / `<th scope="row">`.
|
||||
- Sortable columns: button inside `<th>` with `aria-sort="ascending|descending|none"`.
|
||||
- Empty state: `role="status"`, single line in `text.muted`.
|
||||
- Long tables: pagination or `aria-rowcount` for virtualised lists.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Use `compact` density for ≥ 50 rows on screen at once.
|
||||
- ✅ Right-align numerics and currencies; left-align text and identifiers.
|
||||
- ❌ Don't zebra-stripe — it competes with hover and selection.
|
||||
- ❌ Don't wrap action buttons; truncate text instead.
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
# Tooltips
|
||||
|
||||
Specification for the **tooltips** primitive.
|
||||
Brief, hover/focus-triggered hints. Reference: `kdcvault/frontend/src/components/ui/tooltip.tsx` (base-ui Tooltip).
|
||||
|
||||
## Anatomy
|
||||
TODO — drop `anatomy.svg` next to this file.
|
||||
|
||||
## Variants
|
||||
- default
|
||||
- (list per-component variants)
|
||||
See `anatomy.svg`. Trigger · floating panel · optional arrow. Max-width 240px; wraps to 3 lines maximum.
|
||||
|
||||
## States
|
||||
default · hover · focus-visible · active/pressed · disabled · loading
|
||||
## Styling
|
||||
|
||||
- Background `brand.ink` (`#1A1530`), text `text.inverse`, `caption` typography.
|
||||
- Radius `rounded.sm` (4px), padding `6px 10px`.
|
||||
- Shadow `elevation.2`.
|
||||
- Arrow 6×6px, same fill as panel.
|
||||
- Offset 8px from trigger; flip to opposite side at viewport edge.
|
||||
|
||||
## Timing
|
||||
|
||||
- Open delay 400ms (skip if another tooltip is already open within 500ms).
|
||||
- Close delay 100ms (allow hover-into-tooltip for copy / link interactions).
|
||||
- Open/close transition: `motion.duration.fast` `motion.easing.standard`.
|
||||
|
||||
## Tokens used
|
||||
See `design.md` → `components.tooltips.*`.
|
||||
|
||||
`colors.brand.ink`, `colors.text.inverse`, `typography.caption`, `rounded.sm`, `elevation.2`.
|
||||
|
||||
## Accessibility
|
||||
- All interactive instances are keyboard-reachable.
|
||||
- Focus state has ≥ 3:1 contrast against its background.
|
||||
- Pair color with an icon or text to convey meaning.
|
||||
|
||||
- Trigger is keyboard-focusable; tooltip opens on focus as well as hover.
|
||||
- Use `aria-describedby` on the trigger pointing at the tooltip's `id`.
|
||||
- Never put interactive content inside a tooltip — use a popover instead.
|
||||
- Dismiss on `Esc` and on focus / hover leave.
|
||||
|
||||
## Do / Don't
|
||||
- ✅ Use the canonical token references.
|
||||
- ❌ Don't hardcode colors, sizes, or radii.
|
||||
|
||||
- ✅ Keep copy ≤ 80 chars; one sentence, no period.
|
||||
- ✅ Use to surface a *non-essential* hint (full label, keyboard shortcut, brief explanation).
|
||||
- ❌ Don't put a tooltip on a tappable mobile target — touch users never see it.
|
||||
- ❌ Don't put interactive elements (links, buttons) inside a tooltip — escalate to a `popover`.
|
||||
|
||||
@@ -1,34 +1,60 @@
|
||||
---
|
||||
version: beta
|
||||
version: 1.0.0
|
||||
name: Kode Design Consultants Ltd Design System
|
||||
description: The official design system for Kode Design Consultants — tokens, components, and guidelines for building consistent KDC product and brand experiences.
|
||||
description: The official design system for Kode Design Consultants — tokens, components, and guidelines for building consistent KDC product and brand experiences. Reconciled 2026-05-17 from production usage across kdcsurveyadd, kdcvault, kdc_void_planner and k-d-c-workspace.
|
||||
colors:
|
||||
brand.primary: "#6B35A7"
|
||||
brand.primaryHover: "#582A8D"
|
||||
brand.primaryHover: "#5C2D90"
|
||||
brand.primaryPressed: "#45216E"
|
||||
brand.ink: "#000000"
|
||||
brand.canvas: "#F5F4F0"
|
||||
brand.primaryLight: "#8456C2"
|
||||
brand.primarySoft: "#F1E9F8"
|
||||
brand.ink: "#1A1530"
|
||||
brand.canvas: "#F7F4ED"
|
||||
brand.canvasCool: "#F7F6FB"
|
||||
accent.gold: "#C9892B"
|
||||
accent.goldStrong: "#A86F1F"
|
||||
accent.goldSoft: "#F5E9D2"
|
||||
accent.mint: "#46C194"
|
||||
neutral.0: "#FFFFFF"
|
||||
neutral.50: "#F5F4F0"
|
||||
neutral.100: "#EDEFF3"
|
||||
neutral.200: "#D9DCE3"
|
||||
neutral.300: "#B8BDC9"
|
||||
neutral.500: "#6B7280"
|
||||
neutral.700: "#374151"
|
||||
neutral.900: "#111827"
|
||||
neutral.50: "#FBF8F1"
|
||||
neutral.100: "#F7F4ED"
|
||||
neutral.150: "#ECE9F3"
|
||||
neutral.200: "#E6E0D2"
|
||||
neutral.300: "#D4CCB8"
|
||||
neutral.400: "#A39DB0"
|
||||
neutral.500: "#8C869C"
|
||||
neutral.600: "#6B647A"
|
||||
neutral.700: "#4A4360"
|
||||
neutral.800: "#3A2D5A"
|
||||
neutral.900: "#1A1530"
|
||||
neutral.1000: "#000000"
|
||||
semantic.success: "#16A34A"
|
||||
semantic.warning: "#F59E0B"
|
||||
semantic.danger: "#DC2626"
|
||||
semantic.info: "#0EA5E9"
|
||||
semantic.success: "#2F7A5A"
|
||||
semantic.successSoft: "#E2F0E8"
|
||||
semantic.warning: "#C9892B"
|
||||
semantic.warningSoft: "#F5E9D2"
|
||||
semantic.danger: "#B0382F"
|
||||
semantic.dangerSoft: "#F7E4E1"
|
||||
semantic.info: "#5C2D90"
|
||||
surface.background: "{colors.neutral.0}"
|
||||
surface.canvas: "{colors.brand.canvas}"
|
||||
surface.subtle: "{colors.neutral.50}"
|
||||
surface.muted: "{colors.neutral.100}"
|
||||
text.default: "{colors.neutral.900}"
|
||||
text.muted: "{colors.neutral.500}"
|
||||
surface.muted: "{colors.brand.primarySoft}"
|
||||
text.default: "{colors.brand.ink}"
|
||||
text.soft: "{colors.neutral.700}"
|
||||
text.muted: "{colors.neutral.600}"
|
||||
text.disabled: "{colors.neutral.400}"
|
||||
text.inverse: "{colors.neutral.0}"
|
||||
text.brand: "{colors.brand.primary}"
|
||||
border.default: "{colors.neutral.200}"
|
||||
border.strong: "{colors.neutral.300}"
|
||||
border.subtle: "{colors.neutral.150}"
|
||||
elevation:
|
||||
"0": "none"
|
||||
"1": "0 1px 2px rgba(26,21,48,0.04)"
|
||||
"2": "0 4px 12px -4px rgba(26,21,48,0.08)"
|
||||
"3": "0 16px 40px -16px rgba(26,21,48,0.18)"
|
||||
"4": "0 24px 80px -24px rgba(26,21,48,0.22)"
|
||||
focusRing: "0 0 0 3px rgba(107,53,167,0.18)"
|
||||
typography:
|
||||
display:
|
||||
fontFamily: "Opificio"
|
||||
@@ -117,53 +143,90 @@ components:
|
||||
textColor: "{colors.text.inverse}"
|
||||
typography: "{typography.bodyM}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "12px 20px"
|
||||
padding: "10px 16px"
|
||||
height: "40px"
|
||||
shadow: "{elevation.1}"
|
||||
button.primaryHover:
|
||||
backgroundColor: "{colors.brand.primaryHover}"
|
||||
textColor: "{colors.text.inverse}"
|
||||
button.primaryPressed:
|
||||
backgroundColor: "{colors.brand.primaryPressed}"
|
||||
textColor: "{colors.text.inverse}"
|
||||
button.accent:
|
||||
backgroundColor: "{colors.accent.gold}"
|
||||
textColor: "{colors.text.inverse}"
|
||||
typography: "{typography.bodyM}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "10px 16px"
|
||||
height: "40px"
|
||||
description: "Submit / emphasis button. Used sparingly — one per surface."
|
||||
button.secondary:
|
||||
backgroundColor: "{colors.surface.subtle}"
|
||||
textColor: "{colors.text.default}"
|
||||
borderColor: "{colors.border.default}"
|
||||
typography: "{typography.bodyM}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "12px 20px"
|
||||
padding: "10px 16px"
|
||||
height: "40px"
|
||||
button.ghost:
|
||||
backgroundColor: "transparent"
|
||||
textColor: "{colors.brand.primary}"
|
||||
typography: "{typography.bodyM}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "12px 20px"
|
||||
padding: "10px 16px"
|
||||
height: "40px"
|
||||
button.outline:
|
||||
backgroundColor: "transparent"
|
||||
textColor: "{colors.text.default}"
|
||||
borderColor: "{colors.border.strong}"
|
||||
typography: "{typography.bodyM}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "10px 16px"
|
||||
height: "40px"
|
||||
input.default:
|
||||
backgroundColor: "{colors.surface.background}"
|
||||
textColor: "{colors.text.default}"
|
||||
borderColor: "{colors.border.default}"
|
||||
typography: "{typography.bodyM}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "10px 12px"
|
||||
padding: "8px 12px"
|
||||
height: "40px"
|
||||
focusRing: "{elevation.focusRing}"
|
||||
card.default:
|
||||
backgroundColor: "{colors.surface.background}"
|
||||
textColor: "{colors.text.default}"
|
||||
borderColor: "{colors.border.default}"
|
||||
rounded: "{rounded.lg}"
|
||||
padding: "24px"
|
||||
shadow: "{elevation.1}"
|
||||
card.elevated:
|
||||
backgroundColor: "{colors.surface.background}"
|
||||
rounded: "{rounded.xl}"
|
||||
padding: "28px"
|
||||
shadow: "{elevation.3}"
|
||||
badge.default:
|
||||
backgroundColor: "{colors.surface.muted}"
|
||||
textColor: "{colors.text.default}"
|
||||
backgroundColor: "{colors.brand.primarySoft}"
|
||||
textColor: "{colors.brand.primaryHover}"
|
||||
typography: "{typography.caption}"
|
||||
rounded: "{rounded.full}"
|
||||
padding: "2px 8px"
|
||||
height: "20px"
|
||||
padding: "2px 10px"
|
||||
height: "22px"
|
||||
badge.success:
|
||||
backgroundColor: "{colors.semantic.successSoft}"
|
||||
textColor: "{colors.semantic.success}"
|
||||
badge.warning:
|
||||
backgroundColor: "{colors.semantic.warningSoft}"
|
||||
textColor: "{colors.semantic.warning}"
|
||||
badge.danger:
|
||||
backgroundColor: "{colors.semantic.dangerSoft}"
|
||||
textColor: "{colors.semantic.danger}"
|
||||
modal.default:
|
||||
backgroundColor: "{colors.surface.background}"
|
||||
textColor: "{colors.text.default}"
|
||||
rounded: "{rounded.xl}"
|
||||
padding: "24px"
|
||||
width: "480px"
|
||||
shadow: "{elevation.4}"
|
||||
---
|
||||
|
||||
## Overview
|
||||
@@ -180,13 +243,14 @@ KDS targets WCAG 2.2 AA at minimum across all components and themes.
|
||||
|
||||
## Colors
|
||||
|
||||
Color is organized in three layers:
|
||||
Color is organized in four layers:
|
||||
|
||||
1. **Brand** — the four-color brand palette: `brand.primary` (`#6B35A7` purple), `brand.ink` (`#000000`), `neutral.0` (`#FFFFFF`), and `brand.canvas` (`#F5F4F0` off-white).
|
||||
2. **Neutral** — a 12-step gray ramp powering surfaces, text, and borders. `neutral.50` is set to `brand.canvas` so the off-white reads as the natural page surface.
|
||||
3. **Semantic** — meaning-bearing colors for status: success, warning, danger, info. These sit alongside the brand palette but are not part of brand identity.
|
||||
1. **Brand** — Kode purple (`brand.primary` `#6B35A7`) plus its hover / pressed / light / soft variants, anchored by a purple-tinted ink (`brand.ink` `#1A1530` — never pure black) and the warm off-white canvas (`brand.canvas` `#F7F4ED`).
|
||||
2. **Accent** — `accent.gold` (`#C9892B`) is the product emphasis colour for submit / commit actions; `accent.mint` (`#46C194`) is reserved for marketing surfaces (workspace dashboard, decks). Use one accent per surface — never both.
|
||||
3. **Neutral** — a purple-tinted 13-step ramp (`neutral.0` → `neutral.1000`). The ramp deliberately walks toward the brand ink rather than a cold grey, so muted text and borders stay on-brand. `neutral.50` is a warm inset surface, `neutral.100` aliases `brand.canvas`, `neutral.900` aliases `brand.ink`.
|
||||
4. **Semantic** — `success` / `warning` / `danger` / `info` plus matching `*Soft` background tints, tuned to sit calmly on the warm canvas (e.g. `success` is `#2F7A5A`, not the louder `#16A34A`).
|
||||
|
||||
Always reference *role* tokens (`text.default`, `surface.subtle`, `border.default`) in product code rather than raw palette values, so themes can swap without churn.
|
||||
Always reference *role* tokens (`text.default`, `surface.canvas`, `border.default`) in product code — never raw palette values — so themes can swap without churn.
|
||||
|
||||
Contrast minimums:
|
||||
|
||||
@@ -238,12 +302,13 @@ Elevation is conveyed through a small set of layered shadows plus subtle backgro
|
||||
| Level | Use | Shadow |
|
||||
|-------|-----|--------|
|
||||
| 0 | flush surfaces, page background | none |
|
||||
| 1 | cards, list rows | `0 1px 2px rgba(17,24,39,0.06)` |
|
||||
| 2 | dropdowns, popovers | `0 4px 12px rgba(17,24,39,0.10)` |
|
||||
| 3 | modals, dialogs | `0 16px 32px rgba(17,24,39,0.16)` |
|
||||
| 4 | toasts, command bars | `0 24px 48px rgba(17,24,39,0.20)` |
|
||||
| 1 | cards, list rows, buttons | `0 1px 2px rgba(26,21,48,0.04)` |
|
||||
| 2 | dropdowns, popovers | `0 4px 12px -4px rgba(26,21,48,0.08)` |
|
||||
| 3 | modals, dialogs | `0 16px 40px -16px rgba(26,21,48,0.18)` |
|
||||
| 4 | toasts, command bars | `0 24px 80px -24px rgba(26,21,48,0.22)` |
|
||||
| focusRing | any focusable element | `0 0 0 3px rgba(107,53,167,0.18)` |
|
||||
|
||||
In dark theme, shadows are de-emphasized and elevation is communicated primarily through lighter surface tints.
|
||||
Shadows are tinted with the brand ink (`rgb(26 21 48)`) — not cold slate — so they read warm against the canvas. In dark theme, shadows are de-emphasized and elevation is communicated primarily through lighter surface tints.
|
||||
|
||||
## Shapes
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Design system consolidation — 2026-05-17
|
||||
|
||||
Output of the cross-repo audit that produced **v1.0.0**. Read this first if you're wondering "why does this token look the way it does."
|
||||
|
||||
## Scope
|
||||
|
||||
Scanned 8 repos under `/root/`:
|
||||
|
||||
`kdcsurveyadd` · `kdcvault` · `kdc_void_planner` · `k-d-c-workspace` · `kdcdiary` · `kdctracker` · `kdcdocs` · `kdcmapper`
|
||||
|
||||
For each: every distinct colour, font, radius, shadow, and reusable component was extracted via a parallel `Explore` agent. Findings are summarised below; the raw notes are in agent transcripts under `.claude/jobs/`.
|
||||
|
||||
## Three families, one canonical
|
||||
|
||||
| Family | Repos | Primary | Ink | Canvas | Font | Stack |
|
||||
|--------|-------|---------|-----|--------|------|-------|
|
||||
| **Refined product** *(canonical)* | `kdcsurveyadd`, `kdcvault`, `kdc_void_planner` | `#6B35A7` | `#1A1530` | `#F7F4ED` (warm) | Manrope | Tailwind v4 + shadcn/ui + base-ui |
|
||||
| **Marketing dashboard** | `k-d-c-workspace` (dashboard) | `#6B35A7` | `#1a1530` | `#f7f6fb` (cool) | Manrope | Tailwind v4 + custom components |
|
||||
| **Legacy / exploratory** *(drift)* | `kdcdiary`, `kdctracker`, `kdcdocs`, `kdcmapper` | varies | varies | varies | Geist or Inter+Outfit | varies |
|
||||
|
||||
The product family agrees almost line-for-line on tokens — including the bespoke gold accent `#C9892B`, the focus ring `0 0 0 3px rgba(107,53,167,0.18)`, the ink-tinted shadow stack, and a 4-step radius scale (`4 / 8 / 12 / 16`). v1.0.0 promotes that consensus into the canonical spec.
|
||||
|
||||
The dashboard agrees on brand fundamentals (purple, Manrope, ink) but uses a **cooler canvas** (`#f7f6fb`) and a **mint accent** (`#46C194`) instead of gold. Both are now first-class tokens: `brand.canvasCool` and `accent.mint`. Marketing surfaces use those; product surfaces use the warm canvas + gold.
|
||||
|
||||
## Drift inventory
|
||||
|
||||
| Repo | Primary used | Font used | Status |
|
||||
|------|--------------|-----------|--------|
|
||||
| `kdcdiary` | `#6B4EFF` | Geist + JetBrains Mono | drift — different purple, different typeface |
|
||||
| `kdctracker` | `#6B4EFF` | Geist + JetBrains Mono | drift — same as diary, looks like a shared template |
|
||||
| `kdcdocs` | `#A37CD9` | Geist + Geist Mono | drift — dark-themed Astro site, lifted purple |
|
||||
| `kdcmapper` | `#5567E8` (indigo) | Inter + Outfit + JetBrains Mono | drift — indigo palette, no Manrope, glassmorphism style |
|
||||
|
||||
**Not in scope for v1.0.0.** A follow-up project should migrate these to canonical — order of cheapest first:
|
||||
|
||||
1. **kdctracker** — single-file HTML, ~1 day to swap CSS vars.
|
||||
2. **kdcdiary** — React app with inline styles, ~2 days.
|
||||
3. **kdcdocs** — needs a deliberate "light vs dark canonical" decision before migration. Either we adopt a dark theme as canonical for marketing/docs, or we re-skin it onto the light product theme.
|
||||
4. **kdcmapper** — biggest lift, fully bespoke Tailwind theme. Plan a dedicated re-skin sprint.
|
||||
|
||||
## What changed in v1.0.0
|
||||
|
||||
See `CHANGELOG.md` for the full diff. Highlights:
|
||||
|
||||
- Brand ink moved from pure `#000000` to `#1A1530` — every product surface already uses the purple-tinted ink, the spec was wrong.
|
||||
- Canvas warmed to `#F7F4ED`.
|
||||
- Neutral ramp rebuilt around the purple ink so muted text and borders stay on-brand.
|
||||
- Semantic palette retuned for the warm canvas (`success` `#2F7A5A`, etc.) and each ships a `*Soft` background tint.
|
||||
- Shadow stack retinted with brand-ink rgb.
|
||||
- Added `accent.gold` and `accent.mint` as first-class tokens.
|
||||
- Filled in every previously-stub README in `components/` and `foundations/` with real specs.
|
||||
- Real `themes/light`, `themes/dark`, `themes/high-contrast` JSON.
|
||||
|
||||
## What's still TODO
|
||||
|
||||
Carrying these forward to v1.1 (not blockers):
|
||||
|
||||
- **Wordmark second-line** — design.md still encodes the secondary lockup at `fontWeight: 200`. The workspace brand guide lists Manrope weights starting at 300; either re-confirm 200 is licensed or bump to 300.
|
||||
- **Opificio cuts** — only Bold is in `brand/logos-source/`. If Rounded or Regular are licensed, add them with the same naming convention.
|
||||
- **Workspace brand guide** — `@Context/Assets/Brand/colour-palette.md` still has empty `_Name_` cells. The values in this design system can populate them; do it once and then point the brand guide at this repo as canonical.
|
||||
- **Component anatomy SVGs** — every component README references `anatomy.svg`. The files exist but are placeholder geometry; commission real ones.
|
||||
- **Token build pipeline** — `scripts/build-tokens.js` still produces a tokens dump only. A v1.1 should emit Tailwind v4 `@theme` blocks, CSS variables, and Style Dictionary JSON so apps can consume directly.
|
||||
- **Migration runbooks** — one per drift app (`migrate-kdcdiary.md` etc.), with a token-mapping table so the work is grab-and-go.
|
||||
|
||||
## How to use the new spec
|
||||
|
||||
1. **In a new Tailwind v4 app**, copy the `@theme` block from `kdcvault/frontend/src/index.css` lines 5–62 — that's the canonical reference implementation.
|
||||
2. **Import** `@fontsource-variable/manrope` and reference `var(--font-manrope)`.
|
||||
3. **Always use role tokens** (`text.default`, `surface.canvas`) in component code, never raw palette values.
|
||||
4. **For a new shadcn component**, `npx shadcn@latest add <component>` then re-style with the existing `.kdc-*` utility classes from `kdcsurveyadd/app/globals.css`.
|
||||
|
||||
Questions / drift you spot: open an issue or PR against this repo.
|
||||
@@ -1,8 +1,30 @@
|
||||
# Foundations · Breakpoints
|
||||
|
||||
Reference documentation for the **breakpoints** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
Five mobile-first breakpoints, aligned to Tailwind defaults so utility classes work without remapping.
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
| Token | Min width | Use |
|
||||
|-------|-----------|-----|
|
||||
| `sm` | 640px | Larger phone / small tablet |
|
||||
| `md` | 768px | Tablet portrait |
|
||||
| `lg` | 1024px | Tablet landscape / small laptop |
|
||||
| `xl` | 1280px | Standard desktop |
|
||||
| `2xl` | 1536px | Wide desktop |
|
||||
|
||||
## App shell behaviour
|
||||
|
||||
| Width | Layout |
|
||||
|-------|--------|
|
||||
| < 768px (`md`) | Sidebar off-canvas (drawer). Top bar collapses to brand + menu icon. |
|
||||
| 768–1023px (`md`–`lg`) | Sidebar icon-only (72px). |
|
||||
| ≥ 1024px (`lg`) | Sidebar expanded (240px). |
|
||||
| ≥ 1280px (`xl`) | Optional right-rail / detail column appears. |
|
||||
|
||||
## Container
|
||||
|
||||
Max content width is 1200px, centered, with side padding of 16px (mobile) → 24px (tablet) → 32px (desktop).
|
||||
|
||||
## Rules
|
||||
|
||||
- ✅ Always mobile-first. Stack to single column below `md`; introduce side-by-side at `md`+.
|
||||
- ✅ Test the 320px width — that's the floor.
|
||||
- ❌ Don't use device-specific breakpoints (iPad, iPhone). Design for content reflow, not specific hardware.
|
||||
|
||||
@@ -1,8 +1,35 @@
|
||||
# Foundations · Colors
|
||||
|
||||
Reference documentation for the **colors** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
Canonical values live in [`../../design.md`](../../design.md) and [`../../tokens/colors.json`](../../tokens/colors.json). This page is the human-facing rationale.
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
## The system in one paragraph
|
||||
|
||||
KDC's palette is built around **Kode purple** `#6B35A7`, a **purple-tinted ink** `#1A1530` (never pure black), and a **warm off-white canvas** `#F7F4ED`. Together they give surfaces a craft / editorial feel rather than the cold slate look of generic SaaS. A muted **gold accent** `#C9892B` is reserved for emphasis CTAs in product surfaces; a **mint accent** `#46C194` is reserved for marketing surfaces. Both accents are deliberately scarce — one per surface.
|
||||
|
||||
## Layers
|
||||
|
||||
1. **Brand** — `brand.primary` and its four variants (`primaryHover`, `primaryPressed`, `primaryLight`, `primarySoft`), `brand.ink`, `brand.canvas`.
|
||||
2. **Accent** — `accent.gold` (product emphasis) and `accent.mint` (marketing).
|
||||
3. **Neutral** — a 13-step ink-tinted ramp (`neutral.0` → `neutral.1000`). Aliases: `100` ≡ `canvas`, `900` ≡ `ink`.
|
||||
4. **Semantic** — `success` `#2F7A5A`, `warning` `#C9892B`, `danger` `#B0382F`, `info` `#5C2D90`. Each has a `*Soft` tint for backgrounds.
|
||||
5. **Role tokens** — always consume `text.default`, `surface.canvas`, `border.default`, etc. in product code. Never hardcode the palette.
|
||||
|
||||
## Choosing a colour
|
||||
|
||||
- **Default text** → `text.default` (`brand.ink`). For supporting copy, `text.soft`; for meta, `text.muted`.
|
||||
- **Page surface** → `surface.canvas` (`brand.canvas`) for product UIs, `surface.background` (`#FFFFFF`) for content cards.
|
||||
- **Primary CTA** → `brand.primary` + hover `brand.primaryHover`.
|
||||
- **Emphasis CTA / commit** → `accent.gold`. Only one per surface, and only when there's a meaningful one-way action.
|
||||
- **Status pill / badge** → `semantic.{success|warning|danger}Soft` background + matching semantic text.
|
||||
|
||||
## Contrast (WCAG 2.2 AA minimums)
|
||||
|
||||
- Body text on background ≥ 4.5:1.
|
||||
- Large text (≥ 24px, or ≥ 19px bold) and UI icons ≥ 3:1.
|
||||
- `text.muted` `#6B647A` on `surface.background` `#FFFFFF` → 5.4:1 ✓
|
||||
- `brand.primary` `#6B35A7` on `surface.background` → 6.9:1 ✓
|
||||
- `accent.gold` `#C9892B` on `surface.background` → 3.0:1 — meets large-text only; pair with text labels for small UI.
|
||||
|
||||
## Drift to be aware of
|
||||
|
||||
The legacy apps (kdcdiary, kdctracker, kdcdocs, kdcmapper) currently use different purples (#6B4EFF, #A37CD9, #5567E8) and fonts (Geist, Inter+Outfit). Treat anything not in this swatch sheet as drift; new work follows the canonical palette.
|
||||
|
||||
@@ -4,24 +4,60 @@
|
||||
|
||||
| Token | Hex | Use |
|
||||
|-------|-----|-----|
|
||||
| `brand.primary` | `#6B35A7` | Kode purple — primary brand color, links, primary actions. |
|
||||
| `brand.ink` | `#000000` | Wordmark, high-emphasis text. |
|
||||
| `brand.primary` | `#6B35A7` | Kode purple — primary brand colour, links, primary actions. |
|
||||
| `brand.primaryHover` | `#5C2D90` | Hover state on `brand.primary` surfaces. |
|
||||
| `brand.primaryPressed`| `#45216E` | Pressed / deepest variant. |
|
||||
| `brand.primaryLight` | `#8456C2` | Gradient end, dark-mode primary. |
|
||||
| `brand.primarySoft` | `#F1E9F8` | Tinted surface for chips, callouts, focus halos. |
|
||||
| `brand.ink` | `#1A1530` | Brand ink — high-emphasis text. **Not** pure black. |
|
||||
| `brand.canvas` | `#F7F4ED` | Warm off-white — default page surface. |
|
||||
| `brand.canvasCool` | `#F7F6FB` | Cool off-white — marketing dashboard only. |
|
||||
| `neutral.0` | `#FFFFFF` | Pure white — cards, modals, top-bar. |
|
||||
| `brand.canvas` | `#F5F4F0` | Brand off-white — default page background. |
|
||||
|
||||
## Primary state ramp
|
||||
## Accent
|
||||
|
||||
| Token | Hex |
|
||||
|-------|-----|
|
||||
| `brand.primary` | `#6B35A7` |
|
||||
| `brand.primaryHover` | `#582A8D` |
|
||||
| `brand.primaryPressed` | `#45216E` |
|
||||
| Token | Hex | Use |
|
||||
|-------|-----|-----|
|
||||
| `accent.gold` | `#C9892B` | Product emphasis CTA (one per surface). |
|
||||
| `accent.goldStrong` | `#A86F1F` | Pressed / dark text on gold-soft. |
|
||||
| `accent.goldSoft` | `#F5E9D2` | Background for accent chips and warning pills. |
|
||||
| `accent.mint` | `#46C194` | Marketing accent — workspace dashboard only. |
|
||||
|
||||
## Neutral ramp (purple-tinted)
|
||||
|
||||
| Token | Hex | Role |
|
||||
|-------|-----|------|
|
||||
| `neutral.0` | `#FFFFFF` | Pure surface |
|
||||
| `neutral.50` | `#FBF8F1` | Inset / secondary surface |
|
||||
| `neutral.100` | `#F7F4ED` | = `brand.canvas` |
|
||||
| `neutral.150` | `#ECE9F3` | Cool subtle line |
|
||||
| `neutral.200` | `#E6E0D2` | Hairline border |
|
||||
| `neutral.300` | `#D4CCB8` | Strong border |
|
||||
| `neutral.400` | `#A39DB0` | Disabled / placeholder |
|
||||
| `neutral.500` | `#8C869C` | Muted meta text |
|
||||
| `neutral.600` | `#6B647A` | Mute text |
|
||||
| `neutral.700` | `#4A4360` | Soft ink |
|
||||
| `neutral.800` | `#3A2D5A` | Secondary ink |
|
||||
| `neutral.900` | `#1A1530` | = `brand.ink` |
|
||||
| `neutral.1000` | `#000000` | Pure black (sparingly) |
|
||||
|
||||
## Semantic
|
||||
|
||||
| Token | Hex | Soft tint |
|
||||
|-------|-----|-----------|
|
||||
| `semantic.success` | `#2F7A5A` | `#E2F0E8` |
|
||||
| `semantic.warning` | `#C9892B` | `#F5E9D2` |
|
||||
| `semantic.danger` | `#B0382F` | `#F7E4E1` |
|
||||
| `semantic.info` | `#5C2D90` | `#F1E9F8` |
|
||||
|
||||
## Brand-scale (Tailwind compatibility)
|
||||
|
||||
| Token | Hex |
|
||||
|-------|-----|
|
||||
| `semantic.success` | `#16A34A` |
|
||||
| `semantic.warning` | `#F59E0B` |
|
||||
| `semantic.danger` | `#DC2626` |
|
||||
| `semantic.info` | `#0EA5E9` |
|
||||
| `scale.brand-50` | `#F3EDFB` |
|
||||
| `scale.brand-100` | `#E6DAF7` |
|
||||
| `scale.brand-200` | `#F1E9F8` |
|
||||
| `scale.brand-500` | `#6B35A7` (= `brand.primary`) |
|
||||
| `scale.brand-600` | `#5D2C93` |
|
||||
| `scale.brand-700` | `#52287F` |
|
||||
| `scale.brand-800` | `#45216E` |
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
# Foundations · Elevation
|
||||
|
||||
Reference documentation for the **elevation** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
Five-step shadow scale tinted with the brand ink so shadows read **warm** against the canvas — not cold slate.
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
## Scale
|
||||
|
||||
| Level | Shadow | Use |
|
||||
|-------|--------|-----|
|
||||
| `0` | none | Flush surfaces, page background |
|
||||
| `1` | `0 1px 2px rgba(26,21,48,0.04)` | Cards, list rows, buttons |
|
||||
| `2` | `0 4px 12px -4px rgba(26,21,48,0.08)` | Dropdowns, popovers |
|
||||
| `3` | `0 16px 40px -16px rgba(26,21,48,0.18)` | Modals, dialogs |
|
||||
| `4` | `0 24px 80px -24px rgba(26,21,48,0.22)` | Toasts, command palette |
|
||||
| `focusRing` | `0 0 0 3px rgba(107,53,167,0.18)` | Any focusable element |
|
||||
|
||||
`rgb(26 21 48)` is `brand.ink`; `rgb(107 53 167)` is `brand.primary`.
|
||||
|
||||
## Rules
|
||||
|
||||
- ✅ Treat the scale as discrete. Hover may bump elevation one step, never two.
|
||||
- ✅ Use `focusRing` consistently — never invent a per-component focus style.
|
||||
- ❌ Don't use elevation for decoration. Reserve it for hierarchy and affordance.
|
||||
- ❌ Don't use grey / black shadows; they look dirty on the warm canvas.
|
||||
|
||||
## Dark theme
|
||||
|
||||
In dark theme, elevation is communicated primarily through **lighter surface tints** rather than darker shadows. Shadows still exist but with reduced alpha (0.3–0.4 of brand.ink). See `themes/dark/theme.json`.
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
# Foundations · Grid
|
||||
|
||||
Reference documentation for the **grid** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
12-column responsive grid, with gutters that scale by breakpoint.
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
| Breakpoint | Columns | Gutter | Margin |
|
||||
|------------|---------|--------|--------|
|
||||
| < `md` (mobile) | 4 | 16px | 16px |
|
||||
| `md` (tablet) | 8 | 24px | 24px |
|
||||
| ≥ `lg` (desktop) | 12 | 24px | 32px |
|
||||
|
||||
Max content width: **1200px**, centered.
|
||||
|
||||
## Composition
|
||||
|
||||
- **Cards** snap to multiples of 3 columns (4-up / 3-up / 2-up).
|
||||
- **Form fields** snap to multiples of 4 (full / half / third / quarter).
|
||||
- **Sidebar layouts** reserve 3 columns left (240px ≈ 3 cols at 1200px container) and 9 columns for content.
|
||||
|
||||
## Rules
|
||||
|
||||
- ✅ Use CSS Grid for page layout, Flexbox for component internals.
|
||||
- ✅ When in doubt, give content more room — KDS is generous with whitespace.
|
||||
- ❌ Don't fight the grid for visual interest. Asymmetry comes from content rhythm, not from breaking the column structure.
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
# Foundations · Motion
|
||||
|
||||
Reference documentation for the **motion** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
Motion is functional, not decorative — it provides feedback (state change), continuity (route / modal transitions), and confirmation (success / commit).
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
## Durations
|
||||
|
||||
| Token | ms | Use |
|
||||
|-------|-----|-----|
|
||||
| `instant` | 0 | Reset / disable |
|
||||
| `fast` | 120 | Hover, focus, control state |
|
||||
| `base` | 200 | Tooltips, dropdowns |
|
||||
| `slow` | 320 | Modals, sheets, route transitions |
|
||||
| `slower` | 480 | Hero animations, marketing |
|
||||
|
||||
## Easing
|
||||
|
||||
| Token | Curve | Use |
|
||||
|-------|-------|-----|
|
||||
| `standard` | `cubic-bezier(0.2, 0, 0, 1)` | Default — most state changes |
|
||||
| `enter` | `cubic-bezier(0, 0, 0.2, 1)` | Enter-only (decelerate to rest) |
|
||||
| `exit` | `cubic-bezier(0.4, 0, 1, 1)` | Exit-only (accelerate away) |
|
||||
| `emphasis` | `cubic-bezier(0.2, 0, 0, 1.2)` | Overshoot — success, commit, attention |
|
||||
|
||||
## Signature animation
|
||||
|
||||
`kdc-fade-up` — used on the product family for content reveal:
|
||||
|
||||
```css
|
||||
@keyframes kdc-fade-up {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.kdc-fade-up { animation: kdc-fade-up 360ms cubic-bezier(0.2, 0, 0, 1) both; }
|
||||
```
|
||||
|
||||
Stagger children at 40–60ms intervals.
|
||||
|
||||
## Rules
|
||||
|
||||
- ✅ Respect `prefers-reduced-motion: reduce` — collapse all transitions to ≤ 10ms.
|
||||
- ✅ Use `fast` for state, `base` for surfaces, `slow` for layout shifts.
|
||||
- ❌ Don't animate `width` / `height` — animate `transform` and `opacity`.
|
||||
- ❌ Don't bounce or spin without reason. Loading spinners are the only exception.
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
# Foundations · Radius
|
||||
|
||||
Reference documentation for the **radius** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
Corner radius scales with component density. Pick from the curated set — never hand-roll a value.
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
## Scale
|
||||
|
||||
| Token | px | Use |
|
||||
|-------|-----|-----|
|
||||
| `none` | 0 | Resets, full-bleed images |
|
||||
| `xs` | 2 | Chips, inline tags |
|
||||
| `sm` | 4 | Inputs, small controls, tooltips |
|
||||
| `md` | 8 | Buttons, default surfaces |
|
||||
| `lg` | 12 | Cards, sections |
|
||||
| `xl` | 16 | Modals, sheets, elevated cards |
|
||||
| `2xl` | 24 | Marketing surfaces, feature panels |
|
||||
| `full` | 9999 | Pills, avatars, badges, circular buttons |
|
||||
|
||||
## Rules
|
||||
|
||||
- ✅ Match radius to size: small controls get `sm`, big surfaces get `lg`/`xl`. A 600px modal at `rounded.sm` looks broken.
|
||||
- ✅ Nested radii — the inner radius should be `outer - padding` to keep the corner concentric. Most of the time `lg` outside + `md` inside reads right.
|
||||
- ❌ Don't mix more than two radii in a single composition.
|
||||
- ❌ Don't use `full` on anything taller than 32px unless it's intentionally circular.
|
||||
|
||||
## Per-platform notes
|
||||
|
||||
- iOS: Apple's continuous corners are not directly available in CSS — accept the small perceptual difference rather than approximating.
|
||||
- Tailwind v4: bind these to `--radius-{token}` in `@theme`, e.g. `--radius-md: 8px`. The product family does this in `globals.css`.
|
||||
|
||||
@@ -1,8 +1,39 @@
|
||||
# Foundations · Spacing
|
||||
|
||||
Reference documentation for the **spacing** foundation. The canonical token values
|
||||
live in [`../../design.md`](../../design.md) and the machine-readable export
|
||||
in [`../../tokens/`](../../tokens/).
|
||||
KDS is built on a **4px base unit**. All spacing, sizing, and layout dimensions are multiples of 4.
|
||||
|
||||
This page is for human-facing rationale: when to use which token, examples,
|
||||
and platform notes.
|
||||
## Scale
|
||||
|
||||
| Token | px | rem | Use |
|
||||
|-------|-----|-----|-----|
|
||||
| `0` | 0 | 0 | Reset |
|
||||
| `1` | 4 | 0.25 | Hairline / icon-to-text |
|
||||
| `2` | 8 | 0.5 | Tight stack |
|
||||
| `3` | 12 | 0.75 | Default field-to-label |
|
||||
| `4` | 16 | 1 | Default container padding (mobile) |
|
||||
| `5` | 20 | 1.25 | Card inner gap |
|
||||
| `6` | 24 | 1.5 | Default card padding, section gap |
|
||||
| `8` | 32 | 2 | Form section separator |
|
||||
| `10` | 40 | 2.5 | Page section separator (small) |
|
||||
| `12` | 48 | 3 | Page section separator (default) |
|
||||
| `16` | 64 | 4 | Hero block padding |
|
||||
| `20` | 80 | 5 | Marketing hero |
|
||||
| `24` | 96 | 6 | Marketing hero, very generous |
|
||||
|
||||
## Density
|
||||
|
||||
Product apps expose `[data-density="compact|default|cozy"]` on the `<html>` root (see kdcmapper). Each density rebinds two CSS variables:
|
||||
|
||||
| Density | `--gap` | `--pad` |
|
||||
|---------|---------|---------|
|
||||
| compact | 12px (`3`) | 14px |
|
||||
| default | 18px | 22px |
|
||||
| cozy | 18px | 22px |
|
||||
|
||||
Use density for table-heavy / wizard-heavy screens. Don't fork component padding per screen — change density instead.
|
||||
|
||||
## Rules
|
||||
|
||||
- ✅ Always pick from the scale. If the value isn't on the scale, you're probably making the wrong choice.
|
||||
- ✅ Pair gap and padding from the same step where possible (`gap-4` inside `p-4`).
|
||||
- ❌ Don't use 1px, 3px, 5px etc. as spacing — those are border / decoration only.
|
||||
|
||||
@@ -1,14 +1,54 @@
|
||||
# Foundations · Typography
|
||||
|
||||
KDS uses two brand typefaces:
|
||||
KDS uses two brand typefaces.
|
||||
|
||||
| Family | Use | Weights |
|
||||
|--------|-----|---------|
|
||||
| **Opificio** | Wordmark and display moments only. | Bold (700) — canonical. Rounded and Regular cuts available for editorial. |
|
||||
| **Manrope** | All UI typography — headings, body, labels, captions. | 200 ExtraLight, 300 Light, 400 Regular, 500 Medium, 600 SemiBold, 700 Bold, 800 ExtraBold |
|
||||
| **Manrope** (variable) | All UI typography — headings, body, labels, captions. | 200 ExtraLight, 300 Light, 400 Regular, 500 Medium, 600 SemiBold, 700 Bold, 800 ExtraBold |
|
||||
| **Roboto Mono** *(non-brand)* | Code, tabular figures, identifiers. | 400, 500 |
|
||||
|
||||
Roboto Mono is available as a non-brand fallback for code and tabular figures.
|
||||
Manrope is loaded as a variable font via `@fontsource-variable/manrope` (product apps) or `next/font/google` (Next.js apps). Use the **Variable** build wherever possible — it lets us interpolate weight without shipping multiple files.
|
||||
|
||||
The wordmark lockup pairs them: `kode` in Opificio Bold (72pt, 52pt spacing) above `design consultants` in Manrope ExtraLight (20pt, 76pt spacing).
|
||||
## Wordmark lockup
|
||||
|
||||
Canonical token values live in [`../../design.md`](../../design.md); machine-readable typography tokens are in [`../../tokens/typography.json`](../../tokens/typography.json).
|
||||
`kode` in Opificio Bold (72pt) above `design consultants` in Manrope ExtraLight (20pt, +4% letter-spacing).
|
||||
|
||||
## Type scale (UI)
|
||||
|
||||
| Token | Family | Size | Weight | Line height | Use |
|
||||
|-------|--------|------|--------|-------------|-----|
|
||||
| `display` | Opificio | 72px | 700 | 80px | Hero / wordmark only |
|
||||
| `wordmarkSecondary` | Manrope | 20px | 200 | 28px | Wordmark second line |
|
||||
| `headingXL` | Manrope | 32px | 700 | 40px | Page hero |
|
||||
| `headingL` | Manrope | 24px | 600 | 32px | Section heading |
|
||||
| `headingM` | Manrope | 20px | 600 | 28px | Subsection / card title |
|
||||
| `headingS` | Manrope | 16px | 600 | 24px | Group label |
|
||||
| `bodyL` | Manrope | 18px | 400 | 28px | Lead paragraph |
|
||||
| `bodyM` *(default)* | Manrope | 16px | 400 | 24px | Body copy |
|
||||
| `bodyS` | Manrope | 14px | 400 | 20px | Secondary / helper |
|
||||
| `caption` | Manrope | 12px | 500 | 16px | Labels, badges, meta |
|
||||
| `mono` | Roboto Mono | 14px | 400 | 20px | Code, identifiers |
|
||||
|
||||
## OpenType features
|
||||
|
||||
Product apps enable `font-feature-settings: "ss01", "cv11"` on Manrope. These swap the default `a` and adjust the `1` / `l` for clearer numeric reading on the warm canvas. Keep them on by default — they are part of the brand voice.
|
||||
|
||||
For numerics use `font-variant-numeric: tabular-nums` (utility class `.tnum`) so columns don't jitter.
|
||||
|
||||
## Display weight
|
||||
|
||||
`.font-display` class is `Manrope 800` with `letter-spacing: -0.02em`. Use for hero headlines where Opificio would be too much.
|
||||
|
||||
## Web font delivery
|
||||
|
||||
- Self-host Manrope from `/assets/fonts/manrope/` and preload **400** + **600** in the document head.
|
||||
- Opificio is licensed; self-host from `/assets/fonts/opificio/`. Don't expose source files publicly.
|
||||
- Fallback stack: `'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif`.
|
||||
|
||||
## Do / Don't
|
||||
|
||||
- ✅ Default body to `bodyM` with `line-height: 1.5` for readability.
|
||||
- ✅ Use Opificio for the wordmark and nowhere else.
|
||||
- ❌ Don't mix Manrope with Geist / Inter / Outfit. The legacy apps that do so are drift to be migrated.
|
||||
- ❌ Don't justify text. Don't underline anything that isn't a link.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kdc/design-system",
|
||||
"version": "0.1.0-alpha.0",
|
||||
"version": "1.0.0",
|
||||
"description": "KDC Design System — tokens, components, and brand assets.",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+20
-8
@@ -1,13 +1,25 @@
|
||||
{
|
||||
"name": "KDC Dark",
|
||||
"$description": "Dark theme — lifted purple primary on near-black ink surface. Source: kdcvault dark overrides.",
|
||||
"colors": {
|
||||
"surface.background": "#0B0D12",
|
||||
"surface.subtle": "#12151B",
|
||||
"surface.muted": "#1B1F27",
|
||||
"text.default": "#F7F8FA",
|
||||
"text.muted": "#B8BDC9",
|
||||
"text.inverse": "#111827",
|
||||
"border.default": "#2A2F3A",
|
||||
"border.strong": "#3A4150"
|
||||
"surface.background": "#0E0B1A",
|
||||
"surface.canvas": "#14102A",
|
||||
"surface.subtle": "#1C1838",
|
||||
"surface.muted": "#2A2040",
|
||||
"text.default": "#F1ECFA",
|
||||
"text.soft": "#C8C0DA",
|
||||
"text.muted": "#8A82A0",
|
||||
"text.disabled": "#5A526E",
|
||||
"text.inverse": "#1A1530",
|
||||
"text.brand": "#A37CD9",
|
||||
"border.default": "#2A2D47",
|
||||
"border.strong": "#3D3A55",
|
||||
"border.subtle": "#1F1C36",
|
||||
"accent.primary": "#8456C2",
|
||||
"accent.emphasis": "#D9A858",
|
||||
"status.success": "#5BB58A",
|
||||
"status.warning": "#D9A858",
|
||||
"status.danger": "#D9695E",
|
||||
"status.info": "#A37CD9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
{
|
||||
"name": "KDC High Contrast",
|
||||
"$description": "WCAG AAA-targeted theme. Pure black/white with brand purple preserved as the only chromatic accent.",
|
||||
"colors": {
|
||||
"surface.background": "#000000",
|
||||
"surface.subtle": "#000000",
|
||||
"surface.muted": "#0A0A0A",
|
||||
"surface.canvas": "#000000",
|
||||
"surface.subtle": "#0A0A0A",
|
||||
"surface.muted": "#141414",
|
||||
"text.default": "#FFFFFF",
|
||||
"text.soft": "#FFFFFF",
|
||||
"text.muted": "#EDEFF3",
|
||||
"text.disabled": "#B8BDC9",
|
||||
"text.inverse": "#000000",
|
||||
"text.brand": "#C7A6E8",
|
||||
"border.default": "#FFFFFF",
|
||||
"border.strong": "#FFFFFF"
|
||||
"border.strong": "#FFFFFF",
|
||||
"border.subtle": "#FFFFFF",
|
||||
"accent.primary": "#C7A6E8",
|
||||
"accent.emphasis": "#FFD27A",
|
||||
"status.success": "#7AE0A0",
|
||||
"status.warning": "#FFD27A",
|
||||
"status.danger": "#FF8A8A",
|
||||
"status.info": "#A6D8FF"
|
||||
}
|
||||
}
|
||||
|
||||
+18
-6
@@ -1,13 +1,25 @@
|
||||
{
|
||||
"name": "KDC Light",
|
||||
"$description": "Default product theme — warm canvas, purple-tinted ink. Source: kdcsurveyadd, kdcvault, kdc_void_planner.",
|
||||
"colors": {
|
||||
"surface.background": "#FFFFFF",
|
||||
"surface.subtle": "#F7F8FA",
|
||||
"surface.muted": "#EDEFF3",
|
||||
"text.default": "#111827",
|
||||
"text.muted": "#6B7280",
|
||||
"surface.canvas": "#F7F4ED",
|
||||
"surface.subtle": "#FBF8F1",
|
||||
"surface.muted": "#F1E9F8",
|
||||
"text.default": "#1A1530",
|
||||
"text.soft": "#3A2D5A",
|
||||
"text.muted": "#6B647A",
|
||||
"text.disabled": "#A39DB0",
|
||||
"text.inverse": "#FFFFFF",
|
||||
"border.default": "#D9DCE3",
|
||||
"border.strong": "#B8BDC9"
|
||||
"text.brand": "#6B35A7",
|
||||
"border.default": "#E6E0D2",
|
||||
"border.strong": "#D4CCB8",
|
||||
"border.subtle": "#ECE9F3",
|
||||
"accent.primary": "#6B35A7",
|
||||
"accent.emphasis": "#C9892B",
|
||||
"status.success": "#2F7A5A",
|
||||
"status.warning": "#C9892B",
|
||||
"status.danger": "#B0382F",
|
||||
"status.info": "#5C2D90"
|
||||
}
|
||||
}
|
||||
|
||||
+42
-16
@@ -1,29 +1,55 @@
|
||||
{
|
||||
"$schema": "https://design-tokens.github.io/community-group/format/",
|
||||
"$description": "Reconciled from production usage in kdcsurveyadd, kdcvault, kdc_void_planner (refined product family) and k-d-c-workspace dashboard (marketing surface). 2026-05-17.",
|
||||
"color": {
|
||||
"brand": {
|
||||
"primary": { "$value": "#6B35A7", "$type": "color", "$description": "Kode purple — primary brand color." },
|
||||
"primary-hover": { "$value": "#582A8D", "$type": "color" },
|
||||
"primary-pressed": { "$value": "#45216E", "$type": "color" },
|
||||
"ink": { "$value": "#000000", "$type": "color", "$description": "Brand black — wordmark and high-emphasis text." },
|
||||
"canvas": { "$value": "#F5F4F0", "$type": "color", "$description": "Brand off-white — default page surface." }
|
||||
"primary": { "$value": "#6B35A7", "$type": "color", "$description": "Kode purple — primary brand color. Confirmed across all on-brand surfaces." },
|
||||
"primary-hover": { "$value": "#5C2D90", "$type": "color", "$description": "Hover / pressed-light. (alias: primary-2)" },
|
||||
"primary-pressed": { "$value": "#45216E", "$type": "color", "$description": "Pressed / deep. Legacy alias for darkest variant." },
|
||||
"primary-light": { "$value": "#8456C2", "$type": "color", "$description": "Light variant for gradients and dark-mode primary. (alias: primary-3)" },
|
||||
"primary-soft": { "$value": "#F1E9F8", "$type": "color", "$description": "Tinted background for primary surfaces (chips, callouts, focus halos)." },
|
||||
"ink": { "$value": "#1A1530", "$type": "color", "$description": "Brand ink — primary text. Purple-tinted near-black, NOT pure #000. Confirmed across surveyadd / vault / void / workspace." },
|
||||
"canvas": { "$value": "#F7F4ED", "$type": "color", "$description": "Brand off-white — warm page surface for product UIs." },
|
||||
"canvas-cool": { "$value": "#F7F6FB", "$type": "color", "$description": "Cool off-white variant used on the marketing dashboard. Avoid mixing with `canvas` on the same surface." }
|
||||
},
|
||||
"accent": {
|
||||
"gold": { "$value": "#C9892B", "$type": "color", "$description": "Product accent — used for emphasis CTAs and submit buttons in surveyadd / vault / void." },
|
||||
"gold-strong": { "$value": "#A86F1F", "$type": "color" },
|
||||
"gold-soft": { "$value": "#F5E9D2", "$type": "color" },
|
||||
"mint": { "$value": "#46C194", "$type": "color", "$description": "Marketing accent — secondary brand colour used on the workspace dashboard." }
|
||||
},
|
||||
"neutral": {
|
||||
"0": { "$value": "#FFFFFF", "$type": "color" },
|
||||
"50": { "$value": "#F5F4F0", "$type": "color", "$description": "Aliased to brand.canvas." },
|
||||
"100": { "$value": "#EDEFF3", "$type": "color" },
|
||||
"200": { "$value": "#D9DCE3", "$type": "color" },
|
||||
"300": { "$value": "#B8BDC9", "$type": "color" },
|
||||
"500": { "$value": "#6B7280", "$type": "color" },
|
||||
"700": { "$value": "#374151", "$type": "color" },
|
||||
"900": { "$value": "#111827", "$type": "color" },
|
||||
"50": { "$value": "#FBF8F1", "$type": "color", "$description": "Inset / secondary surface (matches surface-2 in product family)." },
|
||||
"100": { "$value": "#F7F4ED", "$type": "color", "$description": "Aliased to brand.canvas." },
|
||||
"150": { "$value": "#ECE9F3", "$type": "color", "$description": "Cool subtle line (workspace)." },
|
||||
"200": { "$value": "#E6E0D2", "$type": "color", "$description": "Hairline border." },
|
||||
"300": { "$value": "#D4CCB8", "$type": "color", "$description": "Strong border." },
|
||||
"400": { "$value": "#A39DB0", "$type": "color", "$description": "Disabled / mute-2 text." },
|
||||
"500": { "$value": "#8C869C", "$type": "color", "$description": "Muted meta text." },
|
||||
"600": { "$value": "#6B647A", "$type": "color", "$description": "Mute text." },
|
||||
"700": { "$value": "#4A4360", "$type": "color", "$description": "Soft ink — supporting copy." },
|
||||
"800": { "$value": "#3A2D5A", "$type": "color", "$description": "Secondary ink." },
|
||||
"900": { "$value": "#1A1530", "$type": "color", "$description": "Aliased to brand.ink." },
|
||||
"1000": { "$value": "#000000", "$type": "color" }
|
||||
},
|
||||
"semantic": {
|
||||
"success": { "$value": "#16A34A", "$type": "color" },
|
||||
"warning": { "$value": "#F59E0B", "$type": "color" },
|
||||
"danger": { "$value": "#DC2626", "$type": "color" },
|
||||
"info": { "$value": "#0EA5E9", "$type": "color" }
|
||||
"success": { "$value": "#2F7A5A", "$type": "color", "$description": "Muted green tuned for the warm canvas." },
|
||||
"success-soft": { "$value": "#E2F0E8", "$type": "color" },
|
||||
"warning": { "$value": "#C9892B", "$type": "color", "$description": "Aliased to accent.gold." },
|
||||
"warning-soft": { "$value": "#F5E9D2", "$type": "color" },
|
||||
"danger": { "$value": "#B0382F", "$type": "color" },
|
||||
"danger-soft": { "$value": "#F7E4E1", "$type": "color" },
|
||||
"info": { "$value": "#5C2D90", "$type": "color", "$description": "Re-uses primary-hover for informational signals." }
|
||||
},
|
||||
"scale": {
|
||||
"brand-50": { "$value": "#F3EDFB", "$type": "color" },
|
||||
"brand-100": { "$value": "#E6DAF7", "$type": "color" },
|
||||
"brand-200": { "$value": "#F1E9F8", "$type": "color", "$description": "Aliased to brand.primary-soft." },
|
||||
"brand-500": { "$value": "#6B35A7", "$type": "color", "$description": "Aliased to brand.primary." },
|
||||
"brand-600": { "$value": "#5D2C93", "$type": "color" },
|
||||
"brand-700": { "$value": "#52287F", "$type": "color" },
|
||||
"brand-800": { "$value": "#45216E", "$type": "color" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"$description": "Ink-tinted shadow scale (rgb 26 21 48 = brand.ink). Matches the surveyadd / vault / void product family.",
|
||||
"elevation": {
|
||||
"0": { "shadow": "none" },
|
||||
"1": { "shadow": "0 1px 2px rgba(17,24,39,0.06)" },
|
||||
"2": { "shadow": "0 4px 12px rgba(17,24,39,0.10)" },
|
||||
"3": { "shadow": "0 16px 32px rgba(17,24,39,0.16)" },
|
||||
"4": { "shadow": "0 24px 48px rgba(17,24,39,0.20)" }
|
||||
"1": { "shadow": "0 1px 2px rgba(26,21,48,0.04)" },
|
||||
"2": { "shadow": "0 4px 12px -4px rgba(26,21,48,0.08)" },
|
||||
"3": { "shadow": "0 16px 40px -16px rgba(26,21,48,0.18)" },
|
||||
"4": { "shadow": "0 24px 80px -24px rgba(26,21,48,0.22)" },
|
||||
"focus-ring": { "shadow": "0 0 0 3px rgba(107,53,167,0.18)", "$description": "Primary focus halo. Use on any focusable element." }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user