@omni-design/components 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/ScanComponents.md +26 -0
- package/.claude/commands/ShareComponent.md +128 -0
- package/.cursorrules +21 -0
- package/CLAUDE.md +1046 -0
- package/README.md +70 -0
- package/dist/OmniDesignProvider.esm.js +2 -0
- package/dist/OmniDesignProvider.esm.js.map +1 -0
- package/dist/OmniDesignProvider.js +2 -0
- package/dist/OmniDesignProvider.js.map +1 -0
- package/dist/index.esm.js +42 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/types/OmniDesignProvider.d.ts +158 -0
- package/dist/types/components/Accordion/Accordion.d.ts +42 -0
- package/dist/types/components/Accordion/AccordionShowcase.d.ts +2 -0
- package/dist/types/components/Accordion/index.d.ts +2 -0
- package/dist/types/components/Alert/Alert.d.ts +14 -0
- package/dist/types/components/Alert/AlertShowcase.d.ts +2 -0
- package/dist/types/components/Alert/index.d.ts +2 -0
- package/dist/types/components/AppBar/AppBar.d.ts +43 -0
- package/dist/types/components/AppBar/AppBarShowcase.d.ts +2 -0
- package/dist/types/components/AppBar/index.d.ts +2 -0
- package/dist/types/components/Autocomplete/Autocomplete.d.ts +26 -0
- package/dist/types/components/Autocomplete/AutocompleteShowcase.d.ts +2 -0
- package/dist/types/components/Autocomplete/index.d.ts +2 -0
- package/dist/types/components/Avatar/Avatar.d.ts +29 -0
- package/dist/types/components/Avatar/AvatarShowcase.d.ts +2 -0
- package/dist/types/components/Avatar/defaultAvatar.d.ts +2 -0
- package/dist/types/components/Avatar/index.d.ts +3 -0
- package/dist/types/components/BackgroundPicker.d.ts +12 -0
- package/dist/types/components/Badge/Badge.d.ts +43 -0
- package/dist/types/components/Badge/BadgeShowcase.d.ts +1 -0
- package/dist/types/components/Badge/index.d.ts +2 -0
- package/dist/types/components/BevelText/BevelText.d.ts +15 -0
- package/dist/types/components/BevelText/BevelTextShowcase.d.ts +2 -0
- package/dist/types/components/BevelText/index.d.ts +2 -0
- package/dist/types/components/BottomNavigation/BottomNavigation.d.ts +15 -0
- package/dist/types/components/BottomNavigation/BottomNavigationShowcase.d.ts +2 -0
- package/dist/types/components/BottomNavigation/index.d.ts +2 -0
- package/dist/types/components/Box/Box.d.ts +36 -0
- package/dist/types/components/Box/BoxShowcase.d.ts +2 -0
- package/dist/types/components/Box/index.d.ts +2 -0
- package/dist/types/components/Breadcrumbs/Breadcrumbs.d.ts +22 -0
- package/dist/types/components/Breadcrumbs/BreadcrumbsShowcase.d.ts +2 -0
- package/dist/types/components/Breadcrumbs/index.d.ts +2 -0
- package/dist/types/components/Button/Button.d.ts +66 -0
- package/dist/types/components/Button/index.d.ts +2 -0
- package/dist/types/components/ButtonGroup/ButtonGroup.d.ts +41 -0
- package/dist/types/components/ButtonGroup/index.d.ts +2 -0
- package/dist/types/components/Card/Card.d.ts +54 -0
- package/dist/types/components/Card/CardShowcase.d.ts +2 -0
- package/dist/types/components/Card/index.d.ts +2 -0
- package/dist/types/components/Checkbox/Checkbox.d.ts +51 -0
- package/dist/types/components/Checkbox/index.d.ts +2 -0
- package/dist/types/components/Chip/Chip.d.ts +42 -0
- package/dist/types/components/Chip/ChipShowcase.d.ts +2 -0
- package/dist/types/components/Chip/index.d.ts +2 -0
- package/dist/types/components/CircularProgress/CircularProgress.d.ts +12 -0
- package/dist/types/components/CircularProgress/CircularProgressShowcase.d.ts +2 -0
- package/dist/types/components/CircularProgress/index.d.ts +2 -0
- package/dist/types/components/CodeBlock/CodeBlock.d.ts +35 -0
- package/dist/types/components/Colors/Colors.d.ts +7 -0
- package/dist/types/components/Colors/index.d.ts +1 -0
- package/dist/types/components/Container/Container.d.ts +70 -0
- package/dist/types/components/Container/index.d.ts +1 -0
- package/dist/types/components/Copyright/Copyright.d.ts +11 -0
- package/dist/types/components/Copyright/CopyrightShowcase.d.ts +2 -0
- package/dist/types/components/Copyright/index.d.ts +2 -0
- package/dist/types/components/CurvedText/CurvedText.d.ts +23 -0
- package/dist/types/components/CurvedText/CurvedTextShowcase.d.ts +2 -0
- package/dist/types/components/CurvedText/index.d.ts +2 -0
- package/dist/types/components/Dialog/Dialog.d.ts +82 -0
- package/dist/types/components/Dialog/DialogShowcase.d.ts +2 -0
- package/dist/types/components/Dialog/index.d.ts +2 -0
- package/dist/types/components/Divider/Divider.d.ts +22 -0
- package/dist/types/components/Divider/DividerShowcase.d.ts +2 -0
- package/dist/types/components/Divider/index.d.ts +2 -0
- package/dist/types/components/Drawer/Drawer.d.ts +30 -0
- package/dist/types/components/Drawer/DrawerShowcase.d.ts +2 -0
- package/dist/types/components/Drawer/index.d.ts +2 -0
- package/dist/types/components/DropZone/DropZone.d.ts +15 -0
- package/dist/types/components/DropZone/index.d.ts +2 -0
- package/dist/types/components/Fab/Fab.d.ts +17 -0
- package/dist/types/components/Fab/FabShowcase.d.ts +2 -0
- package/dist/types/components/Fab/index.d.ts +2 -0
- package/dist/types/components/Footer/Footer.d.ts +14 -0
- package/dist/types/components/Footer/FooterShowcase.d.ts +2 -0
- package/dist/types/components/Footer/index.d.ts +2 -0
- package/dist/types/components/Gradient/Gradient.d.ts +38 -0
- package/dist/types/components/Gradient/GradientShowcase.d.ts +2 -0
- package/dist/types/components/Gradient/gradientUtils.d.ts +185 -0
- package/dist/types/components/Gradient/index.d.ts +3 -0
- package/dist/types/components/Grid/Grid.d.ts +112 -0
- package/dist/types/components/Grid/index.d.ts +1 -0
- package/dist/types/components/Header/Header.d.ts +53 -0
- package/dist/types/components/Header/index.d.ts +1 -0
- package/dist/types/components/Icon/Icon.d.ts +19 -0
- package/dist/types/components/Icon/IconShowcase.d.ts +2 -0
- package/dist/types/components/Icon/index.d.ts +2 -0
- package/dist/types/components/IconBadge/IconBadge.d.ts +10 -0
- package/dist/types/components/IconBadge/IconBadgeShowcase.d.ts +2 -0
- package/dist/types/components/IconBadge/index.d.ts +2 -0
- package/dist/types/components/Input/Input.d.ts +47 -0
- package/dist/types/components/Input/index.d.ts +2 -0
- package/dist/types/components/LinearProgress/LinearProgress.d.ts +9 -0
- package/dist/types/components/LinearProgress/LinearProgressShowcase.d.ts +2 -0
- package/dist/types/components/LinearProgress/index.d.ts +2 -0
- package/dist/types/components/Link/Link.d.ts +16 -0
- package/dist/types/components/Link/LinkShowcase.d.ts +2 -0
- package/dist/types/components/Link/index.d.ts +2 -0
- package/dist/types/components/List/List.d.ts +55 -0
- package/dist/types/components/List/ListShowcase.d.ts +2 -0
- package/dist/types/components/List/index.d.ts +2 -0
- package/dist/types/components/MainLayout/MainLayout.d.ts +28 -0
- package/dist/types/components/MainLayout/index.d.ts +1 -0
- package/dist/types/components/Menu/Menu.d.ts +37 -0
- package/dist/types/components/Menu/MenuShowcase.d.ts +2 -0
- package/dist/types/components/Menu/index.d.ts +2 -0
- package/dist/types/components/Modal/Modal.d.ts +18 -0
- package/dist/types/components/Modal/ModalShowcase.d.ts +2 -0
- package/dist/types/components/Modal/index.d.ts +2 -0
- package/dist/types/components/NumberField/NumberField.d.ts +22 -0
- package/dist/types/components/NumberField/NumberFieldShowcase.d.ts +2 -0
- package/dist/types/components/NumberField/index.d.ts +2 -0
- package/dist/types/components/Pagination/Pagination.d.ts +17 -0
- package/dist/types/components/Pagination/PaginationShowcase.d.ts +2 -0
- package/dist/types/components/Pagination/index.d.ts +2 -0
- package/dist/types/components/Paper/Paper.d.ts +111 -0
- package/dist/types/components/Paper/index.d.ts +1 -0
- package/dist/types/components/Popover/Popover.d.ts +31 -0
- package/dist/types/components/Popover/index.d.ts +1 -0
- package/dist/types/components/PreviewSurface.d.ts +18 -0
- package/dist/types/components/Radio/Radio.d.ts +55 -0
- package/dist/types/components/Radio/RadioShowcase.d.ts +2 -0
- package/dist/types/components/Radio/index.d.ts +2 -0
- package/dist/types/components/Rail/Rail.d.ts +15 -0
- package/dist/types/components/Rail/RailShowcase.d.ts +2 -0
- package/dist/types/components/Rail/index.d.ts +2 -0
- package/dist/types/components/Rating/Rating.d.ts +16 -0
- package/dist/types/components/Rating/RatingShowcase.d.ts +2 -0
- package/dist/types/components/Rating/index.d.ts +2 -0
- package/dist/types/components/Ratio/Ratio.d.ts +20 -0
- package/dist/types/components/Ratio/RatioShowcase.d.ts +2 -0
- package/dist/types/components/Ratio/index.d.ts +2 -0
- package/dist/types/components/SearchField/SearchField.d.ts +19 -0
- package/dist/types/components/SearchField/SearchFieldShowcase.d.ts +2 -0
- package/dist/types/components/SearchField/index.d.ts +2 -0
- package/dist/types/components/Section/Section.d.ts +44 -0
- package/dist/types/components/Section/SectionShowcase.d.ts +2 -0
- package/dist/types/components/Section/index.d.ts +2 -0
- package/dist/types/components/Select/Select.d.ts +23 -0
- package/dist/types/components/Select/SelectShowcase.d.ts +2 -0
- package/dist/types/components/Select/index.d.ts +2 -0
- package/dist/types/components/SettingsPanel.d.ts +2 -0
- package/dist/types/components/Sidebar/Sidebar.d.ts +53 -0
- package/dist/types/components/Sidebar/index.d.ts +1 -0
- package/dist/types/components/Slider/Slider.d.ts +79 -0
- package/dist/types/components/Slider/SliderShowcase.d.ts +2 -0
- package/dist/types/components/Slider/index.d.ts +2 -0
- package/dist/types/components/Snackbar/Snackbar.d.ts +17 -0
- package/dist/types/components/Snackbar/SnackbarShowcase.d.ts +2 -0
- package/dist/types/components/Snackbar/index.d.ts +2 -0
- package/dist/types/components/Spacing/Spacing.d.ts +16 -0
- package/dist/types/components/Spacing/index.d.ts +1 -0
- package/dist/types/components/SpeedDial/SpeedDial.d.ts +17 -0
- package/dist/types/components/SpeedDial/SpeedDialShowcase.d.ts +2 -0
- package/dist/types/components/SpeedDial/index.d.ts +2 -0
- package/dist/types/components/Stack/Stack.d.ts +52 -0
- package/dist/types/components/Stack/StackShowcase.d.ts +2 -0
- package/dist/types/components/Stack/index.d.ts +2 -0
- package/dist/types/components/Stepper/Stepper.d.ts +33 -0
- package/dist/types/components/Stepper/StepperShowcase.d.ts +2 -0
- package/dist/types/components/Stepper/index.d.ts +2 -0
- package/dist/types/components/Switch/Switch.d.ts +65 -0
- package/dist/types/components/Switch/SwitchShowcase.d.ts +2 -0
- package/dist/types/components/Switch/index.d.ts +2 -0
- package/dist/types/components/Table/Table.d.ts +20 -0
- package/dist/types/components/Table/TableShowcase.d.ts +2 -0
- package/dist/types/components/Table/index.d.ts +2 -0
- package/dist/types/components/Tabs/Tabs.d.ts +51 -0
- package/dist/types/components/Tabs/TabsShowcase.d.ts +2 -0
- package/dist/types/components/Tabs/index.d.ts +2 -0
- package/dist/types/components/Tag/Tag.d.ts +57 -0
- package/dist/types/components/Tag/TagShowcase.d.ts +2 -0
- package/dist/types/components/Tag/index.d.ts +2 -0
- package/dist/types/components/TextField/TextField.d.ts +105 -0
- package/dist/types/components/TextField/index.d.ts +1 -0
- package/dist/types/components/ToggleButton/ToggleButton.d.ts +94 -0
- package/dist/types/components/ToggleButton/index.d.ts +1 -0
- package/dist/types/components/ToggleButtonGroup/ToggleButtonGroup.d.ts +48 -0
- package/dist/types/components/Toolbar/Toolbar.d.ts +14 -0
- package/dist/types/components/Toolbar/ToolbarShowcase.d.ts +2 -0
- package/dist/types/components/Toolbar/index.d.ts +2 -0
- package/dist/types/components/Tooltip/Tooltip.d.ts +20 -0
- package/dist/types/components/Tooltip/TooltipShowcase.d.ts +2 -0
- package/dist/types/components/Tooltip/index.d.ts +2 -0
- package/dist/types/components/TreeView/TreeBranch.d.ts +14 -0
- package/dist/types/components/TreeView/TreeView.d.ts +82 -0
- package/dist/types/components/TreeView/TreeViewShowcase.d.ts +2 -0
- package/dist/types/components/TreeView/index.d.ts +3 -0
- package/dist/types/components/Typography/Typography.d.ts +492 -0
- package/dist/types/components/Typography/TypographyShowcase.d.ts +2 -0
- package/dist/types/components/Typography/index.d.ts +2 -0
- package/dist/types/components/_a11y/ContrastCheck.d.ts +15 -0
- package/dist/types/components/_bevelPresets.d.ts +92 -0
- package/dist/types/components/_shadows.d.ts +32 -0
- package/dist/types/components/index.d.ts +74 -0
- package/dist/types/theme/useThemeMode.d.ts +4 -0
- package/docs/components.md +797 -0
- package/docs/getting-started.md +409 -0
- package/docs/token-system.md +468 -0
- package/package.json +126 -0
- package/public/styles/Dark-Mode.css +13867 -0
- package/public/styles/Light-Mode.css +14091 -0
- package/public/styles/README.md +93 -0
- package/public/styles/base.css +261 -0
- package/public/styles/core.css +378 -0
- package/public/styles/foundation.css +72 -0
- package/public/styles/styles.css +6 -0
- package/public/styles/theme.js +234 -0
- package/public/styles/theme.json +12 -0
- package/public/styles/typography-tokens.css +720 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
# CLAUDE.md — OmniDesign Component Library
|
|
2
|
+
|
|
3
|
+
This file tells AI coding tools (Claude Code, Cursor, Copilot, etc.) exactly how to use this design system correctly. Read this before generating any UI code.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What This Is
|
|
8
|
+
|
|
9
|
+
A React component library where **all visual styling comes from CSS tokens**, not hardcoded values. Colors, spacing, border radius, shadows, and typography are all resolved at runtime from CSS custom properties set by `data-theme`, `data-style`, and `data-surface` attributes.
|
|
10
|
+
|
|
11
|
+
**Never write inline hex colors. Never hardcode border-radius. Never use MUI's `sx` prop for colors. Always use token variables.**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Project Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
my-project/
|
|
19
|
+
├── public/styles/
|
|
20
|
+
│ ├── foundation.css ← primitives, spacing, reset (loads 1st)
|
|
21
|
+
│ ├── core.css ← component base styles (loads 2nd)
|
|
22
|
+
│ ├── Light-Mode.css ← light color tokens + [data-theme] selectors (loads 3rd)
|
|
23
|
+
│ ├── Dark-Mode.css ← dark color tokens + [data-theme] selectors (loads 3rd, swaps with light)
|
|
24
|
+
│ ├── base.css ← data-style, data-surface rules (loads 4th)
|
|
25
|
+
│ └── styles.css ← overrides (loads last)
|
|
26
|
+
└── src/
|
|
27
|
+
├── OmniDesignProvider.js ← CSS injector + context + hook
|
|
28
|
+
├── App.js
|
|
29
|
+
├── index.js
|
|
30
|
+
└── components/ ← 49 components, all token-driven
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## The Three Attributes — Learn These First
|
|
36
|
+
|
|
37
|
+
Every component's visual output is driven by three HTML attributes on ancestor elements:
|
|
38
|
+
|
|
39
|
+
### `data-theme` — sets the color context
|
|
40
|
+
```jsx
|
|
41
|
+
<div data-theme="Primary"> // primary context
|
|
42
|
+
<div data-theme="Neutral" data-surface="Surface-Dimmest"> // black context
|
|
43
|
+
<div data-theme="App-Bar"> // navigation bar
|
|
44
|
+
<div data-theme="Success"> // semantic success
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Valid themes — there are nine:**
|
|
48
|
+
|
|
49
|
+
`Default` · `Primary` · `Secondary` · `Tertiary` · `Neutral` · `Info` ·
|
|
50
|
+
`Success` · `Warning` · `Error`
|
|
51
|
+
|
|
52
|
+
Plus three a component sets for itself rather than something you choose:
|
|
53
|
+
`App-Bar`, `Nav-Bar`, `Status`.
|
|
54
|
+
|
|
55
|
+
**That is the whole list.** Lightness and darkness are NOT themes — they are
|
|
56
|
+
the surface axis. These do not exist and resolve to nothing:
|
|
57
|
+
|
|
58
|
+
| you might reach for | write this instead |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `data-theme="White"` | `data-theme="Neutral" data-surface="Surface-Brightest"` |
|
|
61
|
+
| `data-theme="Black"` | `data-theme="Neutral" data-surface="Surface-Dimmest"` |
|
|
62
|
+
| `data-theme="Primary-Light"` | `data-theme="Primary" data-surface="Surface-Brightest"` |
|
|
63
|
+
| `data-theme="Primary-Dark"` | `data-theme="Primary" data-surface="Surface-Dimmest"` |
|
|
64
|
+
| any `*-Light` / `*-Dark` | the theme, plus a bright or dim surface |
|
|
65
|
+
|
|
66
|
+
A `data-theme` with no matching block paints nothing and reports nothing —
|
|
67
|
+
the element simply inherits whatever surrounded it. Nothing errors, so this
|
|
68
|
+
is worth getting right the first time.
|
|
69
|
+
|
|
70
|
+
### `data-style` — sets the shape language
|
|
71
|
+
```jsx
|
|
72
|
+
<div data-style="Professional"> // 4px border-radius
|
|
73
|
+
<div data-style="Modern"> // 8px border-radius
|
|
74
|
+
<div data-style="Bold"> // 16px border-radius
|
|
75
|
+
<div data-style="Playful"> // 40px border-radius
|
|
76
|
+
```
|
|
77
|
+
Set once on the root. Cascades to every component automatically.
|
|
78
|
+
|
|
79
|
+
### `data-surface` — sets background depth AND lightness
|
|
80
|
+
|
|
81
|
+
Five page surfaces, darkest to brightest:
|
|
82
|
+
|
|
83
|
+
```jsx
|
|
84
|
+
<div data-surface="Surface-Dimmest"> // darkest — black under Neutral
|
|
85
|
+
<div data-surface="Surface-Dim"> // slightly recessed
|
|
86
|
+
<div data-surface="Surface"> // base page background
|
|
87
|
+
<div data-surface="Surface-Bright"> // elevated (AppBar)
|
|
88
|
+
<div data-surface="Surface-Brightest"> // brightest — white under Neutral
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Five container levels for things that sit ON a surface:
|
|
92
|
+
|
|
93
|
+
```jsx
|
|
94
|
+
<div data-surface="Container-Lowest"> // inputs, deepest
|
|
95
|
+
<div data-surface="Container-Low"> // inset card
|
|
96
|
+
<div data-surface="Container"> // card background
|
|
97
|
+
<div data-surface="Container-High"> // modal, highest card
|
|
98
|
+
<div data-surface="Container-Highest">
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
This axis is where light and dark live. `Neutral` + `Surface-Dimmest` is a
|
|
102
|
+
pure-black region with white text; `Neutral` + `Surface-Brightest` is the
|
|
103
|
+
white one.
|
|
104
|
+
|
|
105
|
+
`data-surface` is the **only** way to change a region's background. Setting
|
|
106
|
+
it exposes the full set of paired tokens — `--Background`, `--Text`,
|
|
107
|
+
`--Quiet`, `--Header`, `--Border`, `--Border-Variant`, `--Hover`, `--Active`,
|
|
108
|
+
`--Hotlink`, and the per-palette `--Buttons-*-Border/Highlight/Lowlight`
|
|
109
|
+
overrides — all tuned for that surface's tone. Every nested component reads
|
|
110
|
+
those automatically.
|
|
111
|
+
|
|
112
|
+
```jsx
|
|
113
|
+
// ✅ Right — declare the surface, every nested token auto-resolves
|
|
114
|
+
<section data-theme="Primary" data-surface="Surface-Bright">
|
|
115
|
+
<H2>Title</H2> {/* gets --Header */}
|
|
116
|
+
<Body>Body copy</Body> {/* gets --Text */}
|
|
117
|
+
<Button variant="primary">Save</Button> {/* gets --Buttons-Primary-* */}
|
|
118
|
+
</section>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```jsx
|
|
122
|
+
// ❌ Wrong — paints the box but leaves text/border/quiet on the parent's
|
|
123
|
+
// tone, so contrast breaks the moment the surface flips dark or moves to a
|
|
124
|
+
// different Color-N. Never write these:
|
|
125
|
+
<div style={{ background: 'var(--Surface)' }}>
|
|
126
|
+
<div style={{ background: 'var(--Container)' }}>
|
|
127
|
+
<div style={{ background: 'var(--Surface-Dim)' }}>
|
|
128
|
+
<div style={{ background: 'var(--Primary-Color-11)' }}>
|
|
129
|
+
<div style={{ background: '#f0ebe0' }}>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Components that need to paint a background read `var(--Background)` (which
|
|
133
|
+
`data-surface` resolves for them). User code does **not** write
|
|
134
|
+
`var(--Background)` either — it sets `data-surface` on the element and lets
|
|
135
|
+
the cascade do the rest.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## MUI Integration
|
|
140
|
+
|
|
141
|
+
`@omni-design/components` is built on MUI. The `OmniDesignProvider` handles all MUI `ThemeProvider` wiring internally — **you never configure MUI directly**. OmniDesign's CSS custom properties drive all visual values; MUI is purely structural.
|
|
142
|
+
|
|
143
|
+
**MUI peer dependencies must be installed in the consuming app:**
|
|
144
|
+
```bash
|
|
145
|
+
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Never use MUI's theming system to set colors — always use OmniDesign tokens:**
|
|
149
|
+
```jsx
|
|
150
|
+
// ✅ Correct — token-driven
|
|
151
|
+
style={{ background: 'var(--Buttons-Primary-Button)', color: 'var(--Buttons-Primary-Text)' }}
|
|
152
|
+
|
|
153
|
+
// ❌ Wrong — bypasses the token system
|
|
154
|
+
sx={{ bgcolor: 'primary.main', color: 'white' }}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Provider Setup
|
|
160
|
+
|
|
161
|
+
The `OmniDesignProvider` must wrap your entire app. It injects CSS, configures MUI ThemeProvider, and sets the root attributes:
|
|
162
|
+
|
|
163
|
+
```jsx
|
|
164
|
+
// src/App.js
|
|
165
|
+
import { OmniDesignProvider } from './OmniDesignProvider';
|
|
166
|
+
|
|
167
|
+
function App({ foundationCSS, coreCSS, lightModeCSS, darkModeCSS, baseCSS, stylesCSS }) {
|
|
168
|
+
return (
|
|
169
|
+
<OmniDesignProvider
|
|
170
|
+
foundationCSS={foundationCSS}
|
|
171
|
+
coreCSS={coreCSS}
|
|
172
|
+
lightModeCSS={lightModeCSS}
|
|
173
|
+
darkModeCSS={darkModeCSS}
|
|
174
|
+
baseCSS={baseCSS}
|
|
175
|
+
stylesCSS={stylesCSS}
|
|
176
|
+
defaultTheme="Default"
|
|
177
|
+
defaultSurface="Surface"
|
|
178
|
+
defaultStyle="Modern"
|
|
179
|
+
>
|
|
180
|
+
{/* your app */}
|
|
181
|
+
</OmniDesignProvider>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Accessing theme state in any child component:
|
|
187
|
+
```jsx
|
|
188
|
+
import { useOmniDesign } from './OmniDesignProvider';
|
|
189
|
+
|
|
190
|
+
function MyComponent() {
|
|
191
|
+
const { theme, style, isDark, toggleDarkMode, setTheme, setStyle } = useOmniDesign();
|
|
192
|
+
// ...
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Themed sub-zones (AppBar, alerts, nav):
|
|
197
|
+
```jsx
|
|
198
|
+
import { ThemedZone, Surfaced } from './OmniDesignProvider';
|
|
199
|
+
|
|
200
|
+
// AppBar gets its own dark themed zone
|
|
201
|
+
<ThemedZone theme="App-Bar" surface="Surface-Bright" as="header">
|
|
202
|
+
<AppBar />
|
|
203
|
+
</ThemedZone>
|
|
204
|
+
|
|
205
|
+
// Semantic alert zones
|
|
206
|
+
<ThemedZone theme="Success" surface="Surface-Bright">
|
|
207
|
+
<Alert />
|
|
208
|
+
</ThemedZone>
|
|
209
|
+
|
|
210
|
+
// Card at a specific depth — no theme change, just surface
|
|
211
|
+
<Surfaced surface="Container">
|
|
212
|
+
<Card />
|
|
213
|
+
</Surfaced>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Token Reference — Use These, Not Hex Values
|
|
219
|
+
|
|
220
|
+
### Background & Surface
|
|
221
|
+
```css
|
|
222
|
+
var(--Background) /* what data-surface resolves to. Components read this.
|
|
223
|
+
User code: prefer setting data-surface on the
|
|
224
|
+
element instead of writing background here. */
|
|
225
|
+
|
|
226
|
+
/* Source vars — DO NOT write these in styles. They exist so the cascade can
|
|
227
|
+
resolve --Background. Setting data-surface="Surface" exposes --Surface as
|
|
228
|
+
--Background; data-surface="Container" exposes --Container; etc. */
|
|
229
|
+
var(--Surface)
|
|
230
|
+
var(--Surface-Dim)
|
|
231
|
+
var(--Surface-Bright)
|
|
232
|
+
var(--Container)
|
|
233
|
+
var(--Container-Low)
|
|
234
|
+
var(--Container-Lowest) /* inputs live here */
|
|
235
|
+
var(--Container-High) /* modals live here */
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Text
|
|
239
|
+
```css
|
|
240
|
+
var(--Text) /* primary body text */
|
|
241
|
+
var(--Text-Quiet) /* secondary / muted text */
|
|
242
|
+
var(--Header) /* Display and H1-H3 only - see below */
|
|
243
|
+
var(--Hotlink) /* links */
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`--Header` is a DISPLAY role, not a heading role. `<DisplayLarge>`,
|
|
247
|
+
`<DisplaySmall>` and `<H1>`-`<H3>` default to it; `<H4>`-`<H6>` default to
|
|
248
|
+
`--Text`. At H4 and below the type is body-sized and usually sits inline with
|
|
249
|
+
body copy, so a second tone reads as an inconsistency rather than as another
|
|
250
|
+
level of hierarchy. Both defaults are applied by the component - don't pass a
|
|
251
|
+
`color` prop to restate either one.
|
|
252
|
+
|
|
253
|
+
### Borders & States
|
|
254
|
+
```css
|
|
255
|
+
var(--Border)
|
|
256
|
+
var(--Border-Variant) /* subtle borders */
|
|
257
|
+
var(--Hover) /* hover overlay */
|
|
258
|
+
var(--Active) /* pressed state overlay */
|
|
259
|
+
var(--Focus-Visible) /* keyboard focus ring */
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Buttons — 9 color families
|
|
263
|
+
```css
|
|
264
|
+
/* Replace {Color} with: Primary | Secondary | Tertiary | Neutral |
|
|
265
|
+
Info | Success | Warning | Error | Default */
|
|
266
|
+
var(--Buttons-{Color}-Button) /* fill color */
|
|
267
|
+
var(--Buttons-{Color}-Text) /* label color */
|
|
268
|
+
var(--Buttons-{Color}-Border) /* border + thumb color */
|
|
269
|
+
var(--Buttons-{Color}-Hover) /* hover fill */
|
|
270
|
+
var(--Buttons-{Color}-Active) /* pressed fill */
|
|
271
|
+
|
|
272
|
+
/* Light variants */
|
|
273
|
+
var(--Buttons-{Color}-Light-Button)
|
|
274
|
+
var(--Buttons-{Color}-Light-Text)
|
|
275
|
+
var(--Buttons-{Color}-Light-Hover)
|
|
276
|
+
var(--Buttons-{Color}-Light-Active)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Icons
|
|
280
|
+
```css
|
|
281
|
+
/* Replace {Family} with: Default | Primary | Secondary | Tertiary |
|
|
282
|
+
Neutral | Info | Success | Warning | Error */
|
|
283
|
+
var(--Icons-{Family}) /* icon fill */
|
|
284
|
+
var(--Icons-{Family}-Variant) /* two-tone secondary fill */
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Style & Shape
|
|
288
|
+
```css
|
|
289
|
+
var(--Style-Border-Radius) /* set by data-style — use everywhere */
|
|
290
|
+
var(--Shadow-1) /* subtle shadow */
|
|
291
|
+
var(--Shadow-2) /* elevated shadow */
|
|
292
|
+
var(--Style-Gradient-Color-1)
|
|
293
|
+
var(--Style-Gradient-Color-2)
|
|
294
|
+
var(--Style-Gradient-Angle)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Typography — the four faces
|
|
298
|
+
|
|
299
|
+
A design system publishes four faces, not two. Each has a `--Set-Font-Family-*`
|
|
300
|
+
input and a `--Font-Family-*` token that components actually read:
|
|
301
|
+
|
|
302
|
+
```css
|
|
303
|
+
/* What components read */
|
|
304
|
+
var(--Font-Family-Display) /* the expressive face — display-large/medium/small */
|
|
305
|
+
var(--Font-Family-Header) /* h1–h6, and the only face carrying variable axes */
|
|
306
|
+
var(--Font-Family-Eyebrow) /* eyebrow labels; defaults to the OS UI stack */
|
|
307
|
+
var(--Font-Family-Body) /* everything else */
|
|
308
|
+
|
|
309
|
+
/* Per-face weights */
|
|
310
|
+
var(--Font-Weight-Display)
|
|
311
|
+
var(--Font-Weight-Header)
|
|
312
|
+
var(--Font-Weight-Eyebrow)
|
|
313
|
+
var(--Font-Weight-Body)
|
|
314
|
+
|
|
315
|
+
/* The generator's inputs — you set these, you don't read them */
|
|
316
|
+
var(--Set-Font-Family-Header)
|
|
317
|
+
var(--Set-Font-Family-Header-Weight)
|
|
318
|
+
var(--Set-Font-Family-Display)
|
|
319
|
+
var(--Set-Font-Family-Eyebrow)
|
|
320
|
+
var(--Set-Font-Family-Body)
|
|
321
|
+
var(--Set-Font-Family-Body-Weight)
|
|
322
|
+
var(--Set-Font-Family-Body-Semibold-Weight)
|
|
323
|
+
var(--Set-Font-Family-Body-Bold-Weight)
|
|
324
|
+
var(--Set-Font-Family-Decorative) /* legacy alias of the Display face */
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Every step of every ramp also publishes its own `--{Style}-Font-Family` and
|
|
328
|
+
`--{Style}-Text-Transform`, so a single style can be re-pointed without moving
|
|
329
|
+
the whole face.
|
|
330
|
+
|
|
331
|
+
### Body has two weights — Subtitle is the bold one
|
|
332
|
+
|
|
333
|
+
The Body ramp publishes exactly two weights at each size, and no bold:
|
|
334
|
+
|
|
335
|
+
```css
|
|
336
|
+
--Body-{Small,Medium,Large}-Font-Weight /* standard — var(--Font-Weight-Body) */
|
|
337
|
+
--Body-{Small,Medium,Large}-Semibold-Font-Weight /* 600 */
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**For bold at body sizes, use Subtitle.** It is Body at 700 — same face, same
|
|
341
|
+
sizes, same line height, same letter-spacing — so a third weight on Body would
|
|
342
|
+
publish the same three styles under two names:
|
|
343
|
+
|
|
344
|
+
| You want | Use | Reads |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| body, normal | `<Body>` | `--Body-Medium-Font-Weight` |
|
|
347
|
+
| body, semibold | `<Typography variant="body-semibold">` | `--Body-Medium-Semibold-Font-Weight` |
|
|
348
|
+
| body, **bold** | `<Subtitle>` | `--Subtitle-Medium-Font-Weight` (700) |
|
|
349
|
+
| bold, one step down | `<Typography variant="subtitle-small">` | `--Subtitle-Small-Font-Weight` |
|
|
350
|
+
| bold, one step up | `<SubtitleLarge>` | `--Subtitle-Large-Font-Weight` |
|
|
351
|
+
|
|
352
|
+
Three things that bite when you make that swap:
|
|
353
|
+
|
|
354
|
+
**Subtitle defaults to `--Header`, Body defaults to `--Text`.** Swapping `<Body>`
|
|
355
|
+
for `<Subtitle>` to gain weight also changes the colour. Pass
|
|
356
|
+
`color="standard"` to keep `--Text`.
|
|
357
|
+
|
|
358
|
+
**`variant="body-bold"` is an alias, not a bold.** The `-bold` body keys still
|
|
359
|
+
resolve so old markup doesn't break, but they land on the *semibold* style. If
|
|
360
|
+
you asked for `body-large-bold` and got 600, that is why — reach for Subtitle.
|
|
361
|
+
|
|
362
|
+
**`--Body-Bold-Font-Weight` (700) exists, but it is not a type style.** It is a
|
|
363
|
+
face-level weight for bolding body-face text outside the ramp (`Link` uses it).
|
|
364
|
+
Don't build a Body-Bold ramp out of it — the sizes and leading that make
|
|
365
|
+
Subtitle a real style aren't there.
|
|
366
|
+
|
|
367
|
+
`SubtitleLarge` and `Subtitle` (the medium step) are named exports;
|
|
368
|
+
`subtitle-small` is reachable only through `<Typography variant>`.
|
|
369
|
+
|
|
370
|
+
### Variable-font axes — Header only
|
|
371
|
+
|
|
372
|
+
When the Header face is a variable font (e.g. Google Sans Flex), the system
|
|
373
|
+
publishes its axes as tokens and composes them into one value:
|
|
374
|
+
|
|
375
|
+
```css
|
|
376
|
+
var(--Font-Width-Header) /* wdth */
|
|
377
|
+
var(--Font-Optical-Size-Header) /* opsz */
|
|
378
|
+
var(--Font-Slant-Header) /* slnt — negative leans right */
|
|
379
|
+
var(--Font-Grade-Header) /* GRAD */
|
|
380
|
+
var(--Font-Roundness-Header) /* ROND — 100 is fully rounded */
|
|
381
|
+
|
|
382
|
+
var(--Font-Variation-Header) /* the composed font-variation-settings value */
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`H1`–`H6` apply `--Font-Variation-Header` themselves. If you hand-roll a
|
|
386
|
+
heading, set `font-variation-settings: var(--Font-Variation-Header)` — reaching
|
|
387
|
+
for the individual axis tokens one at a time just re-derives it.
|
|
388
|
+
|
|
389
|
+
**`wght` is deliberately absent from the composed value.** `font-weight`
|
|
390
|
+
already carries it, and declaring the weight in both places lets the two
|
|
391
|
+
disagree. Set weight with `font-weight` / `--Font-Weight-Header`.
|
|
392
|
+
|
|
393
|
+
Display, Eyebrow, and Body have no axis tokens — only the Header face does.
|
|
394
|
+
|
|
395
|
+
### Page setup — the system styles NO bare elements
|
|
396
|
+
|
|
397
|
+
Loading the CSS changes nothing on its own. There is no `body`, `html`, `p`,
|
|
398
|
+
`h1` or `*` rule in any of the six files — only custom properties and opt-in
|
|
399
|
+
`.typography-*` classes. That is deliberate: a design system that restyled
|
|
400
|
+
`body` just by being imported would change a page nobody asked it to change.
|
|
401
|
+
|
|
402
|
+
So set the page defaults yourself, once:
|
|
403
|
+
|
|
404
|
+
```css
|
|
405
|
+
body {
|
|
406
|
+
font-family: var(--Font-Family-Body);
|
|
407
|
+
font-size: var(--Body-Medium-Font-Size);
|
|
408
|
+
font-weight: var(--Font-Weight-Body);
|
|
409
|
+
line-height: var(--Body-Medium-Line-Height);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Colour is separate. `--Text` and `--Background` are only defined under
|
|
414
|
+
`[data-theme]`, so a bare `body` cannot resolve them — either theme the body
|
|
415
|
+
(`<body data-theme="Default" data-surface="Surface">`) or leave colour to the
|
|
416
|
+
themed regions inside it. Type tokens have no such requirement; they resolve at
|
|
417
|
+
`:root`.
|
|
418
|
+
|
|
419
|
+
The alternative to a `body` rule is `class="typography-body"` on the elements
|
|
420
|
+
that want it — same tokens, explicit rather than global.
|
|
421
|
+
|
|
422
|
+
**`data-platform` is OPTIONAL.** `core.css` defines every `--Font-Family-*` at
|
|
423
|
+
`:root`, so text resolves without it. `typography-tokens.css` additionally
|
|
424
|
+
publishes per-platform ramps under `[data-platform="Desktop"|"IOS-Mobile"|
|
|
425
|
+
"IOS-Tablet"|"Android"]`; set the attribute only when you want a platform's
|
|
426
|
+
ramp instead of the default. Text rendering with the wrong family is almost
|
|
427
|
+
never a missing `data-platform` — check that `core.css` is loaded first.
|
|
428
|
+
|
|
429
|
+
### Eyebrow — the style formerly called Overline
|
|
430
|
+
|
|
431
|
+
The component is `<Eyebrow>` (`textStyle="eyebrow"`), in three steps:
|
|
432
|
+
`eyebrow-small` / `eyebrow` / `eyebrow-large`. `<Overline>` and
|
|
433
|
+
`textStyle="overline"` still resolve to it, so existing code keeps working, but
|
|
434
|
+
Eyebrow is the name to write.
|
|
435
|
+
|
|
436
|
+
The rename now reaches the size tokens too, so an eyebrow is spelled one way
|
|
437
|
+
in every property:
|
|
438
|
+
|
|
439
|
+
```css
|
|
440
|
+
color: var(--Eyebrow); /* per theme AND per surface */
|
|
441
|
+
font-family: var(--Font-Family-Eyebrow);
|
|
442
|
+
font-weight: var(--Font-Weight-Eyebrow);
|
|
443
|
+
font-size: var(--Eyebrow-Medium-Font-Size); /* NOT --Eyebrow-Font-Size */
|
|
444
|
+
letter-spacing: var(--Eyebrow-Medium-Letter-Spacing);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`--Eyebrow-Font-Size` and `--Eyebrow-Letter-Spacing` — with no step — do not
|
|
448
|
+
exist and never have. Sizes are per STEP. Reaching for the bare name fails
|
|
449
|
+
silently: the declaration falls back to whatever literal you wrote, so the text
|
|
450
|
+
renders at the wrong size with no error.
|
|
451
|
+
|
|
452
|
+
Steps are `--Eyebrow-{Small,Medium,Large}-*` at 12 / 13 / 15px, with tracking
|
|
453
|
+
loosening as the size drops. Use `<Eyebrow>` and the tokens resolve themselves;
|
|
454
|
+
the token names only matter when you hand-roll one.
|
|
455
|
+
|
|
456
|
+
`--Overline-{Small,Medium,Large}-*` is still emitted as an alias pointing at
|
|
457
|
+
the Eyebrow token, because a design system's CSS is frozen once published and
|
|
458
|
+
consumers upgrade the lib on their own schedule. Read the Eyebrow name; the
|
|
459
|
+
components already fall back to the Overline one for systems generated before
|
|
460
|
+
the rename.
|
|
461
|
+
|
|
462
|
+
`--Eyebrow` is not a muted `--Text`. It is a rotation off the surface's own
|
|
463
|
+
palette — Primary borrows Secondary, Secondary borrows Tertiary, Tertiary and
|
|
464
|
+
Neutral borrow Primary, and the state themes borrow black or white — so
|
|
465
|
+
substituting `--Quiet` throws the rotation away and paints every eyebrow the
|
|
466
|
+
same grey.
|
|
467
|
+
|
|
468
|
+
### Cap-height trim — Figma's "cap height to baseline"
|
|
469
|
+
|
|
470
|
+
Figma's vertical-trim setting has a CSS equivalent, and the lib ships it as
|
|
471
|
+
`CAP_HEIGHT_TRIM`:
|
|
472
|
+
|
|
473
|
+
```jsx
|
|
474
|
+
import { CAP_HEIGHT_TRIM } from './components';
|
|
475
|
+
|
|
476
|
+
<Box sx={{ paddingTop: '2px', ...CAP_HEIGHT_TRIM }}>LABEL</Box>
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
It cuts the text box to the cap height on top and the alphabetic baseline
|
|
480
|
+
underneath, so a label centres on its letterforms instead of on the font's
|
|
481
|
+
line box — which carries ascender and descender space that a button label or
|
|
482
|
+
an avatar's initials never use. `Button` and `Avatar` apply it already.
|
|
483
|
+
|
|
484
|
+
It is progressive enhancement: the rules sit inside
|
|
485
|
+
`@supports (text-box-edge: cap alphabetic)`, so a browser without `text-box`
|
|
486
|
+
renders exactly as it did before. That is why any optical padding you use to
|
|
487
|
+
fake the same effect goes OUTSIDE the spread — the block zeroes the padding
|
|
488
|
+
itself wherever the real trim applies.
|
|
489
|
+
|
|
490
|
+
Don't hand-write `text-box-trim` / `text-box-edge` in a component; spread the
|
|
491
|
+
constant so every trimmed surface trims to the same two edges.
|
|
492
|
+
|
|
493
|
+
### Avatar initials wear the Eyebrow style
|
|
494
|
+
|
|
495
|
+
`Avatar` renders its initials in the Eyebrow face, weight and tracking — not
|
|
496
|
+
Legal or Number. The eyebrow ramp is three steps and the avatar ramp is eight,
|
|
497
|
+
so the step picked per size sets weight and tracking only; the size comes from
|
|
498
|
+
the avatar's own map. The eyebrow's trailing letter-spacing is cancelled with a
|
|
499
|
+
negative `margin-inline-end`, without which the initials sit visibly left of
|
|
500
|
+
centre in the circle.
|
|
501
|
+
|
|
502
|
+
### Typography `color` prop — not `style={{ color }}`
|
|
503
|
+
|
|
504
|
+
All typography components (`H1`–`H6`, `DisplayLarge`/`DisplayMedium`/
|
|
505
|
+
`DisplaySmall`, `Body`, `BodySmall`, `BodyLarge`, `Caption`, `Eyebrow`,
|
|
506
|
+
`Label`, `Subtitle`) accept a `color` prop that maps
|
|
507
|
+
to the right brand token. **Do not write `style={{ color: 'var(--Text-…)' }}`
|
|
508
|
+
on typography** — set the prop instead:
|
|
509
|
+
|
|
510
|
+
```jsx
|
|
511
|
+
<Body color="quiet">…</Body> // var(--Text-Quiet)
|
|
512
|
+
<Body color="primary">…</Body> // var(--Text-Primary)
|
|
513
|
+
<Body color="secondary">…</Body> // var(--Text-Secondary)
|
|
514
|
+
<Body color="success">…</Body> // semantic tokens
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Available values: `standard` (default), `quiet`, `primary`, `secondary`,
|
|
518
|
+
`tertiary`, `neutral`, `info`, `success`, `warning`, `error`. Same list for
|
|
519
|
+
headings.
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Component Imports
|
|
524
|
+
|
|
525
|
+
All 49 components import from `./components` (or `@omni-design/components` if installed as a package):
|
|
526
|
+
|
|
527
|
+
```jsx
|
|
528
|
+
import {
|
|
529
|
+
// Typography
|
|
530
|
+
Typography, H1, H2, H3, H4, H5, H6,
|
|
531
|
+
DisplayLarge, DisplayMedium, DisplaySmall,
|
|
532
|
+
Body, BodySmall, BodyLarge, BodySemibold, Label, Caption,
|
|
533
|
+
Subtitle, SubtitleSmall, SubtitleLarge,
|
|
534
|
+
Eyebrow, EyebrowSmall, EyebrowLarge, // Overline* still exports as an alias
|
|
535
|
+
CAP_HEIGHT_TRIM, // sx spread — cap-height/baseline trim
|
|
536
|
+
|
|
537
|
+
// Buttons
|
|
538
|
+
Button, ButtonGroup, ButtonIcon, Fab, Rail, Toolbar,
|
|
539
|
+
ToggleButton, ToggleButtonGroup, NumberField,
|
|
540
|
+
|
|
541
|
+
// Inputs
|
|
542
|
+
TextField, TextInput, Select, Autocomplete,
|
|
543
|
+
Checkbox, RadioGroup, SwitchInput, SliderInput,
|
|
544
|
+
RatingInput, SearchField,
|
|
545
|
+
|
|
546
|
+
// Chips
|
|
547
|
+
Chip,
|
|
548
|
+
|
|
549
|
+
// Layout
|
|
550
|
+
Stack, HStack, VStack, Box, Container, Grid,
|
|
551
|
+
|
|
552
|
+
// Navigation
|
|
553
|
+
Tabs, TabList, Tab, TabPanel,
|
|
554
|
+
Breadcrumbs, Pagination, Dropdown, Menu, MenuItem,
|
|
555
|
+
BottomNavigation, Stepper, SpeedDial,
|
|
556
|
+
|
|
557
|
+
// Surfaces
|
|
558
|
+
Card, Paper,
|
|
559
|
+
|
|
560
|
+
// Dialogs
|
|
561
|
+
Dialog, Modal, Drawer,
|
|
562
|
+
|
|
563
|
+
// Feedback
|
|
564
|
+
Alert, Snackbar, CircularProgress, LinearProgress,
|
|
565
|
+
|
|
566
|
+
// Data Display
|
|
567
|
+
Avatar, AvatarGroup, Badge, Divider, List, Table, Tooltip,
|
|
568
|
+
|
|
569
|
+
// App Structure
|
|
570
|
+
AppBar, Header, Footer, Sidebar, MainLayout, Accordion,
|
|
571
|
+
|
|
572
|
+
// Utilities
|
|
573
|
+
Link, Skeleton, Backdrop,
|
|
574
|
+
} from './components';
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Component Prop Patterns
|
|
580
|
+
|
|
581
|
+
### Button
|
|
582
|
+
```jsx
|
|
583
|
+
// variant: 'primary' | 'secondary' | 'tertiary' | 'neutral' |
|
|
584
|
+
// 'info' | 'success' | 'warning' | 'error' |
|
|
585
|
+
// 'black-white' |
|
|
586
|
+
// + '-outline' + '-light' variants | 'ghost' | 'text'
|
|
587
|
+
// ('black-white' has solid + outline only — see below)
|
|
588
|
+
// size: 'small' | 'medium' | 'large'
|
|
589
|
+
<Button variant="primary" size="medium" startIcon={<AddIcon />}>
|
|
590
|
+
Save Changes
|
|
591
|
+
</Button>
|
|
592
|
+
<Button variant="primary-outline">Cancel</Button>
|
|
593
|
+
<Button variant="primary-light">Secondary Action</Button>
|
|
594
|
+
|
|
595
|
+
// black-white resolves itself against whatever it is placed on: black on a
|
|
596
|
+
// light surface, white on a dark one, with a label that always clears 4.5:1.
|
|
597
|
+
// Works on every theme and surface with no prop change.
|
|
598
|
+
<Button variant="black-white">Works anywhere</Button>
|
|
599
|
+
<Button variant="black-white-outline">Also anywhere</Button>
|
|
600
|
+
// No 'black-white-light': that variant reads --<Color>-Color-11, and
|
|
601
|
+
// black-white is a resolved pair rather than a palette, so it has no tones.
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### ButtonGroup
|
|
605
|
+
```jsx
|
|
606
|
+
// Purely presentational — visually groups Buttons (shared border, spacing).
|
|
607
|
+
// Does NOT manage selection. Each child Button owns its own onClick + variant.
|
|
608
|
+
// Props: variant, size, disabled, orientation, spacing, fullWidth
|
|
609
|
+
// (variant/size on the group are inherited as defaults by children)
|
|
610
|
+
//
|
|
611
|
+
// For a segmented selector, drive the selected state in the parent and
|
|
612
|
+
// flip each child's variant between solid and -outline:
|
|
613
|
+
<ButtonGroup variant="primary" size="small">
|
|
614
|
+
<Button
|
|
615
|
+
variant={mode === 'light' ? 'primary' : 'primary-outline'}
|
|
616
|
+
onClick={() => setMode('light')}
|
|
617
|
+
>Light</Button>
|
|
618
|
+
<Button
|
|
619
|
+
variant={mode === 'dark' ? 'primary' : 'primary-outline'}
|
|
620
|
+
onClick={() => setMode('dark')}
|
|
621
|
+
>Dark</Button>
|
|
622
|
+
</ButtonGroup>
|
|
623
|
+
|
|
624
|
+
// ❌ Do NOT pass `value` / `onChange` to ButtonGroup — it has no controlled
|
|
625
|
+
// selection API. Use ToggleButtonGroup if you want managed selection.
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Card
|
|
629
|
+
```jsx
|
|
630
|
+
// Always sets data-surface="Container" internally
|
|
631
|
+
// variant: 'default' | 'solid' | 'light'
|
|
632
|
+
<Card variant="default" color="primary">
|
|
633
|
+
<CardContent>...</CardContent>
|
|
634
|
+
<CardActions>...</CardActions>
|
|
635
|
+
</Card>
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Alert
|
|
639
|
+
```jsx
|
|
640
|
+
// variant: 'standard' | 'outline' | 'light' | 'solid'
|
|
641
|
+
// color: 8 brand colors
|
|
642
|
+
// Wrap in ThemedZone for semantic coloring:
|
|
643
|
+
<ThemedZone theme="Success" surface="Surface-Bright">
|
|
644
|
+
<Alert variant="light" color="success">
|
|
645
|
+
Operation completed successfully.
|
|
646
|
+
</Alert>
|
|
647
|
+
</ThemedZone>
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Switch
|
|
651
|
+
```jsx
|
|
652
|
+
// variant: '{color}' | '{color}-outline' | '{color}-light'
|
|
653
|
+
// size: 'small' | 'medium' | 'large'
|
|
654
|
+
<SwitchInput variant="primary" label="Enable notifications" />
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### TextField / Input
|
|
658
|
+
```jsx
|
|
659
|
+
// data-surface="Container-Lowest" set internally
|
|
660
|
+
<TextField
|
|
661
|
+
label="Email"
|
|
662
|
+
variant="primary-outline"
|
|
663
|
+
size="medium"
|
|
664
|
+
validation="error"
|
|
665
|
+
validationMessage="Invalid email address"
|
|
666
|
+
/>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### AppBar
|
|
670
|
+
```jsx
|
|
671
|
+
// Always wrap in ThemedZone — AppBar has its own themed zone
|
|
672
|
+
<ThemedZone theme="App-Bar" surface="Surface-Bright" as="header">
|
|
673
|
+
<AppBar
|
|
674
|
+
mode="desktop"
|
|
675
|
+
barColor="default"
|
|
676
|
+
companyName="My App"
|
|
677
|
+
navLinks={['Home', 'About', 'Contact']}
|
|
678
|
+
/>
|
|
679
|
+
</ThemedZone>
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Section — paint a region with the design system
|
|
683
|
+
```jsx
|
|
684
|
+
// Section bundles data-theme + data-surface + background paint into one
|
|
685
|
+
// component. Use it for page sections, footers, hero, sticky-nav wrappers.
|
|
686
|
+
<Section theme="Primary" surface="Surface" padding="80px 24px">
|
|
687
|
+
<H2>…</H2>
|
|
688
|
+
</Section>
|
|
689
|
+
|
|
690
|
+
// Inherit surface from parent (no theme switch):
|
|
691
|
+
<Section padding="48px 24px">…</Section>
|
|
692
|
+
|
|
693
|
+
// Render as a different element:
|
|
694
|
+
<Section as="footer" theme="Neutral" surface="Surface-Dimmest">…</Section>
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
Use `<ThemedZone>` for the case where you want the attributes but NOT the
|
|
698
|
+
background paint (e.g., wrapping an `AppBar` whose own root paints itself).
|
|
699
|
+
|
|
700
|
+
## The 60/30/10 pass — do this on any page you render
|
|
701
|
+
|
|
702
|
+
A page built entirely from `data-theme="Primary"` is *correct* and looks
|
|
703
|
+
monotonous. The system gives you three brand palettes and most generated pages
|
|
704
|
+
use one. So after the markup is right, do a second pass for colour BALANCE:
|
|
705
|
+
|
|
706
|
+
| share | role | where it lives |
|
|
707
|
+
| --- | --- | --- |
|
|
708
|
+
| ~60% | Primary | the dominant sections, the default surface |
|
|
709
|
+
| ~30% | Secondary | one or two whole sections, switched by `data-theme` |
|
|
710
|
+
| ~10% | Tertiary | accents only — avatars, a left border, a single stat block |
|
|
711
|
+
|
|
712
|
+
The ratio is of VISUAL AREA, not of element count. One full-bleed Secondary
|
|
713
|
+
section outweighs twenty Tertiary avatars, which is the point: Tertiary is a
|
|
714
|
+
seasoning and should never be a section background.
|
|
715
|
+
|
|
716
|
+
### How to scan a rendered page
|
|
717
|
+
|
|
718
|
+
Read it as blocks of area, top to bottom, and count what each one paints:
|
|
719
|
+
|
|
720
|
+
1. List every region that sets `data-theme` (or `<Section theme=…>`) and note
|
|
721
|
+
its rough height. That is the 60/30 split, and it is the only part that
|
|
722
|
+
moves the ratio much.
|
|
723
|
+
2. If everything is one theme, convert ONE mid-page section to Secondary.
|
|
724
|
+
Prefer a section that is already conceptually a break — testimonials, stats,
|
|
725
|
+
a pricing band — over splitting a continuous narrative.
|
|
726
|
+
3. Then place Tertiary in the small stuff, using the two moves below.
|
|
727
|
+
|
|
728
|
+
Do not chase exact percentages. 60/30/10 is a target, and anything in the
|
|
729
|
+
region of "mostly one, a clear second, a glint of a third" reads correctly.
|
|
730
|
+
|
|
731
|
+
### Tertiary move 1 — avatars
|
|
732
|
+
|
|
733
|
+
Avatars are the highest-value Tertiary slot on most pages: they repeat, they
|
|
734
|
+
are small, and they are already visually separate from the text around them.
|
|
735
|
+
|
|
736
|
+
```jsx
|
|
737
|
+
<Avatar initials={initials(name)} alt={name} color="tertiary" />
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
Pass `color`, don't restyle. The component owns its size, shape and contrast,
|
|
741
|
+
and the initials wear the Eyebrow style (see *Avatar initials wear the Eyebrow
|
|
742
|
+
style* above).
|
|
743
|
+
|
|
744
|
+
### Tertiary move 2 — a left border on NON-CLICKABLE cards
|
|
745
|
+
|
|
746
|
+
A card that the user can click already earns its emphasis from hover: it lifts,
|
|
747
|
+
it shadows, its border brightens. Adding a colour bar to it competes with that
|
|
748
|
+
and reads as a second affordance.
|
|
749
|
+
|
|
750
|
+
A card that is purely informational has no such signal, so it is the right
|
|
751
|
+
place for a Tertiary edge:
|
|
752
|
+
|
|
753
|
+
```css
|
|
754
|
+
.stat {
|
|
755
|
+
background: var(--Container);
|
|
756
|
+
border: 1px solid var(--Border-Variant);
|
|
757
|
+
/* Declared AFTER the shorthand so it only replaces the left edge. */
|
|
758
|
+
border-left: 4px solid var(--Tertiary-Color-8);
|
|
759
|
+
border-radius: var(--Card-Radius);
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Two things that are easy to get wrong:
|
|
764
|
+
|
|
765
|
+
**Order matters.** `border-left` must come after the `border` shorthand or the
|
|
766
|
+
shorthand overwrites it and the accent silently disappears.
|
|
767
|
+
|
|
768
|
+
**Clickable cards get nothing.** If the card has an `onClick`, an `<a>` wrapper,
|
|
769
|
+
or a hover transform, leave its border alone.
|
|
770
|
+
|
|
771
|
+
### Which cards get the accent — the standalone ones
|
|
772
|
+
|
|
773
|
+
Do not put a left border on every card in a grid. Repeating it across a
|
|
774
|
+
uniform set turns a 10% accent into a 30% one and flattens the hierarchy the
|
|
775
|
+
border was meant to create.
|
|
776
|
+
|
|
777
|
+
Apply it to cards that are already different from their neighbours:
|
|
778
|
+
|
|
779
|
+
- a lone card that isn't part of a set
|
|
780
|
+
- a summary or total that sits apart from the rows above it
|
|
781
|
+
- the one card in a group that differs in kind (a callout among plain items)
|
|
782
|
+
|
|
783
|
+
If every card in a region would qualify, that region does not need the accent —
|
|
784
|
+
switch the whole region's `data-theme` instead and let the surface carry it.
|
|
785
|
+
|
|
786
|
+
### What NOT to reach for
|
|
787
|
+
|
|
788
|
+
- **Do not** paint a section `data-theme="Tertiary"` to hit the 10%. That makes
|
|
789
|
+
it 30%+ and there is no palette left for accents.
|
|
790
|
+
- **Do not** use `color="tertiary"` on body text to add colour. Text carries a
|
|
791
|
+
4.5:1 requirement and a recoloured paragraph reads as a link.
|
|
792
|
+
- **Do not** hand-write a hex to "balance" a page. Every share of the ratio is
|
|
793
|
+
an existing token; if the colour you want isn't one, the ratio is not the
|
|
794
|
+
problem.
|
|
795
|
+
|
|
796
|
+
### Footer & Copyright
|
|
797
|
+
```jsx
|
|
798
|
+
// Footer = configurable 1–4 column footer with optional social row +
|
|
799
|
+
// subscribe area + auto Copyright strip. First column is always the
|
|
800
|
+
// company address / contact.
|
|
801
|
+
<Footer
|
|
802
|
+
brand={<Logo />}
|
|
803
|
+
address={{
|
|
804
|
+
company: 'My Company',
|
|
805
|
+
lines: ['123 Main St', 'San Francisco, CA'],
|
|
806
|
+
email: 'hi@example.com',
|
|
807
|
+
phone: '+1 555 555 5555',
|
|
808
|
+
}}
|
|
809
|
+
columns={[ // up to 3 more columns
|
|
810
|
+
{ title: 'Product', links: [{ label: 'Features', href: '/features' }] },
|
|
811
|
+
{ title: 'Company', links: [{ label: 'About', href: '/about' }] },
|
|
812
|
+
]}
|
|
813
|
+
socialLinks={[ // optional
|
|
814
|
+
{ icon: <TwitterIcon />, url: 'https://twitter.com/x', label: 'Twitter' },
|
|
815
|
+
]}
|
|
816
|
+
subscribe={{ // optional
|
|
817
|
+
title: 'Stay in the loop',
|
|
818
|
+
onSubscribe: (email) => api.subscribe(email),
|
|
819
|
+
}}
|
|
820
|
+
copyrightName="My Company"
|
|
821
|
+
/>
|
|
822
|
+
|
|
823
|
+
// Copyright = standalone copyright strip (used internally by Footer too).
|
|
824
|
+
<Copyright companyName="My Company" year={2026} />
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Footer/Copyright are hardcoded to `--Primary-Color-2` body / `--Primary-
|
|
828
|
+
Color-1` copyright strip for now. Long term they'll auto-derive from the
|
|
829
|
+
user's chosen background tone.
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## Flagging Missing Components
|
|
834
|
+
|
|
835
|
+
The library has gaps. If a piece of UI you need isn't already in the import
|
|
836
|
+
list above — don't invent it inline. Surface it so it can be added to the
|
|
837
|
+
library for every consumer.
|
|
838
|
+
|
|
839
|
+
Use this exact comment format above the spot where the missing piece would
|
|
840
|
+
live (the literal `MISSING-LIB-COMPONENT` tag is greppable, so reviews and the
|
|
841
|
+
`/ScanComponents` slash command can find it):
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
// MISSING-LIB-COMPONENT: <ComponentName>
|
|
845
|
+
// Needed for: <one line on the use case>
|
|
846
|
+
// Proposed API: <props sketch>
|
|
847
|
+
// Lib-track: open an issue / PR against @omni-design/components
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
Then either:
|
|
851
|
+
|
|
852
|
+
- **Trivial, one-off**: inline a minimal implementation under the tag so the
|
|
853
|
+
feature isn't blocked.
|
|
854
|
+
- **Reusable** (used in 2+ places, has its own state, portals, or needs
|
|
855
|
+
keyboard a11y): stop and tell the user. Confirm whether to add it to the
|
|
856
|
+
lib first, or inline with a tracked follow-up.
|
|
857
|
+
|
|
858
|
+
After tagging and implementing (either path), **ask the user** if they want
|
|
859
|
+
to share the component back with OmniDesign for review:
|
|
860
|
+
|
|
861
|
+
> "I've tagged `<ComponentName>` as a missing lib component. Want me to
|
|
862
|
+
> submit a proposal to the OmniDesign admin dashboard for review? If they
|
|
863
|
+
> accept it, your inline copy can be replaced in a future package update."
|
|
864
|
+
|
|
865
|
+
If the user says yes, run `/ShareComponent <ComponentName>` (see below). If
|
|
866
|
+
no, leave the tag in place — the next contributor or a future
|
|
867
|
+
`/ScanComponents` pass will surface it again.
|
|
868
|
+
|
|
869
|
+
### Scanning for outstanding gaps
|
|
870
|
+
|
|
871
|
+
Two Claude Code slash commands ship with this package:
|
|
872
|
+
|
|
873
|
+
- `/ScanComponents` — runs `grep -rn "MISSING-LIB-COMPONENT" src/` and groups
|
|
874
|
+
the results by file, so you can see what's been tagged across the codebase
|
|
875
|
+
at a glance.
|
|
876
|
+
- `/ShareComponent [Name]` — generates a submission package (proposed API +
|
|
877
|
+
inline implementation + source pointer) for a tagged component, asks the
|
|
878
|
+
user to confirm, then POSTs it to the OmniDesign admin dashboard for
|
|
879
|
+
review.
|
|
880
|
+
|
|
881
|
+
To install them in your project after `npm install @omni-design/components`:
|
|
882
|
+
|
|
883
|
+
```bash
|
|
884
|
+
mkdir -p .claude/commands
|
|
885
|
+
cp node_modules/@omni-design/components/.claude/commands/*.md \
|
|
886
|
+
.claude/commands/
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
Then restart Claude Code and type `/ScanComponents` or `/ShareComponent`.
|
|
890
|
+
(The same scan works from a plain shell without Claude Code:
|
|
891
|
+
`grep -rn "MISSING-LIB-COMPONENT" src/`.)
|
|
892
|
+
|
|
893
|
+
Empty `/ScanComponents` result → codebase is fully covered. Matches → those
|
|
894
|
+
are the components the library still needs to grow.
|
|
895
|
+
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
## Rules for AI-Generated Code
|
|
899
|
+
|
|
900
|
+
### ✅ DO
|
|
901
|
+
```jsx
|
|
902
|
+
// Always use OmniDesign components for layout and interaction — never raw HTML
|
|
903
|
+
<HStack spacing={2}> // instead of <div style={{ display: 'flex', gap: 16 }}>
|
|
904
|
+
<Button variant="outline" color="default"> // instead of <button style={{ border: '1px solid var(--Border)' }}>
|
|
905
|
+
<Body> // instead of <p> or <span>
|
|
906
|
+
<BodySmall> // instead of <span style={{ fontSize: 14 }}>
|
|
907
|
+
<Caption> // instead of <small> or <span style={{ fontSize: 12 }}>
|
|
908
|
+
<H2> // instead of <h2>
|
|
909
|
+
|
|
910
|
+
// Use token variables for all colors
|
|
911
|
+
style={{ background: 'var(--Buttons-Primary-Button)', color: 'var(--Buttons-Primary-Text)' }}
|
|
912
|
+
|
|
913
|
+
// Use data-theme for color context zones
|
|
914
|
+
<div data-theme="Success" data-surface="Surface-Bright">
|
|
915
|
+
|
|
916
|
+
// After the markup is right, run the 60/30/10 pass — a page built entirely
|
|
917
|
+
// from one palette is correct and monotonous. See "The 60/30/10 pass" above.
|
|
918
|
+
<Section theme="Secondary" surface="Surface"> // one section carries the 30
|
|
919
|
+
<Avatar initials="LN" color="tertiary" /> // accents carry the 10
|
|
920
|
+
style={{ borderLeft: '4px solid var(--Tertiary-Color-8)' }} // non-clickable cards only
|
|
921
|
+
|
|
922
|
+
// Use data-surface for background depth
|
|
923
|
+
<div data-surface="Container">
|
|
924
|
+
|
|
925
|
+
// Use var(--Style-Border-Radius) for all border radius
|
|
926
|
+
style={{ borderRadius: 'var(--Style-Border-Radius)' }}
|
|
927
|
+
|
|
928
|
+
// Use var(--Background) as the background in components
|
|
929
|
+
style={{ background: 'var(--Background)' }}
|
|
930
|
+
|
|
931
|
+
// Import components from the library
|
|
932
|
+
import { Button, Card, Alert, H2, Body, HStack } from './components';
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
### ❌ NEVER DO
|
|
936
|
+
```jsx
|
|
937
|
+
// Never use raw HTML elements when a OmniDesign component exists
|
|
938
|
+
<button style={...}> // ← use <Button>
|
|
939
|
+
<div style={{ display: 'flex' }}> // ← use <HStack> or <VStack>
|
|
940
|
+
<span style={{ fontSize: 14 }}> // ← use <BodySmall>, <Label>, <Caption>
|
|
941
|
+
<p> // ← use <Body>
|
|
942
|
+
<h2> // ← use <H2>
|
|
943
|
+
|
|
944
|
+
// Never override a component's colors with style — change the variant instead
|
|
945
|
+
<Button style={{ background: '...', color: '...' }}> // ← use variant="primary"
|
|
946
|
+
<H2 style={{ color: '...' }}> // ← H2 sets --Header itself
|
|
947
|
+
<Body style={{ color: 'var(--Quiet)' }}> // ← use quiet variant or <Caption>
|
|
948
|
+
<Card style={{ background: 'var(--Container-High)' }}> // ← Card sets data-surface itself
|
|
949
|
+
|
|
950
|
+
// Never paint a background with a surface var directly — use data-surface
|
|
951
|
+
style={{ background: 'var(--Surface)' }} // ← <div data-surface="Surface">
|
|
952
|
+
style={{ background: 'var(--Container)' }} // ← <div data-surface="Container">
|
|
953
|
+
style={{ background: 'var(--Surface-Dim)' }} // ← <div data-surface="Surface-Dim">
|
|
954
|
+
|
|
955
|
+
// Never hardcode hex colors
|
|
956
|
+
style={{ background: '#006b5a', color: '#ffffff' }}
|
|
957
|
+
|
|
958
|
+
// Never hardcode border-radius
|
|
959
|
+
style={{ borderRadius: '8px' }}
|
|
960
|
+
|
|
961
|
+
// Never use MUI sx prop for colors
|
|
962
|
+
sx={{ bgcolor: 'primary.main', color: 'white' }}
|
|
963
|
+
|
|
964
|
+
// Never use Tailwind color classes
|
|
965
|
+
className="bg-teal-600 text-white"
|
|
966
|
+
|
|
967
|
+
// Never import MUI components directly without wrapping in OmniDesign tokens
|
|
968
|
+
import Button from '@mui/material/Button'; // ← bypasses the token system
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
---
|
|
972
|
+
|
|
973
|
+
## Correct Page Structure
|
|
974
|
+
|
|
975
|
+
```jsx
|
|
976
|
+
function MyPage() {
|
|
977
|
+
return (
|
|
978
|
+
// Root: set by OmniDesignProvider — don't add data-theme here again
|
|
979
|
+
<main data-surface="Surface-Dim" style={{ minHeight: '100vh' }}>
|
|
980
|
+
|
|
981
|
+
{/* AppBar always gets its own themed zone */}
|
|
982
|
+
<ThemedZone theme="App-Bar" surface="Surface-Bright" as="header">
|
|
983
|
+
<AppBar mode="desktop" companyName="My App" />
|
|
984
|
+
</ThemedZone>
|
|
985
|
+
|
|
986
|
+
<div style={{ padding: '32px', maxWidth: 1200, margin: '0 auto' }}>
|
|
987
|
+
|
|
988
|
+
{/* Cards use Container surface */}
|
|
989
|
+
<Surfaced surface="Container">
|
|
990
|
+
<Card>
|
|
991
|
+
<CardContent>
|
|
992
|
+
<H2>Page Title</H2>
|
|
993
|
+
<Body>Content goes here.</Body>
|
|
994
|
+
</CardContent>
|
|
995
|
+
<CardActions>
|
|
996
|
+
<Button variant="primary">Save</Button>
|
|
997
|
+
<Button variant="primary-outline">Cancel</Button>
|
|
998
|
+
</CardActions>
|
|
999
|
+
</Card>
|
|
1000
|
+
</Surfaced>
|
|
1001
|
+
|
|
1002
|
+
{/* Semantic alert zones */}
|
|
1003
|
+
<ThemedZone theme="Warning" surface="Surface-Bright">
|
|
1004
|
+
<Alert variant="light" color="warning">
|
|
1005
|
+
Please review before continuing.
|
|
1006
|
+
</Alert>
|
|
1007
|
+
</ThemedZone>
|
|
1008
|
+
|
|
1009
|
+
</div>
|
|
1010
|
+
</main>
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
---
|
|
1016
|
+
|
|
1017
|
+
## Dark Mode
|
|
1018
|
+
|
|
1019
|
+
```jsx
|
|
1020
|
+
// Toggle dark mode from any component inside the Provider
|
|
1021
|
+
const { isDark, toggleDarkMode } = useOmniDesign();
|
|
1022
|
+
|
|
1023
|
+
<button onClick={toggleDarkMode}>
|
|
1024
|
+
{isDark ? 'Switch to Light' : 'Switch to Dark'}
|
|
1025
|
+
</button>
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
Dark mode swaps `Light-Mode.css` for `Dark-Mode.css` — all token values update automatically. No component code changes needed.
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
1032
|
+
## CSS Load Order (do not change)
|
|
1033
|
+
|
|
1034
|
+
```
|
|
1035
|
+
1. foundation.css — primitives
|
|
1036
|
+
2. core.css — component base styles
|
|
1037
|
+
3. Light-Mode.css — OR Dark-Mode.css (never both)
|
|
1038
|
+
4. base.css — data-style / data-surface rules
|
|
1039
|
+
5. styles.css — final overrides
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
The `OmniDesignProvider` manages this automatically. Do not add `<link>` tags for these files in `index.html` — the Provider handles injection.
|
|
1043
|
+
|
|
1044
|
+
---
|
|
1045
|
+
|
|
1046
|
+
*Repo: https://github.com/lwnoble/omni-design*
|