@marwes-ui/react 0.0.4 → 1.0.1
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/LICENSE +1 -1
- package/README.md +510 -86
- package/dist/index.d.ts +1550 -441
- package/dist/index.js +3978 -246
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Niklas Westman
|
|
3
|
+
Copyright (c) 2026 Niklas Westman, Martino Ognissanti and contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,137 +1,561 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img alt="Marwes Design System" src="https://raw.githubusercontent.com/niklas-westman/marwes/main/.github/assets/banner-light.png" width="100%" style="border-radius: 40px;">
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Resolve theme from context and call core recipes.
|
|
8
|
-
- Apply typed RenderKit output to React elements.
|
|
5
|
+
<br>
|
|
6
|
+
<br>
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
8
|
+
# Marwes Design System - React
|
|
9
|
+
|
|
10
|
+
**React components with default Marwes styling, typed theme tokens, accessibility contracts, and AI-readable semantics built in.**
|
|
11
|
+
|
|
12
|
+
React 18+ • TypeScript-first • Default CSS included • ThemeInput • Google Fonts DX • Purpose components
|
|
13
|
+
|
|
14
|
+
[Documentation](https://github.com/niklas-westman/marwes/tree/main/docs) • [Storybook](https://d3hobet9plpuvm.cloudfront.net/storybook-react/latest/) • [GitHub](https://github.com/niklas-westman/marwes)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why Use It
|
|
21
|
+
|
|
22
|
+
Marwes is built for teams and AI-assisted workflows that need a component system that is easy to install, easy to theme, and hard to misuse.
|
|
23
|
+
|
|
24
|
+
- **One package for React apps**: components, provider, default preset CSS, theme helpers, and typed props.
|
|
25
|
+
- **Beautiful defaults**: install the adapter, wrap your app, and start building without a separate CSS setup.
|
|
26
|
+
- **Consequential theming**: a `ThemeInput` object changes colors, fonts, radius, density, typography, and component visuals through shared CSS variables.
|
|
27
|
+
- **Purpose components**: `SubmitButton`, `CancelButton`, and `DestructiveButton` make intent machine-readable so tests, audits, and AI agents can handle actions safely.
|
|
28
|
+
- **Shared core contracts**: every React component is backed by the same framework-agnostic recipes, a11y mapping, and theme shape.
|
|
29
|
+
|
|
30
|
+
## Package Map
|
|
31
|
+
|
|
32
|
+
For a React app, install this package first. It includes the React adapter, loads the default preset CSS, and re-exports the core theme helpers you normally need.
|
|
33
|
+
|
|
34
|
+
| Package | Use it when |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `@marwes-ui/react` | You are building a React app. |
|
|
37
|
+
| `@marwes-ui/vue` | You are building a Vue app instead. |
|
|
38
|
+
| `@marwes-ui/core` | You are building adapters, tests, tooling, or framework-agnostic integrations. |
|
|
39
|
+
| `@marwes-ui/presets` | You need standalone preset CSS or preset theme exports. |
|
|
40
|
+
|
|
41
|
+
This split keeps installation simple for app teams while giving humans and AI agents clear package boundaries: adapters render, core defines contracts, presets style.
|
|
13
42
|
|
|
14
43
|
## Install
|
|
44
|
+
|
|
15
45
|
```bash
|
|
16
|
-
pnpm add @marwes-ui/
|
|
46
|
+
pnpm add @marwes-ui/react react react-dom
|
|
17
47
|
```
|
|
18
48
|
|
|
49
|
+
No preset CSS import is needed. `@marwes-ui/react` depends on `@marwes-ui/presets` and loads the default Marwes CSS automatically.
|
|
50
|
+
|
|
19
51
|
## Quick Start
|
|
52
|
+
|
|
20
53
|
```tsx
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
import {
|
|
55
|
+
Button,
|
|
56
|
+
ButtonVariant,
|
|
57
|
+
Checkbox,
|
|
58
|
+
Input,
|
|
59
|
+
MarwesProvider,
|
|
60
|
+
SubmitButton,
|
|
61
|
+
} from "@marwes-ui/react"
|
|
24
62
|
|
|
25
63
|
export function App() {
|
|
26
64
|
return (
|
|
27
|
-
<MarwesProvider
|
|
28
|
-
<
|
|
29
|
-
<Input placeholder="Email" onValueChange={(v) => console.log(v)} />
|
|
65
|
+
<MarwesProvider>
|
|
66
|
+
<Input placeholder="Email" ariaLabel="Email" />
|
|
30
67
|
<Checkbox ariaLabel="Subscribe" />
|
|
68
|
+
<Button variant={ButtonVariant.secondary}>Preview</Button>
|
|
69
|
+
<SubmitButton>Save</SubmitButton>
|
|
31
70
|
</MarwesProvider>
|
|
32
|
-
)
|
|
71
|
+
)
|
|
33
72
|
}
|
|
34
73
|
```
|
|
35
74
|
|
|
36
|
-
##
|
|
75
|
+
## Style App-Owned UI
|
|
37
76
|
|
|
38
|
-
Marwes
|
|
77
|
+
Marwes components pick up provider tokens automatically. Your own React styling can use the same tokens by importing `mwThemeVars`. This example uses styled-components, but the same values work in Emotion, vanilla-extract, inline style objects, CSS Modules, and plain CSS.
|
|
39
78
|
|
|
40
|
-
### Recommended: Semantic Components
|
|
41
79
|
```tsx
|
|
42
|
-
import {
|
|
80
|
+
import { Button, ButtonVariant, MarwesProvider, mwThemeVars } from "@marwes-ui/react"
|
|
81
|
+
import styled from "styled-components"
|
|
43
82
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
83
|
+
const AppShell = styled.main`
|
|
84
|
+
min-height: 100dvh;
|
|
85
|
+
padding: ${mwThemeVars.spacing.sp24};
|
|
86
|
+
color: ${mwThemeVars.color.text};
|
|
87
|
+
background: ${mwThemeVars.color.background};
|
|
88
|
+
`
|
|
89
|
+
|
|
90
|
+
const FeaturePanel = styled.section`
|
|
91
|
+
padding: ${mwThemeVars.spacing.sp24};
|
|
92
|
+
background: ${mwThemeVars.color.surface};
|
|
93
|
+
border: 1px solid ${mwThemeVars.color.border};
|
|
94
|
+
border-radius: ${mwThemeVars.ui.radius};
|
|
95
|
+
`
|
|
96
|
+
|
|
97
|
+
const PrimaryCallout = styled.aside`
|
|
98
|
+
background: ${mwThemeVars.color.primary.base};
|
|
99
|
+
color: ${mwThemeVars.color.primary.label};
|
|
100
|
+
`
|
|
101
|
+
|
|
102
|
+
export function App() {
|
|
103
|
+
return (
|
|
104
|
+
<MarwesProvider>
|
|
105
|
+
<AppShell>
|
|
106
|
+
<PrimaryCallout>Launch workspace</PrimaryCallout>
|
|
107
|
+
<FeaturePanel>
|
|
108
|
+
<Button variant={ButtonVariant.primary}>Save</Button>
|
|
109
|
+
</FeaturePanel>
|
|
110
|
+
</AppShell>
|
|
111
|
+
</MarwesProvider>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Plain CSS and CSS Modules can use the raw custom properties directly:
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
.app-shell {
|
|
120
|
+
min-height: 100dvh;
|
|
121
|
+
padding: var(--mw-spacing-sp-24);
|
|
122
|
+
color: var(--mw-color-text);
|
|
123
|
+
background: var(--mw-color-background);
|
|
124
|
+
}
|
|
49
125
|
```
|
|
50
126
|
|
|
51
|
-
|
|
127
|
+
## Use Typed Components
|
|
128
|
+
|
|
129
|
+
Import components, enums, and prop types from the same package. The values line up with the same core recipes and preset CSS.
|
|
130
|
+
|
|
52
131
|
```tsx
|
|
53
|
-
import {
|
|
132
|
+
import {
|
|
133
|
+
BadgeVariant,
|
|
134
|
+
Button,
|
|
135
|
+
ButtonSize,
|
|
136
|
+
ButtonVariant,
|
|
137
|
+
Card,
|
|
138
|
+
H1,
|
|
139
|
+
InputField,
|
|
140
|
+
Paragraph,
|
|
141
|
+
Spacer,
|
|
142
|
+
Spacings,
|
|
143
|
+
StatusBadge,
|
|
144
|
+
SubmitButton,
|
|
145
|
+
type ButtonProps,
|
|
146
|
+
type H1Props,
|
|
147
|
+
type InputFieldProps,
|
|
148
|
+
type ParagraphProps,
|
|
149
|
+
} from "@marwes-ui/react"
|
|
54
150
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
151
|
+
const primaryAction: ButtonProps = {
|
|
152
|
+
variant: ButtonVariant.primary,
|
|
153
|
+
size: ButtonSize.md,
|
|
154
|
+
children: "Create project",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const emailField: InputFieldProps = {
|
|
158
|
+
label: "Email",
|
|
159
|
+
helperText: "Used for project notifications.",
|
|
160
|
+
input: {
|
|
161
|
+
type: "email",
|
|
162
|
+
placeholder: "you@example.com",
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const titleProps: H1Props = {
|
|
167
|
+
size: "h2",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const descriptionProps: ParagraphProps = {
|
|
171
|
+
size: "md",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function ProjectPanel() {
|
|
175
|
+
return (
|
|
176
|
+
<Card title="Project setup">
|
|
177
|
+
<StatusBadge variant={BadgeVariant.success}>Ready</StatusBadge>
|
|
178
|
+
<Spacer spacing={Spacings.sp16} />
|
|
179
|
+
<H1 {...titleProps}>Launch workspace</H1>
|
|
180
|
+
<Paragraph {...descriptionProps}>
|
|
181
|
+
Components share theme tokens, typed variants, spacing, and semantic metadata.
|
|
182
|
+
</Paragraph>
|
|
183
|
+
<InputField {...emailField} />
|
|
184
|
+
<Spacer spacing={Spacings.sp24} />
|
|
185
|
+
<Button {...primaryAction} />
|
|
186
|
+
<SubmitButton>Save changes</SubmitButton>
|
|
187
|
+
</Card>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
63
190
|
```
|
|
64
191
|
|
|
65
|
-
|
|
192
|
+
## Available Components
|
|
66
193
|
|
|
67
|
-
## Current Exports
|
|
68
194
|
Provider and hooks:
|
|
69
195
|
- `MarwesProvider`
|
|
70
196
|
- `useTheme`
|
|
71
|
-
- `
|
|
197
|
+
- `useToast`
|
|
198
|
+
|
|
199
|
+
Actions and buttons:
|
|
200
|
+
- `Button`
|
|
201
|
+
- `PrimaryButton`, `SecondaryButton`, `TextButton`, `SuccessButton`
|
|
202
|
+
- `SubmitButton`, `CancelButton`, `CreateButton`, `DestructiveButton`
|
|
203
|
+
- `LinkButton`, `SaveButton`, `ConfirmButton`, `VerifyButton`
|
|
204
|
+
- `EditButton`, `CloseButton`, `RefreshButton`
|
|
205
|
+
- `UploadButton`, `DownloadButton`, `CopyButton`
|
|
206
|
+
- `SearchButton`, `FilterButton`, `SortButton`, `DropdownButton`
|
|
207
|
+
|
|
208
|
+
Forms and inputs:
|
|
209
|
+
- `Input`, `Textarea`, `Select`, `RichText`, `InputOtp`
|
|
210
|
+
- `InputField`, `TextareaField`, `SelectField`, `RichTextField`
|
|
211
|
+
- `DropdownField`, `SearchField`, `PasswordField`, `EmailField`
|
|
212
|
+
- `DateOfBirthField`, `ZipCodeField`, `PhoneField`, `URLField`, `CurrencyField`
|
|
213
|
+
- `Checkbox`, `CheckboxField`, `CheckboxGroupField`
|
|
214
|
+
- `Radio`, `RadioGroupField`, `YesNoRadioGroup`, `RatingRadioGroup`, `OptionRadioGroup`
|
|
215
|
+
- `Switch`, `SwitchField`, `FeatureToggle`, `PreferenceSwitch`, `PermissionSwitch`
|
|
216
|
+
- `Slider`, `SliderField`, `VolumeSlider`, `BrightnessSlider`, `RadiusSlider`
|
|
72
217
|
|
|
73
|
-
|
|
74
|
-
- `
|
|
75
|
-
- `
|
|
76
|
-
- `
|
|
218
|
+
Content and layout:
|
|
219
|
+
- `Card`, `ProductCard`, `ProfileCard`, `StatCard`
|
|
220
|
+
- `H1`, `H2`, `H3`, `Paragraph`
|
|
221
|
+
- `Spacer`, `Spacing`, `Divider`
|
|
77
222
|
- `Icon`
|
|
78
|
-
- `
|
|
79
|
-
- `H1`, `H2`, `H3`
|
|
80
|
-
- `Paragraph`
|
|
223
|
+
- `Avatar`, `AvatarBadge`, `AvatarGroup`, `ProfileAvatar`, `PresenceAvatar`, `TeamAvatarGroup`
|
|
81
224
|
|
|
82
|
-
|
|
83
|
-
- `
|
|
84
|
-
- `
|
|
85
|
-
- `
|
|
225
|
+
Feedback and overlays:
|
|
226
|
+
- `Badge`, `BadgeGroup`, `StatusBadge`, `PriorityBadge`, `NotificationBadge`
|
|
227
|
+
- `Spinner`, `ButtonSpinner`, `EmptyStateSpinner`
|
|
228
|
+
- `Toast`, `ToastContainer`, `ToastProvider`
|
|
229
|
+
- `SuccessToast`, `ErrorToast`, `WarningToast`, `InfoToast`
|
|
230
|
+
- `Tooltip`, `TooltipGroup`
|
|
231
|
+
- `Dialog`, `DialogModal`, `ConfirmDialog`, `DestructiveDialog`, `InfoDialog`
|
|
232
|
+
- `Accordion`, `AccordionField`, `FAQAccordion`, `SettingsAccordion`, `SectionsAccordion`
|
|
233
|
+
- `Tab`, `TabGroup`, `TabPanel`, `NavigationTabs`, `ContentTabs`, `SettingsTabs`
|
|
86
234
|
|
|
87
|
-
|
|
88
|
-
- `
|
|
235
|
+
Typed tokens and helpers:
|
|
236
|
+
- `ThemeInput`, `ThemeMode`, `Density`, `ToneName`
|
|
237
|
+
- `mwAvailableFonts`, `mwGoogleFontFamilies`, `mwFontFallbacks`, `createFontStack`
|
|
238
|
+
- `mwThemeVars`, `mwThemeVarNames`, `mwStyledTheme`, `mwVar`
|
|
239
|
+
- `ButtonVariant`, `ButtonSize`, `ButtonAction`
|
|
240
|
+
- `BadgeVariant`, `AvatarSize`, `AvatarType`, `SwitchSize`, `IconName`, `Spacings`
|
|
89
241
|
|
|
90
|
-
|
|
91
|
-
- `useRenderKitDebug`
|
|
242
|
+
## Theme In Seconds
|
|
92
243
|
|
|
93
|
-
|
|
94
|
-
- Treat `@marwes-ui/core` as source of truth for behavior and a11y.
|
|
95
|
-
- Apply RenderKit fields explicitly.
|
|
96
|
-
- Keep component wrappers thin and predictable.
|
|
97
|
-
- Do not hardcode Figma design values in adapters.
|
|
244
|
+
The default Marwes theme is already active. If you already have a good-looking design library or brand system, pass a small typed `ThemeInput` override instead of rebuilding component CSS.
|
|
98
245
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- `src/components/*` - React wrappers around core recipes
|
|
102
|
-
- `src/index.ts` - public exports
|
|
246
|
+
```tsx
|
|
247
|
+
import { MarwesProvider, mwAvailableFonts, type ThemeInput } from "@marwes-ui/react"
|
|
103
248
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
249
|
+
const brandTheme = {
|
|
250
|
+
color: {
|
|
251
|
+
primary: "#2457FF",
|
|
252
|
+
danger: "#D90429",
|
|
253
|
+
success: "#15803D",
|
|
254
|
+
warning: "#D97706",
|
|
255
|
+
background: "#F8FAFC",
|
|
256
|
+
surface: "#FFFFFF",
|
|
257
|
+
surfaceElevated: "#FFFFFF",
|
|
258
|
+
text: "#111827",
|
|
259
|
+
textMuted: "#4B5563",
|
|
260
|
+
border: "#D1D5DB",
|
|
261
|
+
focus: "#2457FF",
|
|
262
|
+
},
|
|
263
|
+
font: {
|
|
264
|
+
primary: mwAvailableFonts.Poppins,
|
|
265
|
+
secondary: mwAvailableFonts.Lora,
|
|
266
|
+
},
|
|
267
|
+
ui: {
|
|
268
|
+
radius: 10,
|
|
269
|
+
density: "comfortable",
|
|
270
|
+
},
|
|
271
|
+
} satisfies ThemeInput
|
|
107
272
|
|
|
108
|
-
|
|
273
|
+
export function App() {
|
|
274
|
+
return (
|
|
275
|
+
<MarwesProvider theme={brandTheme}>
|
|
276
|
+
<AppShell />
|
|
277
|
+
</MarwesProvider>
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
```
|
|
109
281
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
282
|
+
The provider resolves `ThemeInput` into `--mw-*` CSS variables. Preset CSS consumes those variables across the full component system.
|
|
283
|
+
|
|
284
|
+
Marwes is designed to look great from the beginning. Start with the default preset, then override only the colors, fonts, radius, or density that belong to your product. Unspecified values keep the polished Marwes defaults.
|
|
285
|
+
|
|
286
|
+
## Light And Dark Mode
|
|
114
287
|
|
|
115
|
-
|
|
288
|
+
MarwesProvider can own the active mode for the app. Use `ThemeMode.light` and `ThemeMode.dark` instead of string literals, then read or change the active mode with `useThemeMode()` anywhere under the provider. Every component under the provider receives the matching `--mw-*` variables and `mw-theme--light` / `mw-theme--dark` class.
|
|
116
289
|
|
|
290
|
+
```tsx
|
|
291
|
+
import { Button, ButtonVariant, MarwesProvider, ThemeMode, useThemeMode } from "@marwes-ui/react"
|
|
292
|
+
|
|
293
|
+
function ThemeToggle() {
|
|
294
|
+
const { mode, toggleMode } = useThemeMode()
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<Button variant={ButtonVariant.secondary} onClick={toggleMode}>
|
|
298
|
+
Use {mode === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark} mode
|
|
299
|
+
</Button>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function App() {
|
|
304
|
+
return (
|
|
305
|
+
<MarwesProvider defaultMode={ThemeMode.light}>
|
|
306
|
+
<ThemeToggle />
|
|
307
|
+
<AppShell />
|
|
308
|
+
</MarwesProvider>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
117
311
|
```
|
|
118
|
-
dist/ # Build output (npm published files)
|
|
119
|
-
├── index.js # ESM bundle
|
|
120
|
-
├── index.d.ts # TypeScript declarations
|
|
121
|
-
└── index.js.map # Source maps
|
|
122
312
|
|
|
123
|
-
|
|
124
|
-
├── components/ # React components
|
|
125
|
-
├── hooks/ # React hooks
|
|
126
|
-
└── provider/ # Context provider
|
|
313
|
+
`defaultMode` sets the initial uncontrolled mode. `toggleMode()` updates the provider, so Marwes components, preset CSS, and custom app styles that use `--mw-*` variables all move together without duplicating local theme state.
|
|
127
314
|
|
|
128
|
-
|
|
129
|
-
|
|
315
|
+
For a simple brand pass, override shared values once and let Marwes fill the rest. If your product needs different brand colors in light and dark mode, control `mode` and switch between two small `ThemeInput` override objects:
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
import { useState } from "react"
|
|
319
|
+
import {
|
|
320
|
+
Button,
|
|
321
|
+
ButtonVariant,
|
|
322
|
+
MarwesProvider,
|
|
323
|
+
ThemeMode,
|
|
324
|
+
type ThemeInput,
|
|
325
|
+
useThemeMode,
|
|
326
|
+
} from "@marwes-ui/react"
|
|
327
|
+
|
|
328
|
+
const themeByMode = {
|
|
329
|
+
[ThemeMode.light]: {
|
|
330
|
+
color: {
|
|
331
|
+
primary: "#2457FF",
|
|
332
|
+
background: "#F8FAFC",
|
|
333
|
+
surface: "#FFFFFF",
|
|
334
|
+
text: "#111827",
|
|
335
|
+
border: "#D1D5DB",
|
|
336
|
+
focus: "#2457FF",
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
[ThemeMode.dark]: {
|
|
340
|
+
color: {
|
|
341
|
+
primary: "#8BA2FF",
|
|
342
|
+
background: "#0B1020",
|
|
343
|
+
surface: "#111827",
|
|
344
|
+
text: "#F8FAFC",
|
|
345
|
+
border: "#334155",
|
|
346
|
+
focus: "#93C5FD",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
} satisfies Record<ThemeMode, ThemeInput>
|
|
350
|
+
|
|
351
|
+
function ThemeToggle() {
|
|
352
|
+
const { mode, toggleMode } = useThemeMode()
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<Button variant={ButtonVariant.secondary} onClick={toggleMode}>
|
|
356
|
+
Use {mode === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark} mode
|
|
357
|
+
</Button>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function App() {
|
|
362
|
+
const [mode, setMode] = useState<ThemeMode>(ThemeMode.light)
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<MarwesProvider mode={mode} theme={themeByMode[mode]} onModeChange={setMode}>
|
|
366
|
+
<ThemeToggle />
|
|
367
|
+
<AppShell />
|
|
368
|
+
</MarwesProvider>
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Mode-specific defaults fill every omitted token, so each override can stay small. `theme={{ mode: ThemeMode.dark }}` is still enough for the default dark baseline.
|
|
374
|
+
|
|
375
|
+
## Custom Styling Tokens
|
|
376
|
+
|
|
377
|
+
`MarwesProvider` resolves `ThemeInput` into `--mw-*` CSS variables. Marwes components and preset CSS consume those variables automatically. The custom styling token helpers let app-owned styles use the same provider-scoped values instead of hard-coding colors, spacing, radius, typography, or duplicated `var(...)` strings.
|
|
378
|
+
|
|
379
|
+
Use the helpers by purpose:
|
|
380
|
+
|
|
381
|
+
- `mwThemeVars` is the default styling helper. It returns CSS `var(...)` references for styled-components, Emotion, vanilla-extract, inline style values, and config files.
|
|
382
|
+
- `mwThemeVarNames` returns raw custom property names for assigning provider-scoped overrides in style objects or tooling.
|
|
383
|
+
- `mwVar()` wraps custom or advanced `--mw-*` names when the named token object does not cover a specialized case.
|
|
384
|
+
- `mwStyledTheme` is a plain object that mirrors `mwThemeVars` for styled-components or Emotion `ThemeProvider` usage.
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import type { ReactNode } from "react"
|
|
388
|
+
import { mwStyledTheme, mwThemeVarNames, mwThemeVars, mwVar } from "@marwes-ui/react"
|
|
389
|
+
import styled, { ThemeProvider } from "styled-components"
|
|
390
|
+
|
|
391
|
+
const Panel = styled.section`
|
|
392
|
+
padding: ${mwThemeVars.spacing.sp24};
|
|
393
|
+
color: ${mwThemeVars.color.text};
|
|
394
|
+
background: ${mwThemeVars.color.surface};
|
|
395
|
+
border-radius: ${mwThemeVars.ui.radius};
|
|
396
|
+
`
|
|
397
|
+
|
|
398
|
+
const InlinePanel = () => (
|
|
399
|
+
<div
|
|
400
|
+
style={{
|
|
401
|
+
[mwThemeVarNames.color.focus]: "#FF00AA",
|
|
402
|
+
color: mwThemeVars.color.text,
|
|
403
|
+
outlineColor: mwVar("--mw-color-focus", "#2457FF"),
|
|
404
|
+
}}
|
|
405
|
+
/>
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
const AppTheme = ({ children }: { children: ReactNode }) => (
|
|
409
|
+
<ThemeProvider theme={mwStyledTheme}>{children}</ThemeProvider>
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Plain CSS and CSS Modules do not need a JavaScript bridge:
|
|
414
|
+
|
|
415
|
+
```css
|
|
416
|
+
.panel {
|
|
417
|
+
padding: var(--mw-spacing-sp-24);
|
|
418
|
+
color: var(--mw-color-text);
|
|
419
|
+
border-radius: var(--mw-ui-radius);
|
|
420
|
+
}
|
|
130
421
|
```
|
|
131
422
|
|
|
132
|
-
|
|
423
|
+
This enables React apps to keep custom panels, local layouts, third-party CSS-in-JS components, and design-tool integrations visually tied to the same active theme as Marwes components. Changing `MarwesProvider theme={...}` updates both preset components and app-owned styles.
|
|
424
|
+
|
|
425
|
+
Keep the APIs separate:
|
|
426
|
+
- Use `Spacings.sp24` for Marwes spacing props such as `<Spacer spacing={Spacings.sp24} />`.
|
|
427
|
+
- Use `mwThemeVars.spacing.sp24` for custom CSS values.
|
|
428
|
+
- Use `mwThemeVarNames.spacing.sp24` when assigning or inspecting a CSS custom property name.
|
|
429
|
+
- Use `useTheme()` when React logic needs resolved runtime values such as `"#2457FF"`.
|
|
430
|
+
|
|
431
|
+
## Google Fonts DX
|
|
432
|
+
|
|
433
|
+
Most Google Font use cases only need `mwAvailableFonts`; no `fontLoading` prop is needed.
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import { MarwesProvider, mwAvailableFonts, type ThemeInput } from "@marwes-ui/react"
|
|
437
|
+
|
|
438
|
+
const fontTheme = {
|
|
439
|
+
font: {
|
|
440
|
+
primary: mwAvailableFonts.Poppins,
|
|
441
|
+
secondary: mwAvailableFonts.Lora,
|
|
442
|
+
},
|
|
443
|
+
} satisfies ThemeInput
|
|
444
|
+
|
|
445
|
+
<MarwesProvider
|
|
446
|
+
theme={fontTheme}
|
|
447
|
+
>
|
|
448
|
+
<App />
|
|
449
|
+
</MarwesProvider>
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
For self-hosted or licensed fonts, use `BrandSans`, `BrandSerif`, `BrandMono`, or `createFontStack()`.
|
|
453
|
+
|
|
454
|
+
## Semantic Buttons
|
|
455
|
+
|
|
456
|
+
Prefer purpose components for common actions. They lock UX intent and emit AI-readable metadata, so the component is not just "a button with red styling" but a known action with known risk.
|
|
457
|
+
|
|
458
|
+
```tsx
|
|
459
|
+
import {
|
|
460
|
+
CancelButton,
|
|
461
|
+
CreateButton,
|
|
462
|
+
DestructiveButton,
|
|
463
|
+
SubmitButton,
|
|
464
|
+
} from "@marwes-ui/react"
|
|
465
|
+
|
|
466
|
+
<CancelButton>Cancel</CancelButton>
|
|
467
|
+
<CreateButton>Create</CreateButton>
|
|
468
|
+
<SubmitButton>Save</SubmitButton>
|
|
469
|
+
<DestructiveButton>Delete</DestructiveButton>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
That matters for agentic workflows. A human, test, or AI agent can inspect the DOM and see that a destructive button requires confirmation before activation:
|
|
473
|
+
|
|
474
|
+
```html
|
|
475
|
+
<button
|
|
476
|
+
data-component="button"
|
|
477
|
+
data-purpose="destructive"
|
|
478
|
+
data-action="delete"
|
|
479
|
+
data-destructive="true"
|
|
480
|
+
data-confirmation-required="true"
|
|
481
|
+
>
|
|
482
|
+
Delete
|
|
483
|
+
</button>
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
An agent can then follow a safer rule: if `data-confirmation-required="true"`, ask the user before clicking. A test can assert the same behavior without guessing from the label "Delete" or from a red color.
|
|
487
|
+
|
|
488
|
+
Use raw `Button` props when you intentionally need a custom combination.
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
import { Button, ButtonAction, ButtonSize, ButtonVariant } from "@marwes-ui/react"
|
|
492
|
+
|
|
493
|
+
<Button
|
|
494
|
+
action={ButtonAction.submit}
|
|
495
|
+
size={ButtonSize.md}
|
|
496
|
+
variant={ButtonVariant.primary}
|
|
497
|
+
>
|
|
498
|
+
Submit
|
|
499
|
+
</Button>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Why It Is Accessible
|
|
503
|
+
|
|
504
|
+
Marwes React components are accessible because the adapter renders a shared core contract, not because each component hand-rolls ARIA in isolation.
|
|
505
|
+
|
|
506
|
+
- Core recipes produce typed `a11y` output for roles, labels, described-by wiring, invalid state, disabled state, and semantic metadata.
|
|
507
|
+
- React components prefer native DOM controls first: `button`, `input`, `select`, `textarea`, `hr`, and standard form wiring.
|
|
508
|
+
- Field components connect visible labels, helper text, and errors through `id`, `htmlFor`, `aria-describedby`, `aria-invalid`, and polite error announcements.
|
|
509
|
+
- Coordinated widgets carry explicit contracts: tabs wire `tablist`/`tab`/`tabpanel`, dialogs own dialog semantics, toasts expose live-region behavior, and purpose buttons expose risk metadata.
|
|
510
|
+
- Storybook accessibility smoke checks run through the Storybook a11y addon for the promoted React families, and shared contract tests keep React aligned with Vue.
|
|
511
|
+
|
|
512
|
+
Example:
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import { InputField, type InputFieldProps } from "@marwes-ui/react"
|
|
516
|
+
|
|
517
|
+
const receiptEmailField = {
|
|
518
|
+
label: "Email",
|
|
519
|
+
helperText: "Used for receipts.",
|
|
520
|
+
error: "Enter a valid email.",
|
|
521
|
+
input: { type: "email", placeholder: "you@example.com" },
|
|
522
|
+
} satisfies InputFieldProps
|
|
523
|
+
|
|
524
|
+
<InputField {...receiptEmailField} />
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
That contract resolves to DOM wiring like:
|
|
528
|
+
|
|
529
|
+
```html
|
|
530
|
+
<label for="email">Email</label>
|
|
531
|
+
<input
|
|
532
|
+
id="email"
|
|
533
|
+
type="email"
|
|
534
|
+
aria-describedby="email-helper email-error"
|
|
535
|
+
aria-invalid="true"
|
|
536
|
+
>
|
|
537
|
+
<p id="email-helper">Used for receipts.</p>
|
|
538
|
+
<p id="email-error" aria-live="polite">Enter a valid email.</p>
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
The important part is that the same label, helper, error, and invalid contract is tested at the shared contract layer and then applied by the React adapter.
|
|
542
|
+
|
|
543
|
+
## Package Boundaries
|
|
544
|
+
|
|
545
|
+
- `@marwes-ui/core` owns recipes, theme resolution, a11y mapping, and semantic metadata.
|
|
546
|
+
- `@marwes-ui/presets` owns default preset CSS.
|
|
547
|
+
- `@marwes-ui/react` owns React rendering and provider behavior.
|
|
548
|
+
|
|
549
|
+
## Scripts
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
pnpm --filter @marwes-ui/react build
|
|
553
|
+
pnpm --filter @marwes-ui/react typecheck
|
|
554
|
+
pnpm --filter @marwes-ui/react test
|
|
555
|
+
```
|
|
133
556
|
|
|
134
557
|
## Related Docs
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
558
|
+
|
|
559
|
+
- [Docs index](https://github.com/niklas-westman/marwes/tree/main/docs)
|
|
560
|
+
- [Architecture](https://github.com/niklas-westman/marwes/blob/main/docs/reference/architecture.md)
|
|
561
|
+
- [Figma to Marwes](https://github.com/niklas-westman/marwes/blob/main/docs/guides/figma-to-marwes.md)
|