maverick-wave 5.29.0 → 5.31.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.
Files changed (144) hide show
  1. package/README.md +2 -2
  2. package/maverick-wave.min.css +2 -2
  3. package/package.json +8 -2
  4. package/src/scss/components/_avatars.scss +35 -3
  5. package/src/scss/components/_dropdown.scss +24 -1
  6. package/src/scss/components/_lang-switch.scss +4 -8
  7. package/src/scss/components/_modals.scss +33 -5
  8. package/.claude/settings.local.json +0 -43
  9. package/.claude/skills/mw-maverick-wave/SKILL.md +0 -532
  10. package/.claude/skills/mw-maverick-wave/examples/angular-form.md +0 -279
  11. package/.claude/skills/mw-maverick-wave/examples/angular-list-page.md +0 -309
  12. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +0 -538
  13. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +0 -530
  14. package/.claude/skills/mw-maverick-wave/references/components.md +0 -2169
  15. package/.claude/skills/mw-maverick-wave/references/forms.md +0 -358
  16. package/.claude/skills/mw-maverick-wave/references/javascript.md +0 -164
  17. package/.claude/skills/mw-maverick-wave/references/layout.md +0 -764
  18. package/.claude/skills/mw-maverick-wave/references/theming.md +0 -528
  19. package/.github/workflows/ci.yml +0 -41
  20. package/.idea/codeStyles/Project.xml +0 -59
  21. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  22. package/.idea/maverick-wave.iml +0 -12
  23. package/.idea/modules.xml +0 -8
  24. package/.idea/prettier.xml +0 -6
  25. package/.idea/vcs.xml +0 -6
  26. package/.prettierignore +0 -8
  27. package/.prettierrc.json +0 -7
  28. package/CHANGELOG.md +0 -950
  29. package/CLAUDE.md +0 -60
  30. package/gulpfile.js +0 -167
  31. package/index.html +0 -1730
  32. package/release.sh +0 -31
  33. package/scripts/verify.js +0 -252
  34. package/src/assets/favicon/apple-touch-icon.png +0 -0
  35. package/src/assets/favicon/favicon-96x96.png +0 -0
  36. package/src/assets/favicon/favicon.svg +0 -1
  37. package/src/assets/favicon/site.webmanifest +0 -37
  38. package/src/assets/favicon/web-app-manifest-192x192.png +0 -0
  39. package/src/assets/favicon/web-app-manifest-512x512.png +0 -0
  40. package/src/assets/favicon.ico +0 -0
  41. package/src/assets/header-logo.svg +0 -24
  42. package/src/assets/images/gallery-city.svg +0 -49
  43. package/src/assets/images/gallery-desert.svg +0 -25
  44. package/src/assets/images/gallery-forest.svg +0 -46
  45. package/src/assets/images/gallery-lake.svg +0 -26
  46. package/src/assets/images/gallery-mountain.svg +0 -19
  47. package/src/assets/images/gallery-ocean.svg +0 -26
  48. package/src/assets/images/gallery-oldtown.svg +0 -41
  49. package/src/assets/images/gallery-snow.svg +0 -39
  50. package/src/assets/images/photo-balloon.svg +0 -35
  51. package/src/assets/images/photo-forest-path.svg +0 -31
  52. package/src/assets/images/photo-lighthouse.svg +0 -42
  53. package/src/assets/images/photo-palm.svg +0 -27
  54. package/src/assets/images/photo-peak.svg +0 -27
  55. package/src/assets/images/photo-tower.svg +0 -34
  56. package/src/assets/images/photo-waterfall.svg +0 -32
  57. package/src/assets/images/slider-field.svg +0 -124
  58. package/src/assets/images/slider-forest.svg +0 -33
  59. package/src/assets/images/slider-lake.svg +0 -35
  60. package/src/assets/images/slider-mountains.svg +0 -29
  61. package/src/assets/images/slider-trees.svg +0 -41
  62. package/src/assets/images/story-aurora.svg +0 -36
  63. package/src/assets/images/story-beach.svg +0 -34
  64. package/src/assets/images/story-stars.svg +0 -35
  65. package/src/assets/images/tile-analytics.svg +0 -22
  66. package/src/assets/images/tile-cloud.svg +0 -19
  67. package/src/assets/images/tile-ecommerce.svg +0 -14
  68. package/src/assets/images/tile-enterprise.svg +0 -22
  69. package/src/assets/images/tile-marketing.svg +0 -16
  70. package/src/assets/media/demo-chime.wav +0 -0
  71. package/src/assets/media/story-sunrise.jpg +0 -0
  72. package/src/assets/media/story-sunrise.mp4 +0 -0
  73. package/src/partials/accordions-container.html +0 -179
  74. package/src/partials/alerts-container.html +0 -151
  75. package/src/partials/announcement-container.html +0 -67
  76. package/src/partials/avatars-container.html +0 -229
  77. package/src/partials/badges-container.html +0 -114
  78. package/src/partials/blog-posts-container.html +0 -158
  79. package/src/partials/breadcrumbs-container.html +0 -126
  80. package/src/partials/button-bar-container.html +0 -175
  81. package/src/partials/buttons-container.html +0 -272
  82. package/src/partials/calendar-container.html +0 -203
  83. package/src/partials/cards-container.html +0 -727
  84. package/src/partials/code-container.html +0 -194
  85. package/src/partials/colors-container.html +0 -263
  86. package/src/partials/coming-soon-container.html +0 -10
  87. package/src/partials/contact-container.html +0 -85
  88. package/src/partials/content-slider-container.html +0 -36
  89. package/src/partials/divider-container.html +0 -70
  90. package/src/partials/documentation-container.html +0 -204
  91. package/src/partials/dropdown-container.html +0 -119
  92. package/src/partials/empty-state-container.html +0 -79
  93. package/src/partials/feed-container.html +0 -506
  94. package/src/partials/footer-container.html +0 -113
  95. package/src/partials/form-container.html +0 -262
  96. package/src/partials/form-elements-container.html +0 -616
  97. package/src/partials/form-field-container.html +0 -166
  98. package/src/partials/gallery-container.html +0 -62
  99. package/src/partials/get-started-container.html +0 -423
  100. package/src/partials/grid-container.html +0 -191
  101. package/src/partials/header-container.html +0 -69
  102. package/src/partials/header-utilities-container.html +0 -331
  103. package/src/partials/highlights-container.html +0 -71
  104. package/src/partials/history-container.html +0 -67
  105. package/src/partials/home-container.html +0 -31
  106. package/src/partials/html-lists-container.html +0 -63
  107. package/src/partials/info-container.html +0 -104
  108. package/src/partials/input-group-container.html +0 -299
  109. package/src/partials/item-lists-container.html +0 -368
  110. package/src/partials/kanban-container.html +0 -459
  111. package/src/partials/kbd-container.html +0 -63
  112. package/src/partials/leader-row-container.html +0 -139
  113. package/src/partials/login-container.html +0 -65
  114. package/src/partials/lunch-menu-container.html +0 -341
  115. package/src/partials/media-container.html +0 -27
  116. package/src/partials/meta-info-container.html +0 -21
  117. package/src/partials/modals-container.html +0 -199
  118. package/src/partials/mosaic-container.html +0 -173
  119. package/src/partials/page-header-container.html +0 -78
  120. package/src/partials/pagination-container.html +0 -64
  121. package/src/partials/palette-container.html +0 -480
  122. package/src/partials/panels-container.html +0 -126
  123. package/src/partials/parallax-container.html +0 -147
  124. package/src/partials/portrait-gallery-container.html +0 -175
  125. package/src/partials/preview-container.html +0 -136
  126. package/src/partials/pricing-container.html +0 -523
  127. package/src/partials/progress-container.html +0 -230
  128. package/src/partials/prose-container.html +0 -43
  129. package/src/partials/ratings-container.html +0 -104
  130. package/src/partials/section-head-container.html +0 -77
  131. package/src/partials/segmented-container.html +0 -66
  132. package/src/partials/skeleton-container.html +0 -87
  133. package/src/partials/spinners-container.html +0 -96
  134. package/src/partials/stepper-container.html +0 -111
  135. package/src/partials/stories-container.html +0 -182
  136. package/src/partials/tables-container.html +0 -309
  137. package/src/partials/tabs-container.html +0 -290
  138. package/src/partials/tags-container.html +0 -242
  139. package/src/partials/techstack-bucket-container.html +0 -187
  140. package/src/partials/testimonials-container.html +0 -88
  141. package/src/partials/tiles-container.html +0 -247
  142. package/src/partials/timelines-container.html +0 -196
  143. package/src/partials/typography-container.html +0 -74
  144. package/src/partials/utilities-container.html +0 -553
@@ -1,532 +0,0 @@
1
- ---
2
- name: mw-maverick-wave
3
- description: Complete usage guide for the MaverickWave CSS framework - mw-* classes, --mw-* design tokens, grid, components, forms, theming and integration into Angular, other SPAs or plain HTML. Use whenever markup or styles are written in a project that loads maverick-wave (CSS/SCSS/CDN), when a mw-* class or --mw-* token appears, or when a UI has to be built with it.
4
- ---
5
-
6
- # MaverickWave
7
-
8
- A CSS-only component framework: it ships classes and design tokens, no components,
9
- no directives, no build-time API. You write plain HTML with `mw-*` classes - in a
10
- template, a JSX file, a Twig partial, it makes no difference. Everything below is
11
- therefore framework-neutral; the Angular notes are marked as such because Angular
12
- is the reference integration.
13
-
14
- **One rule dominates all others: only use classes that actually exist.** The
15
- framework is not utility-complete, invented class names fail silently (no error,
16
- just unstyled markup). Verify with `grep -o '\.mw-[a-z0-9-]*' dist/maverick-wave.min.css | sort -u`
17
- or, in the framework repo, `npm run verify`.
18
-
19
- ## Reference files
20
-
21
- Load the one you need - do not read them all up front.
22
-
23
- | File | Content |
24
- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
25
- | `references/layout.md` | Page skeleton, grid, container, section, page header, header/navbar, footer, spacing/flex/display/text utilities |
26
- | `references/components.md` | Every component: markup, variants, sizes, state classes |
27
- | `references/forms.md` | Inputs, select, textarea, checkbox, radio, toggle, slider, input group, `mw-field` pattern, validation |
28
- | `references/theming.md` | Token model, `color-mix` derivation, light/dark, site-wide variants, SCSS configuration, cherry-picking single components |
29
- | `references/javascript.md` | What the shipped `main.js` does, why SPAs must not load it, what to implement instead |
30
-
31
- ## Examples
32
-
33
- | File | Content |
34
- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
35
- | `examples/angular-list-page.md` | Full list/CRUD page: page header, meta counters, filter bar, table with status tags, empty state, delete modal, toasts |
36
- | `examples/angular-form.md` | Reactive form: `mw-field`, validation states, input groups, numeric input, toggles, form actions |
37
- | `examples/angular-services.md` | Theme service, toast service, modal/scroll-lock, tabs and accordion without `main.js` |
38
- | `examples/static-landing-page.md` | Plain HTML page: announcement ribbon, hero with scroll cue, feature and offer cards, testimonials, scroll reveal, accordion, footer, theme toggle |
39
-
40
- ## Setup
41
-
42
- ### Plain HTML / CDN
43
-
44
- ```html
45
- <meta
46
- name="viewport"
47
- content="width=device-width, initial-scale=1.0, viewport-fit=cover"
48
- />
49
- <link
50
- rel="stylesheet"
51
- href="https://cdn.jsdelivr.net/npm/maverick-wave@5/maverick-wave.min.css"
52
- />
53
- <link
54
- rel="stylesheet"
55
- href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
56
- />
57
- ...
58
- <script src="https://cdn.jsdelivr.net/npm/maverick-wave@5/maverick-wave.min.js"></script>
59
- ```
60
-
61
- `@5` keeps this snippet current; in a real project pin the exact version so a
62
- build stays reproducible. The JS file is optional and only for
63
- server-rendered/static pages - see below. `viewport-fit=cover` is not optional -
64
- see pitfall 30.
65
-
66
- ### Angular (or any SPA)
67
-
68
- ```jsonc
69
- // angular.json
70
- "styles": ["src/assets/maverick-wave.min.css", "src/styles.scss"]
71
- // no "scripts" entry - see the warning below
72
- ```
73
-
74
- Or from the npm package via SCSS: `@use 'maverick-wave/src/scss/main';`
75
- (details and configuration in `references/theming.md`). The viewport meta above
76
- belongs in `src/index.html` there, with `viewport-fit=cover` just the same.
77
-
78
- **`main-lean` for a page that uses a handful of components.** The full build is
79
- ~41 kB gzipped and carries all 48 of them. `main-lean` is the same framework
80
- without any component - tokens, reset, typography, layout, utilities - and the
81
- components come in one by one next to it:
82
-
83
- ```scss
84
- @use 'maverick-wave/src/scss/main-lean' with (
85
- $primary-color: #1b1b1d
86
- );
87
- @use 'maverick-wave/src/scss/components/buttons';
88
- @use 'maverick-wave/src/scss/components/cards';
89
- ```
90
-
91
- That lands at ~16 kB gzipped plus what the components add (three of them ≈ 19 kB
92
- in total). The configuration goes on `main-lean`, not on the component imports.
93
-
94
- > **Never load `maverick-wave.min.js` in a SPA.** Everything in it is wired up
95
- > once on `DOMContentLoaded` and writes straight into the DOM. In an Angular
96
- > app that fires once during bootstrap: components rendered later are never
97
- > initialised, and the theme toggle mutates the DOM behind change detection.
98
- > Reimplement the handful of behaviours in your own components -
99
- > `references/javascript.md` lists all of them, `examples/angular-services.md`
100
- > shows the code.
101
-
102
- ### Icons
103
-
104
- The framework styles icon slots but ships no icons. Examples use FontAwesome
105
- (`<i class="fas fa-save"></i>`); any icon element works - inline SVG, an Angular
106
- `fa-icon`, a `<span>` with a webfont.
107
-
108
- ### Fonts
109
-
110
- No font is bundled. The defaults are system stacks - `--mw-font-family-base`
111
- and `-heading` resolve to the platform UI font, `-mono` to Fira Code with the
112
- system monospace stack behind it. Load a font yourself and either set the three
113
- `$font-family-*` variables in SCSS or override the tokens in CSS.
114
-
115
- ## Naming rules
116
-
117
- - Classes: `mw-` prefix, kebab-case: `.mw-button-bar`, `.mw-item-list-compact`
118
- - Tokens: `--mw-` prefix: `--mw-primary-color`, `--mw-border-accent`
119
- - Keyframes: `mw-` prefix too (`mw-toast-in`, `mw-home-fade`). `@keyframes` is
120
- global, so an unprefixed name in your app and one in the framework are the
121
- same animation - the loser disappears without an error
122
- - Variants are suffixes on the base class: `mw-btn` + `mw-btn-primary`,
123
- `mw-alert` + `mw-alert-danger`
124
- - Colour variant names are the same everywhere: `primary`, `secondary`,
125
- `success`, `warning`, `danger`, `info` (plus `muted` on tags). Not every
126
- component offers all of them - check `references/components.md`.
127
- - Size variants: `-sm`, `-lg`, sometimes `-xs` / `-xl`. The unsuffixed class is
128
- the medium size.
129
-
130
- ### State classes
131
-
132
- Since 4.0.0 there is one spelling for "this one is on": **`mw-active`**. It
133
- works on every component that previously wanted a bare `active` - accordion,
134
- tabs, navbar link, timeline step, image slider, button, theme toggle, segmented
135
- control - alongside gallery dot, stepper and kanban composer, which used it all
136
- along. Write `mw-active` and stop looking things up.
137
-
138
- The unprefixed `active` is still styled everywhere it used to be, so existing
139
- markup keeps working, but it is deprecated. The shipped `main.js` clears both
140
- when it switches a state off, precisely so a stale `active` in the HTML cannot
141
- leave a second tab lit.
142
-
143
- The classes that mean something _other_ than "on" keep their own names:
144
-
145
- | Component | State class |
146
- | -------------------------------------------- | -------------------------------------------------------- |
147
- | Anything switchable, "on" | `mw-active` (`active` deprecated) |
148
- | Burger button + navbar panel | `open` (no prefix), plus `mw-nav-open` on `<body>` |
149
- | Stepper indicator / label / connector / step | `mw-active`, `mw-done` |
150
- | Checkbox list item (`li`) | `mw-selected` |
151
- | Calendar day, picked | `mw-selected` |
152
- | Kanban ticket being edited | `mw-kanban-editing` |
153
- | Modal, older overlay div | `mw-modal-open` (a `<dialog>` carries `open` itself) |
154
- | Alert, dismissing / dismissed | `mw-alert-closing` → `mw-alert-closed` (`display: none`) |
155
- | Field wrapper in error | `mw-field-has-error` |
156
- | Single form control in error | `mw-form-element-error` |
157
-
158
- `mw-active` and `mw-selected` are not the same thing and not interchangeable:
159
- active is the one of several that is currently showing, selected is a choice the
160
- user made and can undo.
161
-
162
- Angular: `[class.mw-active]="i === current"`.
163
-
164
- ## Scales
165
-
166
- **Spacing** (`mw-m-*`, `mw-p-*`, `mw-gap-*` and every internal value):
167
-
168
- | key | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
169
- | --- | --- | --- | ---- | --- | ---- | --- | ---- | ---- | --- | --- | --- | --- | --- | --- | --- |
170
- | rem | 0 | 0.2 | 0.35 | 0.5 | 0.75 | 1 | 1.35 | 1.75 | 2 | 2.5 | 3.3 | 4.3 | 5.5 | 7 | 9 |
171
-
172
- Negative margins exist for keys 1-6 (`mw-mt--3` = -0.5rem). Gap has no negative
173
- keys at all (negative gap is invalid CSS and is not generated).
174
-
175
- **Font sizes** (`mw-text-3xs` … `mw-text-6xl`): `3xs` 0.6, `2xs` 0.7, `xs` 0.8,
176
- `sm` 0.9, `base` 1, `md` 1.1, `lg` 1.3, `xl` 1.5, `2xl` 1.8, `3xl` 2.2, `4xl` 2.5,
177
- `5xl` 3, `6xl` 4.3 rem. The utility classes are single steps; `h1`, `h2`,
178
- `mw-section-head-title` and `mw-section-subtitle` interpolate instead - `h1`
179
- runs `3xl` to `5xl` between 375px and 768px, `h2` `2xl` to `4xl`, the head title
180
- `2xl` to `4xl` up to 992px and the subtitle one step up to 576px. The endpoints are the values the old
181
- breakpoint steps had, so a phone and a desktop look the same and only the widths
182
- between them moved.
183
-
184
- **Breakpoints**: `xs` 375, `sm` 576, `md` 768, `lg` 992, `xl` 1200, `2xl` 1400 px.
185
- Column grids and most components are mobile-first. The mixins emit range syntax
186
- (`media-up` is `width >= bp`, `media-down` is `width < bp`), so the two never
187
- overlap at the breakpoint itself and neither has to stop a fraction short of it.
188
- Write `media-up`: the base block is the phone and each breakpoint adds to it.
189
- `media-down` is right in three cases - a rule that sets what the base never sets
190
- (turned around it would hard-wire an inherited value), a narrow layout that costs
191
- more properties than the wide one (the navbar sheet, the modal bottom sheet), and
192
- a class that only ever means "below here", like `mw-hide-mobile`.
193
-
194
- **Overriding.** Everything the framework emits sits in
195
- `@layer mw.reset, mw.base, mw.forms, mw.components, mw.layout, mw.utilities`.
196
- Any rule written outside a layer beats all of it, so a single `.mw-card {}` in
197
- your own stylesheet wins - no `!important`, no doubled selectors. `!important`
198
- is not a stronger version of this but a weaker one: important declarations
199
- reverse the layer order and unlayered comes last, so an important rule in
200
- `mw.base` beats an important one of yours even behind an ID selector. The flip
201
- side: third-party CSS loaded unlayered also wins, so pull it into a layer of
202
- its own:
203
- `@import url('font-awesome.css') layer(vendor);` with `@layer vendor, mw;`
204
- declared before it.
205
-
206
- **Radius** (`mw-radius-none|xs|sm|md|lg|xl|2xl|full`): 0, 2, 5, 10, 15, 20, 30 px, 50%.
207
- Every one of them is multiplied by `--mw-radius-scale` (default 1), so one number
208
- squares the framework off or rounds it further; `none` and `full` pass through.
209
-
210
- **The surface signature.** Every panel-like component - card, panel, modal,
211
- accordion, tile, calendar, pagination, login box - shares one silhouette: sharp
212
- 5px corners on the top-left/bottom-right diagonal, round 20px ones on the other,
213
- plus a 2px **corner accent** in the primary tone (`--mw-corner-accent`) sitting
214
- on the two round corners. It is not a class and not opt-in; the components carry
215
- it. `mw-corner-plain` drops the accent on a box too small to hold it - see
216
- `references/layout.md` - and `mw-corners-even` on `<html>` drops the whole
217
- signature site-wide.
218
-
219
- **Elevation** (`mw-elevation-0` … `-5`, and `var(--mw-elevation-N)` inside SCSS).
220
- Two shadows per level - a tight contact layer plus a wide ambient one:
221
- `1` resting (inputs, tags), `2` raised (cards, panels at rest), `3` floating
222
- (a card under the pointer), `4` overlay (dropdown, popover, drawer), `5` modal.
223
- `0` is explicitly flat. Never write a `box-shadow` by hand - the twelve one-off
224
- values that used to exist are exactly what this replaced.
225
-
226
- **Glow** (`mw-glow`, `mw-glow-{primary,secondary,info,success,warning,danger}`).
227
- The same two-layer idea without the offset, so the surface reads as giving off
228
- the light rather than casting a shadow. For the one element on a screen that has
229
- to be seen first, and for nothing else.
230
-
231
- **Motion** `--mw-duration-instant|fast|base|slow|slower` = 110/180/300/520/900ms
232
- plus `--mw-duration-zoom` (650ms, for a large surface actually travelling),
233
- `--mw-ease-out` (things arriving - the default), `--mw-ease-in-out` (A to B and
234
- back), `--mw-ease-spring` (a pop). Two ready-made transitions:
235
- `var(--mw-transition)` for hover and focus states, `var(--mw-transition-fast)`
236
- for anything that should feel instant under the pointer. Both list their
237
- properties explicitly rather than saying `all`. All six durations are multiplied
238
- by `--mw-motion-scale` (default 1), the tempo counterpart to
239
- `--mw-radius-scale`; `prefers-reduced-motion` overrides the result.
240
-
241
- **Control sizes** `--mw-control-height-sm|base|lg` = 1.875 / 2.125 / 2.375rem (an
242
- even 30 / 34 / 38px step, moved by `mw-density-compact` and `-roomy`) and
243
- `--mw-control-font-sm|base|lg` = 0.8 / 0.9 / 1rem, shared by `mw-input`,
244
- `mw-select`, `mw-textarea` and `mw-btn`. A field and the button beside it are
245
- the same height by construction. Buttons run one font step above the fields.
246
-
247
- **Focus** `--mw-focus-ring-width` 2px, `--mw-focus-ring-offset` 2px,
248
- `--mw-focus-ring-color`, plus `--mw-focus-halo-size` 3px /
249
- `--mw-focus-halo-opacity` 28% for the soft ring a form field gets instead of a
250
- hard outline. SCSS: `@include focus-ring`, `focus-ring-inset`, `field-focus`.
251
-
252
- **SCSS helpers** - `@use 'maverick-wave/src/scss/abstracts' as *` brings them in;
253
- none of them survive into the compiled CSS. Full list in `references/theming.md`.
254
-
255
- - `fluid($min, $max, $from: 'xs', $to: 'md')` - a clamp between two sizes, each
256
- end a key of `$font-sizes` or a plain rem/px length. The rem term is the
257
- point: a vw-only clamp ignores the reader's font size and stops responding to
258
- zoom. A headline passes `$to: 'xl'`.
259
- - `fluid-container($min, $max, $from, $to)` - the same ramp in `cqi` against the
260
- container. Use it instead of `fluid()` inside a `@container` block.
261
- - `media-up($bp)` / `media-down($bp)` - the breakpoint map as a query. `media-up`
262
- is the default direction, see **Breakpoints** above.
263
- - `touch-context($bp: 'md')` - coarse pointer _or_ narrow viewport, the condition
264
- every target-size rule in the framework hangs under.
265
- - `touch-floor($size: 2.75rem)` - that condition plus a `min-height`.
266
- - `hit-area($grow: 6px, $box: 1.5rem)` - grows the hit area through `::after`
267
- without touching the silhouette. An element carrying a `data-tooltip` owns that
268
- pseudo for its arrow, so those are skipped and grow to `$box` instead.
269
- - `hover` / `hover-move`, `focus-ring`, `focus-ring-inset`, `field-focus`,
270
- `truncate`, `surface`.
271
-
272
- ## Component index
273
-
274
- Everything below is documented in `references/components.md` unless marked otherwise.
275
-
276
- **Actions** `mw-btn` (+ `primary`, `secondary`, `danger`, `success`, `outline`,
277
- `ghost`, `ghost-danger`, `link`, `link-muted`, `plain`, `icon`, `block`, `sm`,
278
- `lg`) · `mw-btn-mini` · `mw-link` / `mw-link-muted` (the link in running text) ·
279
- `mw-button-bar`
280
- (+ `left`, `right`, `center`, `between`) · `mw-segmented` · `mw-actions-note`
281
-
282
- **Containers** `mw-card` (+ `simple`, `lg`, `xl`, `stack`, badge, ribbon,
283
- feature frame) ·
284
- `mw-panel` · `mw-tile` · `mw-accordion` · `mw-tabs` · `mw-modal` ·
285
- `mw-item-list` family · `mw-offer` (+ `-head`, `-name`) ·
286
- `mw-leader-row` (+ `-label`, `-value`, `-top`, `-dashed`, `-solid`) ·
287
- `mw-date-stamp` (+ `-day`, `-date`, `-body`, `-today`) ·
288
- `mw-contact` (+ `-row`, `-label`)
289
-
290
- **Pricing** `mw-price` (+ `-amount`, `-fraction`, `-currency`, `-original`,
291
- `-period`, `-word`, `-note`, `xs`/`sm`/`lg`, `inline`, `center`, `plain`)
292
-
293
- **Data & status** `mw-table` (+ `subtle`, `sticky-head`, `cards`, `compact`,
294
- `hover`, responsive wrappers) · `mw-kanban` (+ `plain`, `compact`) ·
295
- `mw-calendar` (+ `compact`, `plain`) · `mw-tag` /
296
- `mw-tags` · `mw-badge` (+ `-dot`, `-status`, `-anchor`, `-float`, `-pulse`) ·
297
- `mw-info` / `mw-info-mini` / `mw-info-counter` · `mw-progress-bar` ·
298
- `mw-rating` · `mw-meta-header` · `mw-stepper` · `mw-timeline-big` /
299
- `mw-timeline-simple`
300
-
301
- **Feedback** `mw-alert` · `mw-toast-stack` · `mw-announcement` (+ `-content`,
302
- `-highlight`, `-static`, color variants) · `mw-empty-state` ·
303
- `mw-spinner-border` / `mw-spinner-dots` / `mw-spinner-dual-ring` · `mw-skeleton`
304
-
305
- **Navigation** `mw-header` (+ `mw-header-reveal`, `mw-header-keep`) +
306
- `mw-navbar` · `mw-breadcrumbs` · `mw-pagination` ·
307
- `mw-dropdown` (+ `-menu`, `-item`, `-item-danger`, `-divider`, `-label`,
308
- `-caret`, `-end`, `-up`) · `mw-lang-switch` (+ `-code`, `-name`, `-check`,
309
- `inverted`) · `mw-flag` (+ 23 country codes) ·
310
- `mw-section-nav` (`references/layout.md`)
311
-
312
- **Media & content** `mw-avatar` (+ `initials`, `group`, `landscape`,
313
- `portrait`) · `mw-gallery` · `mw-image-slider` · `mw-mosaic` (+ lightbox) ·
314
- `mw-portrait-gallery` · `mw-feed` · `mw-stories` / `mw-story-reel` ·
315
- `mw-overlay-btn` · `mw-blog-post` · `mw-testimonial` (+ `-source`, `-detail`,
316
- `-date`, `-featured`) · `mw-prose` · `mw-media` (+ `-caption`) · `mw-code-block` /
317
- `mw-terminal` · `mw-techstack-bucket` · `mw-coming-soon` · `mw-divider` ·
318
- `mw-kbd` · `mw-list` family
319
-
320
- **Forms** (`references/forms.md`) `mw-field` · `mw-input` · `mw-select` ·
321
- `mw-textarea` · `mw-checkbox` · `mw-radio` · `mw-toggle` · `mw-slider` ·
322
- `mw-input-group` · `mw-prefilled` · `mw-form` / `mw-form-group` /
323
- `mw-form-actions` · `mw-login`
324
-
325
- **Layout** (`references/layout.md`) `mw-main` · `mw-container` (+ `-narrow`) ·
326
- `mw-content` ·
327
- `mw-section` · `mw-section-head` (+ `-title`, `-intro`, `-numbered`, colours) ·
328
- `mw-page-header` · `mw-grid-*`
329
- (+ `mw-grid-even`, `mw-grid-from-{sm,md,lg,xl}`) ·
330
- `mw-columns-2/3` · `mw-row-split` · `mw-hero` (+ `mw-scroll-hint`, `-end`) ·
331
- `mw-parallax` (+ `-media`, `-content`, `-dimmed`, `-sticky`, `-rise`,
332
- `-slow`, `-pattern`) · `mw-footer`
333
-
334
- **Utilities** (`references/layout.md`) `mw-sr-only` / `mw-sr-only-focusable` /
335
- `mw-skip-link` · `mw-row-split` (+ `center`) · `mw-text-numeric` /
336
- `mw-text-currency` · `mw-text-truncate` / `mw-text-clamp-2..5` /
337
- `mw-text-break` / `mw-text-nowrap` · `mw-text-balance` / `mw-text-pretty` /
338
- `mw-text-eyebrow` / `mw-text-measure` · `mw-elevation-0..5` · `mw-glow` /
339
- `mw-glow-{primary,secondary,info,success,warning,danger}` ·
340
- `mw-corner-plain` ·
341
- `mw-aspect-square|video|wide|portrait|photo` · `mw-d-{sm,md,lg,xl}-*` /
342
- `mw-hide-mobile` / `mw-hide-desktop` / `mw-hide-print` / `mw-print-only` · `mw-overflow-*` / `mw-snap-x` ·
343
- `mw-reveal` / `mw-reveal-stagger` · spacing, flex, display, text
344
-
345
- ## Pitfalls
346
-
347
- 1. **`<section>` gets no padding by itself.** Since 3.4.0 the bare element
348
- selector is gone - write `<section class="mw-section">`. `mw-section-alternate`
349
- only paints the pattern, it does not space anything.
350
- 2. **`mw-form` has no padding** (removed in 3.4.0) - it is a flex column with a
351
- gap. Safe to put on `mw-modal-body` or `mw-panel-body`.
352
- 3. **Use `mw-btn-danger` for destructive actions**, never `mw-btn-secondary`. The
353
- secondary colour is a brand decision and turns yellow, teal or anything else
354
- the moment the palette changes.
355
- 4. **One tag or many?** `mw-tag` is a standalone chip and carries its own colour -
356
- right for a table cell. `mw-tags` is a container that colours all its
357
- `mw-tags-item` children - right for a tag list. Mixing them (`mw-tag` inside
358
- `mw-tags`) is wrong.
359
- 5. **A spinner needs a shape class.** `mw-spinner-primary` alone renders nothing;
360
- use `mw-spinner-border`, `mw-spinner-dots` or `mw-spinner-dual-ring`.
361
- 6. **Sticky table headers need a height-limited scroll container** -
362
- `mw-table-responsive` only scrolls horizontally. Use
363
- `mw-table-responsive-scroll` (height via `--mw-table-scroll-height`).
364
- 7. **`--mw-text-muted-color` on a coloured surface is always wrong.** It follows
365
- the theme; buttons, table headers and badges do not. Use
366
- `--mw-*-accent-text-color` there, and `opacity` for disabled states.
367
- `--mw-accent-text-color` is the shared label for all six colours; each one
368
- also has its own `--mw-primary-accent-text-color`,
369
- `--mw-secondary-accent-text-color`, `--mw-success/warning/danger/info-accent-text-color`,
370
- defaulting to the shared token. Override a single one when that colour needs
371
- the opposite label - a neon primary on an otherwise dark palette. The fixed
372
- grey surfaces - tooltip, `mw-btn-mini`, `mw-info-mini`, gallery arrows -
373
- hardcode white and ignore all of them.
374
- 8. **`mw-card` already lifts on hover** and `mw-btn` already has `inline-flex`
375
- plus a gap for icons. Neither has a modifier class for it - adding one from
376
- memory produces markup that does nothing.
377
- 9. **Overriding one root colour is enough.** Hover tones, translucent
378
- backgrounds and borders are derived with `color-mix()` at runtime. Setting
379
- `--mw-primary-color-hover` by hand is usually a sign the base token was not
380
- set.
381
- 10. **Browser floor: `color-mix()` and `oklch(from ...)`** - Chrome 119+,
382
- Safari 16.4+, Firefox 128+. Both carry the derived tones, so older browsers
383
- get no colours at all, not merely worse ones.
384
- 11. **Touch targets grow on their own.** On `pointer: coarse` or below 768px,
385
- `mw-btn` gets a 2.75rem minimum height, `mw-btn-sm`, `mw-input-sm`,
386
- `mw-select-sm` and `mw-textarea-sm` 2.5rem, a tab 2.75rem, a calendar day
387
- 44px, `mw-section-btn` 2.75rem, and list rows / menu items / pager pages /
388
- accordion headers 2.75rem.
389
- `mw-btn-mini` keeps its 18px circle - it sits in tag rows and table cells
390
- where a bigger one would shift the layout - and grows its _hit area_ to 28px
391
- via a pseudo-element, and `mw-kanban-action` does the same - its 32px box
392
- stays and the hit area around it reaches 44px.
393
- `mw-techstack-item-sm` takes a real 2.5rem minimum instead, because half of
394
- those chips carry a `data-tooltip` and that owns the pseudo-element.
395
- Nothing to switch on, and no reason to write the media query again in an
396
- app: your own control gets `@include touch-floor` for a height or
397
- `@include hit-area` where the size is the design.
398
- 12. **`mw-empty-state` has a `-desc`, not a `-text`.** The parts are
399
- `mw-empty-state-icon`, `-title`, `-desc`, plus the size variant
400
- `mw-empty-state-sm`. Invented names fail silently, as always.
401
- 13. **`mw-text-numeric` gives digits, `mw-text-currency` gives a money column.**
402
- The first is only `tabular-nums` - for a clock, a counter, an ID. The second
403
- adds right alignment and `nowrap`. Before 4.0.0 the first did both, which
404
- is why people wrote the declaration out by hand.
405
- 14. **A component host between a container and its children breaks the layout,
406
- silently.** Every `mw-*` flex or grid container styles its _children_ -
407
- `mw-grid-*`, `mw-tags`, `mw-button-bar`, `mw-form-actions`, `mw-modal-footer`,
408
- `mw-toast-stack`. In plain HTML the children are right there; in a SPA a
409
- component host often sits in between and becomes the item instead, so it
410
- shrinks to content width or swallows a whole grid row. Put `mw-d-contents` on
411
- that host - it then generates no box and the children take the item role
412
- back. Angular: `host: { class: 'mw-d-contents' }`. `mw-header` handles this
413
- case on its own, the others do not.
414
- 15. **A badge is not a tag.** `mw-tag` names something - a topic, a state - and
415
- sits in a row of its own kind, with a tinted surface. `mw-badge` carries a
416
- _count_ or a _status_ and usually sits **on** something, filled rather than
417
- tinted. Number on a bell: badge. "Draft" next to a title: tag.
418
- 16. **A dropdown is a `<details>`, not a div.** `<details class="mw-dropdown">`
419
- with a `<summary>` trigger - that is where the keyboard handling, the focus
420
- and the open state come from, and it works without script. The framework JS
421
- only adds Escape and click-outside. Writing your own div-plus-click loses all
422
- of it.
423
- 17. **An open dropdown can be clipped by anything that hides its overflow.**
424
- Where the browser supports anchor positioning the menu is `position: fixed`
425
- and anchored to its trigger, so it escapes the clip on its own. Everywhere
426
- else it is absolutely positioned and cut off. Either way the framework's own
427
- containers (panel, card, tile, modal body, responsive table) lift the clip
428
- while a menu is open. On your own container it is one line:
429
- `:has(.mw-dropdown[open]) { overflow: visible }`.
430
- 18. **Never write `box-shadow` by hand.** Use `var(--mw-elevation-1..5)` or the
431
- `mw-elevation-*` class. A hand-rolled shadow is the wrong colour in one of
432
- the two themes - the dark theme's shadow is a light rim over a dark contact
433
- layer, not a black blur. The one place the ramp is wrong is a shadow that
434
- lands on a picture instead of on a theme surface: its light-theme layers are
435
- cut for paper and vanish over a photograph. That is why the edge after a
436
- parallax block carries fixed values, and why `mw-parallax-dimmed` hardcodes
437
- its brightness and text-shadow.
438
- 19. **Never write a duration or an easing curve by hand** either. Use
439
- `var(--mw-transition)` for a hover or focus state,
440
- `var(--mw-transition-fast)` for something that should feel instant, and
441
- `var(--mw-duration-*)` with `var(--mw-ease-*)` for anything else. That is
442
- also what makes `prefers-reduced-motion` work - it turns the duration tokens
443
- down, so anything built on them is covered for free.
444
- 20. **A hover effect that _moves_ something needs the `hover` mixin.** On touch
445
- `:hover` latches after a tap and stays on, so a lifted card stays lifted,
446
- visibly out of line with its row. `@include hover { transform: ... }` -
447
- colour changes are fine unguarded, movement is not. The framework's own
448
- components already do this.
449
- 21. **Below 576px a modal is a bottom sheet.** Full width, anchored to the bottom
450
- edge, rounded on the top two corners, with a grab handle and full-width
451
- actions in the footer. Nothing to switch on - do not fight it with your own
452
- media query, and do not put a fixed height on `mw-modal`.
453
- 22. **Press states exist on every control**, because a finger never hovers.
454
- `mw-btn` and friends dip 1px and invert their highlight, `mw-btn-mini` and
455
- `mw-modal-close` scale down. If you build your own control, give it an
456
- `:active` - on touch it is the only feedback there is.
457
- 23. **Never set a height on a form control.** `mw-input`, `mw-select`,
458
- `mw-textarea` and `mw-btn` all take their minimum height from the control
459
- scale, so a field and the button next to it line up on their own. Pick the
460
- size step (`-sm` / nothing / `-lg`) and leave the height alone - a hand-set
461
- one puts that control back out of line with everything around it.
462
- 24. **Put the size modifier on the input group, not inside it.**
463
- `mw-input-group-sm` and `-lg` size the prefix, the suffix _and_ the field.
464
- Adding `mw-input-sm` inside as well is redundant, and mixing the two steps
465
- is what makes a group look broken.
466
- 25. **Loops keep their own timing, entrances go on the scale.** A spinner, a
467
- skeleton shimmer and a pulse ring are ambient - running them at 300ms would
468
- be frantic, and they are the one place a hand-written duration is right.
469
- Anything that plays once - a panel appearing, a card sliding in, a drawer -
470
- uses `var(--mw-duration-*)`.
471
- 26. **`transition: all` is out.** `var(--mw-transition)` lists paint-only
472
- properties on purpose. The one exception in the framework is the header
473
- burger, which morphs by animating `top` and `bottom`, and it says so in a
474
- comment. If your component really does need to animate a size, name that
475
- property - do not reach for `all`.
476
- 27. **A control is a `<button>`, never a styled `<div>`.** `mw-tabs-nav-item`,
477
- `mw-theme-toggle`, `mw-gallery-dot`, `mw-accordion-header` and `mw-menu-btn`
478
- are all written for one, and all five shipped as divs and spans - the first
479
- three until 4.11, the accordion header until 4.12, the burger after that -
480
- that no keyboard could reach. Each class clears what a `<button>` brings
481
- with it, so `<button type="button" class="mw-tabs-nav-item" data-tab="...">`
482
- is the whole markup. The burger is the worst of the five to get wrong:
483
- below the collapse breakpoint it is the only route to the navigation.
484
- `mw-tile` is the same rule from the other side: the class paints a surface
485
- on anything, but the hand cursor, the focus ring and the tap handling only
486
- come with `<a class="mw-tile">` or `<button class="mw-tile">`. On a `<div>`
487
- you get the look and nothing a keyboard can reach.
488
- If you build your own clickable thing: the element decides whether anyone
489
- without a mouse can use it, the class only decides how it looks.
490
- 28. **A link in running text is `mw-link`, not `mw-btn mw-btn-link`.** Since
491
- 4.13.0 there is a class for exactly that. `mw-btn` is `inline-flex` with
492
- `min-height: var(--mw-control-height)`, so a link written that way pulls its
493
- own line up to 2.125rem while every line around it keeps the paragraph's
494
- height - in a footer disclaimer at `font-size: sm` that is nearly double,
495
- and it reads as a layout bug. `mw-p-0` is not the fix it looks like: the
496
- height does it, not the padding. `mw-link` is the same look without the
497
- control height and works on an `<a>` and a `<button>` alike.
498
- `mw-btn mw-btn-link` stays right where the link really is one of several
499
- buttons and has to line up with them - a card's actions, a button bar.
500
- 29. **`mw-parallax-sticky` goes on the section, not on the hero.** Sticky pins
501
- inside its parent, and the rule that lifts the page above the pinned picture
502
- reaches that content as a _sibling_. Put the class on the hero itself and
503
- nothing pins and nothing covers - no error, no effect, and the picture shows
504
- through every transparent section below. `mw-parallax` and the
505
- `mw-parallax-media` child stay where the image is: on the container. Same
506
- split for `mw-parallax-slow`, and `--mw-parallax-slow-travel` goes with the
507
- class on the section - on the inner block it never reaches the animation.
508
- That same sibling rule also puts an upward shadow on the one section
509
- directly after the block, so the overlap reads as a surface and not a cut.
510
- It sits at `:where()` weight, so a plain `box-shadow` of your own on that
511
- section replaces it.
512
- 30. **A stored theme needs a script in the `<head>`.** `main.js` runs at the
513
- end of the body, so a reader whose choice differs from the OS sees the
514
- other theme for one frame. Five inline lines in the head apply it before
515
- the first paint - the snippet is in `references/javascript.md`. The theme
516
- classes live on `<html>` for exactly this reason.
517
- 31. **`viewport-fit=cover` belongs in the viewport meta.** The container
518
- gutter, the mobile nav panel and the modal padding all budget for the
519
- cutout with `env(safe-area-inset-*)`, and iOS resolves every one of those
520
- to `0` without it - on a notched phone the content then sits under the
521
- rounded corner. Nothing errors, which is why the shipped JS writes a
522
- console warning on localhost when the meta is missing.
523
- 32. **Half the layout reads its own width, not the viewport.** `mw-card`,
524
- `mw-tile`, `mw-stepper`, both timelines and - since 5.13.0 -
525
- `mw-gallery-container` and `mw-image-slider` are `@container` queries, so
526
- any of them in a half-width column takes its narrow layout there while the
527
- window is still a desktop. Two consequences: the query only reaches
528
- _descendants_, which is why the gallery wrapper around `mw-gallery`,
529
- `mw-gallery-desc` and `mw-gallery-dots` cannot be dropped; and
530
- `container-type: inline-size` computes the width as if the element were
531
- empty, so one dropped into a flex or grid slot needs a width - the
532
- framework sets `width: 100%` on its own containers already.