@auldrant/ui 0.5.2 → 0.6.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/README.md CHANGED
@@ -22,7 +22,7 @@ import '@auldrant/ui/styles';
22
22
 
23
23
  function App() {
24
24
  return (
25
- <Theme class="my-theme">
25
+ <Theme>
26
26
  <Form onSubmit={(data) => console.log(data)}>
27
27
  <Button label="Submit" type="submit" />
28
28
  </Form>
@@ -54,7 +54,7 @@ function App() {
54
54
  | `Card` | Visual surface container | `children` |
55
55
  | `Section` | Semantic `<section>` with configurable heading level | `title`, `level?`, `children` |
56
56
  | `Table` | Accessible data table with required headers | `headers`, `data` |
57
- | `Theme` | Scopes `--aui-*` custom properties to its subtree | `children` |
57
+ | `Theme` | Scopes `--aui-base-*` overrides to its subtree | `children` |
58
58
  | `VisuallyHidden` | Screen-reader-only content | `children` |
59
59
 
60
60
  ### Navigation
@@ -71,42 +71,106 @@ All components extend `IBaseProps` which includes `class?` and `id?`. Form contr
71
71
 
72
72
  ## Theming
73
73
 
74
- Wrap your app in `<Theme>` and define `--aui-*` custom properties in your CSS:
74
+ The library ships with a built-in contrast system that derives all color tokens from a small set of base values. No theme class is required — the defaults work out of the box with WCAG AAA (7:1) text contrast.
75
+
76
+ ### Zero-config (defaults)
77
+
78
+ ```tsx
79
+ <Theme>
80
+ <App />
81
+ </Theme>
82
+ ```
83
+
84
+ ### Custom primary color
85
+
86
+ Provide a single `--aui-base-primary` token. The library derives all other tokens automatically:
75
87
 
76
88
  ```css
77
- .my-theme {
78
- --aui-color-text: #1a1a1a;
79
- --aui-color-text-muted: #6b7280;
80
- --aui-color-background: #ffffff;
81
- --aui-color-surface: #f9fafb;
82
- --aui-color-border: #d1d5db;
83
- --aui-color-primary: #2563eb;
84
- --aui-color-primary-hover: #1d4ed8;
85
- --aui-color-focus-ring: #3b82f6;
86
- --aui-color-error: #dc2626;
89
+ .brand {
90
+ --aui-base-primary: oklch(0.78 0.20 280);
87
91
  }
88
92
  ```
89
93
 
90
94
  ```tsx
91
- <Theme class="my-theme">
95
+ <Theme class="brand">
92
96
  <App />
93
97
  </Theme>
94
98
  ```
95
99
 
100
+ ### Full override
101
+
102
+ Override primary, white, and black for complete control:
103
+
104
+ ```css
105
+ .custom {
106
+ --aui-base-primary: oklch(0.78 0.19 150);
107
+ --aui-base-white: #fafafa;
108
+ --aui-base-black: #111111;
109
+ }
110
+ ```
111
+
112
+ ### Base tokens (consumer-provided)
113
+
114
+ | Token | Default | Description |
115
+ |-------|---------|-------------|
116
+ | `--aui-base-primary` | `oklch(0.78 0.18 260)` | Brand accent color (blue) |
117
+ | `--aui-base-white` | `#f5f5f5` | Light endpoint (light-mode background, dark-mode text) |
118
+ | `--aui-base-black` | `#1a1a1a` | Dark endpoint (dark-mode background, light-mode text) |
119
+ | `--aui-base-error` | `oklch(0.78 0.22 27)` | Error/danger semantic color |
120
+ | `--aui-base-success` | `oklch(0.78 0.18 145)` | Success semantic color |
121
+
122
+ ### Derived tokens (library-managed)
123
+
124
+ These are computed via `color-mix(in oklch, ...)` — do not set them manually.
125
+
96
126
  | Token | Purpose |
97
127
  |-------|---------|
98
128
  | `--aui-color-text` | Body text |
99
129
  | `--aui-color-text-muted` | Placeholder text, secondary content |
100
- | `--aui-color-background` | Input and page backgrounds |
130
+ | `--aui-color-background` | Page and input backgrounds |
101
131
  | `--aui-color-background-hover` | Hover state for interactive backgrounds |
102
132
  | `--aui-color-surface` | Card and container backgrounds |
103
133
  | `--aui-color-border` | Borders on inputs, cards, tables |
104
134
  | `--aui-color-primary` | Buttons, links, accents |
105
135
  | `--aui-color-primary-hover` | Hover state for buttons and links |
106
136
  | `--aui-color-focus-ring` | Focus indicator outline |
107
- | `--aui-color-error` | Validation error text |
137
+ | `--aui-color-error` | Validation error text and indicators |
138
+ | `--aui-color-success` | Success text and indicators |
139
+
140
+ ### Dark-first design
141
+
142
+ The library defaults to dark mode. In light mode (`prefers-color-scheme: light`), the direction tokens swap and primary/semantic colors are automatically darkened for contrast on light backgrounds.
108
143
 
109
- Themes are nestable for sub-themes (e.g. dark mode sections).
144
+ You provide ONE primary color optimized for dark backgrounds (oklch lightness ~0.75–0.80). The library handles light mode automatically.
145
+
146
+ ### Contrast guarantees
147
+
148
+ With the recommended `--aui-base-white` / `--aui-base-black` pair (`#f5f5f5` / `#1a1a1a`):
149
+
150
+ - Text tokens meet **WCAG AAA (7:1)** in both dark and light modes
151
+ - Border and focus-ring meet **3:1** per WCAG 1.4.11 (non-text contrast)
152
+ - Primary, error, and success meet **AAA (7:1)** for text use
153
+
154
+ Custom white/black pairs with lower inherent contrast (e.g. `#e8e8e8` / `#2a2a2a`) may reduce guarantees to AA (4.5:1). Verify with a contrast checker when using non-default endpoints.
155
+
156
+ ### Primary color guidance
157
+
158
+ Use oklch lightness **0.75–0.80** and chroma **0.15–0.22** for the primary color. Most hue families (blue, purple, teal, green, red) work well at these ranges.
159
+
160
+ Yellows and oranges (hue 60–110) are harder to guarantee at AAA because oklch lightness maps non-linearly to WCAG luminance for warm hues. Test these with a [contrast checker](https://webaim.org/resources/contrastchecker/).
161
+
162
+ ### Nestable themes
163
+
164
+ `<Theme>` is nestable for sub-themes. Inner overrides re-derive all tokens:
165
+
166
+ ```tsx
167
+ <Theme>
168
+ <App />
169
+ <Theme class="accent-section">
170
+ <Sidebar />
171
+ </Theme>
172
+ </Theme>
173
+ ```
110
174
 
111
175
  ## Routing & Signals
112
176
 
@@ -149,21 +213,13 @@ cx(styles.card, props.class); // handles undefined class prop
149
213
 
150
214
  ## Development
151
215
 
152
- ### Prerequisites
153
-
154
- - [Bun](https://bun.sh/) >= 1.0.0
155
- - Or use the included DevContainer
156
-
157
- ### Setup
158
-
159
- ```bash
160
- bun install
161
- ```
216
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code style, dev test page, and PR workflow.
162
217
 
163
218
  ### Commands
164
219
 
165
220
  | Command | Description |
166
221
  |---------|-------------|
222
+ | `bun run dev` | Start dev server with test page |
167
223
  | `bun run build` | Build the library |
168
224
  | `bun run check` | Lint and format check (Biome) |
169
225
  | `bun run check:fix` | Auto-fix lint and format issues |
@@ -1 +1 @@
1
- ._button_4gn7y_1{padding:.5em 1em;border:1px solid var(--aui-color-border);border-radius:.25em;background:var(--aui-color-primary);color:var(--aui-color-background);font:inherit;cursor:pointer}._button_4gn7y_1:hover{background:var(--aui-color-primary-hover)}._card_awzw5_1{display:grid;gap:.5em;padding:1em;border:1px solid var(--aui-color-border);border-radius:.25em;background:var(--aui-color-surface)}._field_17xwz_1{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:.5em}._error_17xwz_12{grid-column:2}._form_jr324_1{display:grid;gap:1em}._actions_jr324_6{display:grid;grid-auto-flow:column;justify-content:start;gap:.5em}._status_jr324_13{margin:0;font-size:.875em;color:var(--aui-color-text-muted)}._field_7l9ux_1{display:grid;grid-template-columns:auto 1fr;align-items:start;gap:.5em}._label_7l9ux_8{padding-top:.25em}._required_7l9ux_12{color:var(--aui-color-error)}._error_7l9ux_16{grid-column:2}._nav_778nl_1{display:grid;gap:.5em}._title_778nl_6{font-weight:700}._wrapper_1mu6v_1{display:grid;grid-template-columns:1fr auto}._input_1mu6v_6{border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}._toggle_1mu6v_13{padding:.375em .75em;border:1px solid var(--aui-color-border);border-top-right-radius:.25em;border-bottom-right-radius:.25em;background:var(--aui-color-background);color:var(--aui-color-text);font:inherit;font-size:.875em;cursor:pointer}._toggle_1mu6v_13:hover:not(:disabled){background:var(--aui-color-background-hover, var(--aui-color-background))}._fieldset_lymkd_1{border:none;padding:0}._legend_lymkd_6{font-weight:700;margin-bottom:.5em}._option_lymkd_11{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:.5em;margin-bottom:.25em}._section_4nogy_1{display:grid;gap:1em}._skip_xbsul_1{position:absolute;top:-100%;left:0;padding:.5em 1em;background:var(--aui-color-background);color:var(--aui-color-primary);z-index:1000}._skip_xbsul_1:focus{top:0}._table_dls60_1{width:100%;border-collapse:collapse}._caption_dls60_6{text-align:left;padding:.5em 0;font-weight:700}._table_dls60_1 th,._table_dls60_1 td{padding:.5em;text-align:left;border-bottom:1px solid var(--aui-color-border)}._table_dls60_1 th{font-weight:700;color:var(--aui-color-text)}._table_dls60_1 td{color:var(--aui-color-text)}._focus-ring_4u8pk_3:focus-visible{outline:2px solid var(--aui-color-focus-ring);outline-offset:2px}._disabled_4u8pk_8:disabled{opacity:.5;cursor:not-allowed}._text-input_4u8pk_13{padding:.375em .5em;border:1px solid var(--aui-color-border);border-radius:.25em;background:var(--aui-color-background);color:var(--aui-color-text);font:inherit}._link_4u8pk_22{color:var(--aui-color-primary);text-decoration:underline;cursor:pointer}._link_4u8pk_22:hover{color:var(--aui-color-primary-hover)}._check-input_4u8pk_32{accent-color:var(--aui-color-primary)}._field-error_4u8pk_36{margin:0;font-size:.875em;color:var(--aui-color-error)}._visually-hidden_4u8pk_42{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}._wrapper_1kyv1_1{display:grid;gap:.25em}._textarea_1kyv1_6{resize:vertical}._counter_1kyv1_11{justify-self:end;font-size:.875em;color:var(--aui-color-text-muted)}._theme_13mb7_1{display:contents}
1
+ :root{--aui-base-primary: oklch(.78 .18 260);--aui-base-white: #f5f5f5;--aui-base-black: #1a1a1a;--aui-base-error: oklch(.78 .22 27);--aui-base-success: oklch(.78 .18 145);--_aui-fg: var(--aui-base-white);--_aui-bg: var(--aui-base-black);--aui-color-text: var(--_aui-fg);--aui-color-text-muted: color-mix(in oklch, var(--_aui-fg) 82%, var(--_aui-bg));--aui-color-background: var(--_aui-bg);--aui-color-background-hover: color-mix(in oklch, var(--_aui-bg) 92%, var(--_aui-fg));--aui-color-surface: color-mix(in oklch, var(--_aui-bg) 97%, var(--_aui-fg));--aui-color-border: color-mix(in oklch, var(--_aui-bg) 50%, var(--_aui-fg));--aui-color-primary: var(--aui-base-primary);--aui-color-primary-hover: color-mix(in oklch, var(--aui-base-primary) 85%, var(--_aui-fg));--aui-color-focus-ring: var(--aui-base-primary);--aui-color-error: var(--aui-base-error);--aui-color-success: var(--aui-base-success)}@media(prefers-color-scheme:light){:root{--_aui-fg: var(--aui-base-black);--_aui-bg: var(--aui-base-white);--aui-color-primary: color-mix(in oklch, var(--aui-base-primary) 33%, var(--_aui-fg));--aui-color-primary-hover: color-mix(in oklch, var(--aui-base-primary) 25%, var(--_aui-fg));--aui-color-focus-ring: color-mix(in oklch, var(--aui-base-primary) 33%, var(--_aui-fg));--aui-color-error: color-mix(in oklch, var(--aui-base-error) 33%, var(--_aui-fg));--aui-color-success: color-mix(in oklch, var(--aui-base-success) 33%, var(--_aui-fg))}}._button_4gn7y_1{padding:.5em 1em;border:1px solid var(--aui-color-border);border-radius:.25em;background:var(--aui-color-primary);color:var(--aui-color-background);font:inherit;cursor:pointer}._button_4gn7y_1:hover{background:var(--aui-color-primary-hover)}._card_awzw5_1{display:grid;gap:.5em;padding:1em;border:1px solid var(--aui-color-border);border-radius:.25em;background:var(--aui-color-surface)}._field_17xwz_1{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:.5em}._error_17xwz_12{grid-column:2}._form_jr324_1{display:grid;gap:1em}._actions_jr324_6{display:grid;grid-auto-flow:column;justify-content:start;gap:.5em}._status_jr324_13{margin:0;font-size:.875em;color:var(--aui-color-text-muted)}._field_7l9ux_1{display:grid;grid-template-columns:auto 1fr;align-items:start;gap:.5em}._label_7l9ux_8{padding-top:.25em}._required_7l9ux_12{color:var(--aui-color-error)}._error_7l9ux_16{grid-column:2}._nav_778nl_1{display:grid;gap:.5em}._title_778nl_6{font-weight:700}._wrapper_1mu6v_1{display:grid;grid-template-columns:1fr auto}._input_1mu6v_6{border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}._toggle_1mu6v_13{padding:.375em .75em;border:1px solid var(--aui-color-border);border-top-right-radius:.25em;border-bottom-right-radius:.25em;background:var(--aui-color-background);color:var(--aui-color-text);font:inherit;font-size:.875em;cursor:pointer}._toggle_1mu6v_13:hover:not(:disabled){background:var(--aui-color-background-hover, var(--aui-color-background))}._fieldset_lymkd_1{border:none;padding:0}._legend_lymkd_6{font-weight:700;margin-bottom:.5em}._option_lymkd_11{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:.5em;margin-bottom:.25em}._section_4nogy_1{display:grid;gap:1em}._skip_xbsul_1{position:absolute;top:-100%;left:0;padding:.5em 1em;background:var(--aui-color-background);color:var(--aui-color-primary);z-index:1000}._skip_xbsul_1:focus{top:0}._table_dls60_1{width:100%;border-collapse:collapse}._caption_dls60_6{text-align:left;padding:.5em 0;font-weight:700}._table_dls60_1 th,._table_dls60_1 td{padding:.5em;text-align:left;border-bottom:1px solid var(--aui-color-border)}._table_dls60_1 th{font-weight:700;color:var(--aui-color-text)}._table_dls60_1 td{color:var(--aui-color-text)}._focus-ring_4u8pk_3:focus-visible{outline:2px solid var(--aui-color-focus-ring);outline-offset:2px}._disabled_4u8pk_8:disabled{opacity:.5;cursor:not-allowed}._text-input_4u8pk_13{padding:.375em .5em;border:1px solid var(--aui-color-border);border-radius:.25em;background:var(--aui-color-background);color:var(--aui-color-text);font:inherit}._link_4u8pk_22{color:var(--aui-color-primary);text-decoration:underline;cursor:pointer}._link_4u8pk_22:hover{color:var(--aui-color-primary-hover)}._check-input_4u8pk_32{accent-color:var(--aui-color-primary)}._field-error_4u8pk_36{margin:0;font-size:.875em;color:var(--aui-color-error)}._visually-hidden_4u8pk_42{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}._wrapper_1kyv1_1{display:grid;gap:.25em}._textarea_1kyv1_6{resize:vertical}._counter_1kyv1_11{justify-self:end;font-size:.875em;color:var(--aui-color-text-muted)}._theme_1x47p_11{display:contents;--aui-color-text: var(--_aui-fg);--aui-color-text-muted: color-mix(in oklch, var(--_aui-fg) 82%, var(--_aui-bg));--aui-color-background: var(--_aui-bg);--aui-color-background-hover: color-mix(in oklch, var(--_aui-bg) 92%, var(--_aui-fg));--aui-color-surface: color-mix(in oklch, var(--_aui-bg) 97%, var(--_aui-fg));--aui-color-border: color-mix(in oklch, var(--_aui-bg) 50%, var(--_aui-fg));--aui-color-primary: var(--aui-base-primary);--aui-color-primary-hover: color-mix(in oklch, var(--aui-base-primary) 85%, var(--_aui-fg));--aui-color-focus-ring: var(--aui-base-primary);--aui-color-error: var(--aui-base-error);--aui-color-success: var(--aui-base-success)}
@@ -508,7 +508,7 @@ const pt = (t) => {
508
508
  ] })
509
509
  }
510
510
  );
511
- }, Me = "_theme_13mb7_1", Ve = {
511
+ }, Me = "_theme_1x47p_11", Ve = {
512
512
  theme: Me
513
513
  }, ht = (t) => {
514
514
  const { children: e, class: n } = t;
@@ -6,27 +6,34 @@ interface IThemeProps extends IBaseProps {
6
6
  children: ComponentChildren;
7
7
  }
8
8
  /**
9
- * Theme wrapper that scopes `--aui-*` custom properties to its subtree.
9
+ * Theme wrapper that scopes `--aui-base-*` overrides to its subtree.
10
10
  *
11
- * The consumer defines the custom properties in their own CSS class:
11
+ * The library provides sensible defaults no theme class is required for
12
+ * the default dark/light appearance. Override the base primary to brand:
12
13
  *
13
14
  * ```css
14
- * .my-theme {
15
- * --aui-color-text: #1a1a1a;
16
- * --aui-color-background: #ffffff;
17
- * --aui-color-primary: #2563eb;
15
+ * .brand {
16
+ * --aui-base-primary: oklch(0.65 0.20 280);
18
17
  * }
19
18
  * ```
20
19
  *
21
- * Then wrap your app:
22
- *
23
20
  * ```tsx
24
- * <Theme class="my-theme">
21
+ * <Theme class="brand">
25
22
  * <App />
26
23
  * </Theme>
27
24
  * ```
28
25
  *
29
- * Nestable for sub-themes (e.g. dark mode sections).
26
+ * Full override (primary + white/black):
27
+ *
28
+ * ```css
29
+ * .custom {
30
+ * --aui-base-primary: oklch(0.62 0.19 150);
31
+ * --aui-base-white: #fafafa;
32
+ * --aui-base-black: #111111;
33
+ * }
34
+ * ```
35
+ *
36
+ * Nestable for sub-themes (e.g. accent sections within a page).
30
37
  */
31
38
  declare const Theme: FunctionComponent<IThemeProps>;
32
39
  export default Theme;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auldrant/ui",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Accessible Preact component library with design tokens and CSS modules",
6
6
  "author": "Colonel Jade",
@@ -53,6 +53,7 @@
53
53
  "test:watch": "bun test --watch",
54
54
  "typecheck": "tsc --noEmit",
55
55
  "prepublishOnly": "bun run build",
56
+ "dev": "vite",
56
57
  "storybook": "storybook dev -p 6006",
57
58
  "build-storybook": "storybook build",
58
59
  "prepare": "test -n \"$CI\" || lefthook install"
@@ -66,6 +67,7 @@
66
67
  "@testing-library/preact": "^3.2.4",
67
68
  "@types/bun": "latest",
68
69
  "axe-core": "^4.11.1",
70
+ "culori": "^4.0.2",
69
71
  "lefthook": "^2.1.1",
70
72
  "preact": "^10.28.4",
71
73
  "storybook": "^10.2.10",