maverick-wave 5.30.0 → 5.32.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 +2 -2
- package/maverick-wave.min.css +2 -2
- package/package.json +8 -2
- package/src/scss/abstracts/_variables.scss +2 -0
- package/src/scss/components/_modals.scss +23 -9
- package/src/scss/components/_testimonial.scss +2 -0
- package/src/scss/layout/_home.scss +46 -0
- package/src/scss/layout/_main.scss +20 -0
- package/src/scss/layout/_parallax.scss +2 -2
- package/src/scss/main-lean.scss +8 -3
- package/.claude/settings.local.json +0 -43
- package/.claude/skills/mw-maverick-wave/SKILL.md +0 -532
- package/.claude/skills/mw-maverick-wave/examples/angular-form.md +0 -279
- package/.claude/skills/mw-maverick-wave/examples/angular-list-page.md +0 -309
- package/.claude/skills/mw-maverick-wave/examples/angular-services.md +0 -538
- package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +0 -530
- package/.claude/skills/mw-maverick-wave/references/components.md +0 -2170
- package/.claude/skills/mw-maverick-wave/references/forms.md +0 -358
- package/.claude/skills/mw-maverick-wave/references/javascript.md +0 -164
- package/.claude/skills/mw-maverick-wave/references/layout.md +0 -764
- package/.claude/skills/mw-maverick-wave/references/theming.md +0 -530
- package/.github/workflows/ci.yml +0 -41
- package/.idea/codeStyles/Project.xml +0 -59
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/maverick-wave.iml +0 -12
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -6
- package/.idea/vcs.xml +0 -6
- package/.prettierignore +0 -8
- package/.prettierrc.json +0 -7
- package/CHANGELOG.md +0 -956
- package/CLAUDE.md +0 -60
- package/gulpfile.js +0 -167
- package/index.html +0 -1730
- package/release.sh +0 -31
- package/scripts/verify.js +0 -252
- package/src/assets/favicon/apple-touch-icon.png +0 -0
- package/src/assets/favicon/favicon-96x96.png +0 -0
- package/src/assets/favicon/favicon.svg +0 -1
- package/src/assets/favicon/site.webmanifest +0 -37
- package/src/assets/favicon/web-app-manifest-192x192.png +0 -0
- package/src/assets/favicon/web-app-manifest-512x512.png +0 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/header-logo.svg +0 -24
- package/src/assets/images/gallery-city.svg +0 -49
- package/src/assets/images/gallery-desert.svg +0 -25
- package/src/assets/images/gallery-forest.svg +0 -46
- package/src/assets/images/gallery-lake.svg +0 -26
- package/src/assets/images/gallery-mountain.svg +0 -19
- package/src/assets/images/gallery-ocean.svg +0 -26
- package/src/assets/images/gallery-oldtown.svg +0 -41
- package/src/assets/images/gallery-snow.svg +0 -39
- package/src/assets/images/photo-balloon.svg +0 -35
- package/src/assets/images/photo-forest-path.svg +0 -31
- package/src/assets/images/photo-lighthouse.svg +0 -42
- package/src/assets/images/photo-palm.svg +0 -27
- package/src/assets/images/photo-peak.svg +0 -27
- package/src/assets/images/photo-tower.svg +0 -34
- package/src/assets/images/photo-waterfall.svg +0 -32
- package/src/assets/images/slider-field.svg +0 -124
- package/src/assets/images/slider-forest.svg +0 -33
- package/src/assets/images/slider-lake.svg +0 -35
- package/src/assets/images/slider-mountains.svg +0 -29
- package/src/assets/images/slider-trees.svg +0 -41
- package/src/assets/images/story-aurora.svg +0 -36
- package/src/assets/images/story-beach.svg +0 -34
- package/src/assets/images/story-stars.svg +0 -35
- package/src/assets/images/tile-analytics.svg +0 -22
- package/src/assets/images/tile-cloud.svg +0 -19
- package/src/assets/images/tile-ecommerce.svg +0 -14
- package/src/assets/images/tile-enterprise.svg +0 -22
- package/src/assets/images/tile-marketing.svg +0 -16
- package/src/assets/media/demo-chime.wav +0 -0
- package/src/assets/media/story-sunrise.jpg +0 -0
- package/src/assets/media/story-sunrise.mp4 +0 -0
- package/src/partials/accordions-container.html +0 -179
- package/src/partials/alerts-container.html +0 -151
- package/src/partials/announcement-container.html +0 -67
- package/src/partials/avatars-container.html +0 -229
- package/src/partials/badges-container.html +0 -114
- package/src/partials/blog-posts-container.html +0 -158
- package/src/partials/breadcrumbs-container.html +0 -126
- package/src/partials/button-bar-container.html +0 -175
- package/src/partials/buttons-container.html +0 -272
- package/src/partials/calendar-container.html +0 -203
- package/src/partials/cards-container.html +0 -727
- package/src/partials/code-container.html +0 -194
- package/src/partials/colors-container.html +0 -263
- package/src/partials/coming-soon-container.html +0 -10
- package/src/partials/contact-container.html +0 -85
- package/src/partials/content-slider-container.html +0 -36
- package/src/partials/divider-container.html +0 -70
- package/src/partials/documentation-container.html +0 -204
- package/src/partials/dropdown-container.html +0 -119
- package/src/partials/empty-state-container.html +0 -79
- package/src/partials/feed-container.html +0 -506
- package/src/partials/footer-container.html +0 -113
- package/src/partials/form-container.html +0 -262
- package/src/partials/form-elements-container.html +0 -616
- package/src/partials/form-field-container.html +0 -166
- package/src/partials/gallery-container.html +0 -62
- package/src/partials/get-started-container.html +0 -423
- package/src/partials/grid-container.html +0 -191
- package/src/partials/header-container.html +0 -69
- package/src/partials/header-utilities-container.html +0 -331
- package/src/partials/highlights-container.html +0 -71
- package/src/partials/history-container.html +0 -67
- package/src/partials/home-container.html +0 -31
- package/src/partials/html-lists-container.html +0 -63
- package/src/partials/info-container.html +0 -104
- package/src/partials/input-group-container.html +0 -299
- package/src/partials/item-lists-container.html +0 -368
- package/src/partials/kanban-container.html +0 -459
- package/src/partials/kbd-container.html +0 -63
- package/src/partials/leader-row-container.html +0 -139
- package/src/partials/login-container.html +0 -65
- package/src/partials/lunch-menu-container.html +0 -341
- package/src/partials/media-container.html +0 -27
- package/src/partials/meta-info-container.html +0 -21
- package/src/partials/modals-container.html +0 -199
- package/src/partials/mosaic-container.html +0 -173
- package/src/partials/page-header-container.html +0 -78
- package/src/partials/pagination-container.html +0 -64
- package/src/partials/palette-container.html +0 -480
- package/src/partials/panels-container.html +0 -126
- package/src/partials/parallax-container.html +0 -147
- package/src/partials/portrait-gallery-container.html +0 -175
- package/src/partials/preview-container.html +0 -136
- package/src/partials/pricing-container.html +0 -523
- package/src/partials/progress-container.html +0 -230
- package/src/partials/prose-container.html +0 -43
- package/src/partials/ratings-container.html +0 -104
- package/src/partials/section-head-container.html +0 -77
- package/src/partials/segmented-container.html +0 -66
- package/src/partials/skeleton-container.html +0 -87
- package/src/partials/spinners-container.html +0 -96
- package/src/partials/stepper-container.html +0 -111
- package/src/partials/stories-container.html +0 -182
- package/src/partials/tables-container.html +0 -309
- package/src/partials/tabs-container.html +0 -290
- package/src/partials/tags-container.html +0 -242
- package/src/partials/techstack-bucket-container.html +0 -187
- package/src/partials/testimonials-container.html +0 -88
- package/src/partials/tiles-container.html +0 -247
- package/src/partials/timelines-container.html +0 -196
- package/src/partials/typography-container.html +0 -74
- package/src/partials/utilities-container.html +0 -553
|
@@ -1,2170 +0,0 @@
|
|
|
1
|
-
# Components
|
|
2
|
-
|
|
3
|
-
Colour variants are always one of `primary`, `secondary`, `success`, `warning`,
|
|
4
|
-
`danger`, `info` - but not every component offers all six. The lists below are
|
|
5
|
-
exhaustive: what is not named does not exist.
|
|
6
|
-
|
|
7
|
-
## Buttons
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<button type="button" class="mw-btn mw-btn-primary">Save</button>
|
|
11
|
-
<button type="button" class="mw-btn mw-btn-primary">
|
|
12
|
-
<i class="fas fa-save"></i> Save
|
|
13
|
-
</button>
|
|
14
|
-
<a href="#" class="mw-btn mw-btn-outline">Link that looks like a button</a>
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
- Variants: `mw-btn-primary`, `mw-btn-secondary`, `mw-btn-danger`,
|
|
18
|
-
`mw-btn-success`, `mw-btn-outline`, `mw-btn-ghost`, `mw-btn-ghost-danger`,
|
|
19
|
-
`mw-btn-link`, `mw-btn-link-muted`
|
|
20
|
-
- Shapes: `mw-btn-icon` (square, for an icon-only button), `mw-btn-block`
|
|
21
|
-
(full width)
|
|
22
|
-
- Sizes: `mw-btn-sm`, `mw-btn-lg`
|
|
23
|
-
- `mw-btn` is `inline-flex` with a gap - icons need no wrapper and no extra class
|
|
24
|
-
- `disabled` gets `opacity: .6` and `not-allowed`; there is no disabled class
|
|
25
|
-
- `mw-active` gives the pressed/selected look on the coloured variants
|
|
26
|
-
- Every variant dips 1px on `:active`, and the solid ones invert their top
|
|
27
|
-
highlight into an inner shadow. On touch that press is the only feedback there
|
|
28
|
-
is, so do not override it away
|
|
29
|
-
- **A link inside a sentence is `mw-link`, not `mw-btn mw-btn-link`.** The base
|
|
30
|
-
class carries a control height, so in running text it lifts that one line to
|
|
31
|
-
2.125rem while the lines around it stay where they are. `mw-p-0` does not help
|
|
32
|
-
- the height does it, not the padding. Keep `mw-btn` only where the link
|
|
33
|
-
really is a button in a row of buttons and has to line up with them
|
|
34
|
-
- On a coarse pointer or below 768px `mw-btn` grows to a 2.75rem minimum height
|
|
35
|
-
and `mw-btn-sm` to 2.5rem, on their own
|
|
36
|
-
|
|
37
|
-
```html
|
|
38
|
-
<!-- a button that happens to look like a link -->
|
|
39
|
-
<button type="button" class="mw-btn mw-btn-link">Read Article</button>
|
|
40
|
-
|
|
41
|
-
<!-- a link inside a sentence -->
|
|
42
|
-
<p>Built with <a href="https://astro.build" class="mw-link">Astro</a>.</p>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Text links
|
|
46
|
-
|
|
47
|
-
`mw-link` and `mw-link-muted` are the inline links, added in 4.13.0. Same look
|
|
48
|
-
as the two button variants - no box, underline on hover - but without
|
|
49
|
-
`inline-flex` and without the control height, which is what makes them sit on
|
|
50
|
-
the line instead of above it. `font` and the focus ring are set explicitly, so
|
|
51
|
-
a `<button>` in the middle of a paragraph works as well as an `<a>`.
|
|
52
|
-
|
|
53
|
-
```html
|
|
54
|
-
<p>
|
|
55
|
-
Built with <a href="https://astro.build" class="mw-link">Astro</a> and written
|
|
56
|
-
up in the
|
|
57
|
-
<a href="#get-started" class="mw-link-muted">Get Started</a> section.
|
|
58
|
-
</p>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Three emphasis levels
|
|
62
|
-
|
|
63
|
-
`mw-btn-primary` (or any solid colour) **fills**, `mw-btn-outline` **draws a
|
|
64
|
-
line**, `mw-btn-ghost` does **neither** until you point at it. Ghost is what a
|
|
65
|
-
toolbar, a card's own actions or a row of icon buttons wants - five outlined
|
|
66
|
-
boxes in a row fight the content they sit on.
|
|
67
|
-
|
|
68
|
-
```html
|
|
69
|
-
<button type="button" class="mw-btn mw-btn-ghost">Rename</button>
|
|
70
|
-
<button type="button" class="mw-btn mw-btn-ghost mw-btn-ghost-danger">
|
|
71
|
-
Remove
|
|
72
|
-
</button>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Ghost is not `mw-btn-plain`: it keeps the button's shape, padding and hit area
|
|
76
|
-
and only leaves them unpainted at rest. `mw-btn-plain` has no look at all.
|
|
77
|
-
|
|
78
|
-
### Icon-only buttons
|
|
79
|
-
|
|
80
|
-
```html
|
|
81
|
-
<button
|
|
82
|
-
type="button"
|
|
83
|
-
class="mw-btn mw-btn-ghost mw-btn-icon"
|
|
84
|
-
aria-label="Duplicate"
|
|
85
|
-
>
|
|
86
|
-
<i class="fas fa-copy"></i>
|
|
87
|
-
</button>
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
`mw-btn-icon` makes the button square and follows the size modifier next to it
|
|
91
|
-
(`mw-btn-sm` / `mw-btn-lg`), growing to 2.75rem on a coarse pointer. It always
|
|
92
|
-
needs an `aria-label` - there is no text to read out. Combine it with any
|
|
93
|
-
variant; ghost is usually the right one.
|
|
94
|
-
|
|
95
|
-
### `mw-btn-plain` - a button with no button in it
|
|
96
|
-
|
|
97
|
-
For everywhere a `<button>` is the right element and a button is the wrong look:
|
|
98
|
-
a calendar day, a time in a request row, a settings tab, a modal backdrop.
|
|
99
|
-
|
|
100
|
-
```html
|
|
101
|
-
<button type="button" class="mw-btn-plain">14:30</button>
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
It clears `appearance`, background, border, padding and margin, inherits font
|
|
105
|
-
and colour, and keeps exactly two things: the pointer and the focus ring. Used
|
|
106
|
-
on its own - `mw-btn` is neither needed nor wanted next to it.
|
|
107
|
-
|
|
108
|
-
Not the same as `mw-btn-link`, which is a button dressed as a link and brings
|
|
109
|
-
its own colour and control height, nor as `mw-link`, which is the link inside a
|
|
110
|
-
sentence.
|
|
111
|
-
|
|
112
|
-
**Destructive actions use `mw-btn-danger`.** `mw-btn-secondary` is a brand
|
|
113
|
-
colour, not a semantic one.
|
|
114
|
-
|
|
115
|
-
### Mini buttons
|
|
116
|
-
|
|
117
|
-
Round 18px icon buttons - remove a row, close an alert, drop a tag.
|
|
118
|
-
|
|
119
|
-
```html
|
|
120
|
-
<button type="button" class="mw-btn-mini mw-btn-mini-danger">
|
|
121
|
-
<i class="fas fa-times"></i>
|
|
122
|
-
</button>
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Variants: `mw-btn-mini-primary`, `-secondary`, `-danger`, `-success`. Plain
|
|
126
|
-
`mw-btn-mini` is the neutral one. Works on `<a>` as well.
|
|
127
|
-
|
|
128
|
-
### Button bar
|
|
129
|
-
|
|
130
|
-
Row of equally treated buttons, centered by default.
|
|
131
|
-
|
|
132
|
-
```html
|
|
133
|
-
<div class="mw-button-bar mw-button-bar-primary">
|
|
134
|
-
<button class="mw-btn">Previous</button>
|
|
135
|
-
<button class="mw-btn">Overview</button>
|
|
136
|
-
<button class="mw-btn">Next</button>
|
|
137
|
-
</div>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
- Colouring: `mw-button-bar-primary`, `-secondary`, `-outline` style the bare
|
|
141
|
-
`mw-btn` children, so the buttons carry no variant class themselves.
|
|
142
|
-
`mw-button-bar-nav` mixes them for prev/center/next navigation.
|
|
143
|
-
- Alignment: `mw-button-bar-left`, `-right`, `-center` (which is also the
|
|
144
|
-
default), `mw-button-bar-between` pushes first and last apart.
|
|
145
|
-
- Sizes: `mw-button-bar-sm`, `mw-button-bar-lg`.
|
|
146
|
-
- Below 576px it stacks and gives every button the full width. Right for a row of
|
|
147
|
-
independent actions - wrong for a switch, see below.
|
|
148
|
-
|
|
149
|
-
### `mw-actions-note` - one line above a row of actions
|
|
150
|
-
|
|
151
|
-
```html
|
|
152
|
-
<div class="mw-modal-footer">
|
|
153
|
-
<p class="mw-actions-note">This action cannot be undone.</p>
|
|
154
|
-
<button class="mw-btn mw-btn-outline">Cancel</button>
|
|
155
|
-
<button class="mw-btn mw-btn-danger">Delete</button>
|
|
156
|
-
</div>
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Right-aligned, muted, one size smaller. For what the buttons apply to, why one
|
|
160
|
-
of them is disabled, or that something cannot be taken back.
|
|
161
|
-
|
|
162
|
-
- Works in `mw-modal-footer`, `mw-card-footer`, `mw-form-actions` and
|
|
163
|
-
`mw-panel-footer`. It is a child of the action row, not a wrapper around it:
|
|
164
|
-
the line takes the full width and pushes the buttons onto the row below.
|
|
165
|
-
- Only rows that actually carry a note start wrapping, so adding one changes
|
|
166
|
-
nothing anywhere else.
|
|
167
|
-
- Inside `mw-form-actions` the alignment variants steer it as well -
|
|
168
|
-
`mw-form-actions-left` makes the note left-aligned too.
|
|
169
|
-
- `mw-form-actions-hint` is the old, form-only name for the same thing. Still
|
|
170
|
-
styled, but use `mw-actions-note`: the hint name is wrong the moment the line
|
|
171
|
-
sits in a modal.
|
|
172
|
-
|
|
173
|
-
### Segmented control
|
|
174
|
-
|
|
175
|
-
Two or three positions of **one** switch: income/expense, offer/quote, a period
|
|
176
|
-
picker.
|
|
177
|
-
|
|
178
|
-
```html
|
|
179
|
-
<div class="mw-segmented">
|
|
180
|
-
<button type="button" class="mw-segmented-item mw-active">Income</button>
|
|
181
|
-
<button type="button" class="mw-segmented-item">Expense</button>
|
|
182
|
-
</div>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
- Stays horizontal at every width and splits the row into equal shares - which
|
|
186
|
-
is the whole point. Stacked in a `mw-button-bar`, two positions of a switch
|
|
187
|
-
look like two buttons you could press both of.
|
|
188
|
-
- Active position: `mw-active` on the item.
|
|
189
|
-
- `mw-segmented-secondary` switches the active fill to the secondary colour,
|
|
190
|
-
`mw-segmented-auto` shrinks the control to its content instead of filling the
|
|
191
|
-
row - capped at `100%`, so labels wider than a 320px screen truncate instead
|
|
192
|
-
of pushing the row off it. `disabled` works on an item.
|
|
193
|
-
- Items reach a 2.5rem minimum height on a coarse pointer.
|
|
194
|
-
|
|
195
|
-
## Cards
|
|
196
|
-
|
|
197
|
-
```html
|
|
198
|
-
<div class="mw-card">
|
|
199
|
-
<div class="mw-card-img">
|
|
200
|
-
<img src="cover.jpg" alt="" />
|
|
201
|
-
</div>
|
|
202
|
-
<div class="mw-card-body">
|
|
203
|
-
<h3 class="mw-card-title">Title</h3>
|
|
204
|
-
<hr class="mw-card-title-divider" />
|
|
205
|
-
<p class="mw-card-subtitle">Subtitle in muted italics</p>
|
|
206
|
-
<p class="mw-card-text">Body copy.</p>
|
|
207
|
-
<div class="mw-card-footer">
|
|
208
|
-
<button class="mw-btn mw-btn-primary">Open</button>
|
|
209
|
-
<span class="mw-text-muted">12 items</span>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
- `mw-card` is the shell; the hover lift is built in and has no modifier class.
|
|
216
|
-
It is a flex column by default, so `mw-card-footer` sticks to the bottom and
|
|
217
|
-
cards in a grid line up without any extra class.
|
|
218
|
-
- `mw-card-simple` is the flat variant with even padding for arbitrary content.
|
|
219
|
-
Use it when you only need a padded surface - it stays a block container, so
|
|
220
|
-
inline children keep flowing side by side.
|
|
221
|
-
- Image height: 210px, `mw-card-lg` 340px, `mw-card-xl` 480px (260px/340px below
|
|
222
|
-
`sm`). Set `--mw-card-img-height` on the card for any other height.
|
|
223
|
-
`mw-card-img-contain` shows the whole image instead of cropping it.
|
|
224
|
-
- `mw-card-footer` pushes its last child to the right - with a single child that
|
|
225
|
-
means it sits right, not left. Everything is vertically centered, so plain text
|
|
226
|
-
lines up with a button next to it. Actions that no longer fit next to each
|
|
227
|
-
other wrap.
|
|
228
|
-
- The card measures **itself**, not the window - a card in a three-column grid
|
|
229
|
-
is just as narrow on a 1200px desktop as it is on a phone, and looks the same
|
|
230
|
-
in both. Every card declares `container: mw-card / inline-size` and takes
|
|
231
|
-
`width: 100%`, so it fills its slot instead of sizing to its content. Give a
|
|
232
|
-
card an explicit width if you need it to hug its content.
|
|
233
|
-
- What steps on the card's own width: the footer stacks to full-width children
|
|
234
|
-
below 360px, and the title, the subtitle and the body padding step up at
|
|
235
|
-
420px. The image height is the one that still follows the window, because it
|
|
236
|
-
is declared on the card itself and a container cannot query itself.
|
|
237
|
-
- `mw-tile` works the same way, and so do `mw-stepper`, both timelines, the
|
|
238
|
-
gallery wrapper and the image slider: the step labels shrink and the two-sided
|
|
239
|
-
timeline collapses to one side once the component is narrow, whatever the
|
|
240
|
-
window does. One catch on tiles: a tile
|
|
241
|
-
clips its overflow, and being a container makes it the containing block for
|
|
242
|
-
`position: fixed`, so a `data-tooltip` **inside** a tile is cut off at its
|
|
243
|
-
edge. Cards do not clip and are unaffected.
|
|
244
|
-
- `mw-card-badge` (top right corner) and `mw-card-ribbon` (diagonal banner) are
|
|
245
|
-
absolutely positioned overlays; colour them with `mw-card-addon-primary`,
|
|
246
|
-
`-secondary`, `-success`, `-warning`, `-danger`, `-info`.
|
|
247
|
-
- A card does not clip its content, so a tooltip or dropdown inside it can reach
|
|
248
|
-
outside. The exception is `mw-card-ribbon`: that banner has to be cut off at
|
|
249
|
-
the edge, so a card with one as its **direct child** switches to
|
|
250
|
-
`overflow: hidden` and clips everything else too. A ribbon deeper inside - on
|
|
251
|
-
a card nested in another card - does not make the outer one clip.
|
|
252
|
-
|
|
253
|
-
```html
|
|
254
|
-
<div class="mw-card">
|
|
255
|
-
<div class="mw-card-badge mw-card-addon-success">New</div>
|
|
256
|
-
...
|
|
257
|
-
</div>
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
**Feature frame** - wraps a card to lift one option out of a grid of equals,
|
|
261
|
-
for the "most booked" plan in a pricing row:
|
|
262
|
-
|
|
263
|
-
```html
|
|
264
|
-
<div class="mw-card-feature mw-card-feature-secondary">
|
|
265
|
-
<p class="mw-card-feature-label">Most booked</p>
|
|
266
|
-
<div class="mw-card">
|
|
267
|
-
<div class="mw-card-body">...</div>
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
- Unlike badge and ribbon this is a **wrapper around** the card, not an overlay
|
|
273
|
-
inside it. The card itself stays untouched - same width, same height, same
|
|
274
|
-
hover as its neighbours - and the frame plus its label bar grow outwards into
|
|
275
|
-
the grid gap.
|
|
276
|
-
- Colours are `mw-card-feature-secondary` and `mw-card-feature-info`; primary is
|
|
277
|
-
the default and needs no class. This is a separate set from the
|
|
278
|
-
`mw-card-addon-*` classes that colour badge and ribbon - the frame only comes
|
|
279
|
-
in those three.
|
|
280
|
-
- The label is a single line. It is set in uppercase at `2xs` and truncates with
|
|
281
|
-
an ellipsis rather than wrapping, because the 22px bar height is baked into
|
|
282
|
-
the frame's negative margins.
|
|
283
|
-
- The frame reaches 29px above the card and 7px below it, so it sticks out of
|
|
284
|
-
its grid cell. Any `mw-grid-*` that directly contains one therefore gets
|
|
285
|
-
`row-gap: 56px` automatically, which leaves 20px between two stacked frames.
|
|
286
|
-
CSS has no per-row gap, so this applies to every row of that grid - expect
|
|
287
|
-
wrapped rows without a frame to sit further apart than usual. That is also why
|
|
288
|
-
the bar is kept low: each extra pixel is charged to every row.
|
|
289
|
-
- The space **above the first row** is not covered - the frame reaches into
|
|
290
|
-
whatever sits there. Give the container the room (`mw-mt-10` on the grid, or
|
|
291
|
-
`mw-mb-10` on the heading above it).
|
|
292
|
-
- In a layout that is not a `mw-grid-*` - your own flex column, an Angular host
|
|
293
|
-
with its own grid - nothing raises the gap for you. Leave at least 36px
|
|
294
|
-
between stacked items yourself.
|
|
295
|
-
|
|
296
|
-
**Stack card** - icon + title header with a gradient rule, for feature or
|
|
297
|
-
tech-stack grids:
|
|
298
|
-
|
|
299
|
-
```html
|
|
300
|
-
<div class="mw-card mw-card-stack">
|
|
301
|
-
<div class="mw-card-stack-header">
|
|
302
|
-
<div class="mw-card-stack-icon"><i class="fas fa-server"></i></div>
|
|
303
|
-
<h5 class="mw-card-stack-title">Backend</h5>
|
|
304
|
-
</div>
|
|
305
|
-
<div class="mw-card-stack-body">
|
|
306
|
-
<div class="mw-tags"><span class="mw-tags-item">Kotlin</span></div>
|
|
307
|
-
<p class="mw-card-stack-text">Description.</p>
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
## Pricing
|
|
313
|
-
|
|
314
|
-
Two pieces that are almost always needed together: `mw-price`, the figure on its
|
|
315
|
-
own, and `mw-offer`, the card it is sold from.
|
|
316
|
-
|
|
317
|
-
### Price
|
|
318
|
-
|
|
319
|
-
One baseline row - what it cost before, what it costs now, the unit, a discount
|
|
320
|
-
tag:
|
|
321
|
-
|
|
322
|
-
```html
|
|
323
|
-
<p class="mw-price">
|
|
324
|
-
<span class="mw-price-original">
|
|
325
|
-
<span class="mw-sr-only">Regular price:</span>159 Euro
|
|
326
|
-
</span>
|
|
327
|
-
<span class="mw-sr-only">Now:</span>
|
|
328
|
-
<span class="mw-price-amount"
|
|
329
|
-
>129<span class="mw-price-fraction">,90</span></span
|
|
330
|
-
>
|
|
331
|
-
<span class="mw-price-currency">Euro</span>
|
|
332
|
-
<span class="mw-price-period">/ month</span>
|
|
333
|
-
<span class="mw-tag mw-tag-secondary">-20%</span>
|
|
334
|
-
</p>
|
|
335
|
-
<p class="mw-price-note">Billed yearly, cancel any time.</p>
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
- Everything in the row is sized in `em` off `--mw-price-size`, so
|
|
339
|
-
`mw-price-xs` (1.3rem), `mw-price-sm` (1.5rem) and `mw-price-lg` (3rem) move
|
|
340
|
-
the whole row at once - cents, unit and struck original included. Set the
|
|
341
|
-
token yourself for any other size.
|
|
342
|
-
- `mw-price-original` is muted **and** struck, and it takes its own line
|
|
343
|
-
**above** the new price. Muted alone reads as a footnote; the line is what
|
|
344
|
-
says this is no longer the price. The rule is drawn explicitly, because the
|
|
345
|
-
browser default hairline disappears at this size against a muted tone.
|
|
346
|
-
- Stacked is the default because inline the row runs long - "199 Euro 159 Euro
|
|
347
|
-
-20 %" - and the first thing to wrap away when the width gives out is the
|
|
348
|
-
discount tag, the one part that was there to catch the eye. `mw-price-inline`
|
|
349
|
-
puts it back on one line for a wide card or a table row.
|
|
350
|
-
- `mw-price-fraction` goes **inside** the amount and sits one step down on the
|
|
351
|
-
baseline, not raised to the cap height - raised cents are a discounter's idiom
|
|
352
|
-
and make a service price read cheaper than it is.
|
|
353
|
-
- `mw-price-currency` and `mw-price-period` stop shrinking at 0.8rem, and
|
|
354
|
-
`mw-price-original` at 0.9rem. At `mw-price-xs` the `em` chain would put them
|
|
355
|
-
under 10px, which is where the floor earns its keep.
|
|
356
|
-
- `mw-price-word` for "On request" or "Free" - bold, so it holds the same slot in
|
|
357
|
-
a row of cards, but set well below a figure. Its line box is scaled back up to
|
|
358
|
-
the amount's, so a word and a number in neighbouring cards land on one line.
|
|
359
|
-
- `mw-price-note` is a **sibling** of the row, not a child - inside it, it would
|
|
360
|
-
inherit the price size and join the baseline.
|
|
361
|
-
- A `mw-tag` or `mw-badge` placed directly in the row is centred against the
|
|
362
|
-
digits rather than hung off their baseline.
|
|
363
|
-
|
|
364
|
-
| Class | What it does |
|
|
365
|
-
| ------------------- | ---------------------------------------------- |
|
|
366
|
-
| `mw-price-xs/sm/lg` | Retunes `--mw-price-size`, everything follows |
|
|
367
|
-
| `mw-price-inline` | Old price in front of the new one, on one line |
|
|
368
|
-
| `mw-price-center` | Centres the row |
|
|
369
|
-
| `mw-price-plain` | Body colour instead of the brand |
|
|
370
|
-
|
|
371
|
-
A struck-through price is a visual convention a screen reader does not pass on -
|
|
372
|
-
`<s>` is announced by almost none of them. Wherever an old and a new price stand
|
|
373
|
-
next to each other, name them with `mw-sr-only`, as above. Without it the reader
|
|
374
|
-
hears two prices and no way to tell which one is charged.
|
|
375
|
-
|
|
376
|
-
### Offer card
|
|
377
|
-
|
|
378
|
-
`mw-offer` goes on a `mw-card` and does the one thing the card cannot: it pushes
|
|
379
|
-
the price to the bottom of the body, so the prices in a row line up even though
|
|
380
|
-
the feature lists have different lengths.
|
|
381
|
-
|
|
382
|
-
```html
|
|
383
|
-
<article class="mw-card mw-offer">
|
|
384
|
-
<div class="mw-card-body">
|
|
385
|
-
<h3 class="mw-card-title">Studio</h3>
|
|
386
|
-
<hr class="mw-card-title-divider" />
|
|
387
|
-
<p class="mw-card-text">Description.</p>
|
|
388
|
-
<ul class="mw-list mw-list-check">
|
|
389
|
-
<li>10 projects</li>
|
|
390
|
-
</ul>
|
|
391
|
-
<p class="mw-price mw-price-sm">…</p>
|
|
392
|
-
<p class="mw-price-note">Per month, billed yearly.</p>
|
|
393
|
-
<div class="mw-card-footer">
|
|
394
|
-
<button class="mw-btn mw-btn-primary">Choose</button>
|
|
395
|
-
</div>
|
|
396
|
-
</div>
|
|
397
|
-
</article>
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
- Bottom-aligned, note included: a card that carries a `mw-price-note` and one
|
|
401
|
-
that does not will **not** have their prices on the same line. Give every card
|
|
402
|
-
in a row a note, or none of them.
|
|
403
|
-
- To lift one plan out of the row, wrap it in `mw-card-feature` - the frame is
|
|
404
|
-
the card's, not the offer's.
|
|
405
|
-
- A price dropped into a `mw-card-footer` instead needs no modifier: it lines up
|
|
406
|
-
with the button beside it on its own, and `mw-price-sm` keeps it from
|
|
407
|
-
outweighing the action. That is the layout for a dated event, where the price
|
|
408
|
-
is a detail rather than the offer.
|
|
409
|
-
- Such a footer keeps its row once the card gets narrow, and gives the action
|
|
410
|
-
half of it. A footer normally stacks its children to full width down there, so
|
|
411
|
-
that two buttons do not squeeze - but a price is a label, not a second action,
|
|
412
|
-
and a full-bleed button under a left-aligned price reads as a banner rather
|
|
413
|
-
than as the end of a card.
|
|
414
|
-
|
|
415
|
-
**Head band** - name and price in a solid bar at the top, for when the price is
|
|
416
|
-
the thing being compared and the feature list is only its justification:
|
|
417
|
-
|
|
418
|
-
```html
|
|
419
|
-
<article class="mw-card mw-offer mw-card-addon-secondary">
|
|
420
|
-
<div class="mw-offer-head">
|
|
421
|
-
<p class="mw-offer-name">Studio</p>
|
|
422
|
-
<p class="mw-price mw-price-sm">…</p>
|
|
423
|
-
</div>
|
|
424
|
-
<div class="mw-card-body">…</div>
|
|
425
|
-
</article>
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
The band reads `mw-card-addon-*` for its colour - the same set the card badge and
|
|
429
|
-
the ribbon use, primary without one. The price inherits the band's ink, and the
|
|
430
|
-
card drops its top corner arc, which the band already owns.
|
|
431
|
-
|
|
432
|
-
### Billing cycle
|
|
433
|
-
|
|
434
|
-
A subscription sold on several terms, where the longer one is cheaper. The
|
|
435
|
-
switch is a `mw-segmented` **above** the grid, not one per card - three cards
|
|
436
|
-
with three switches are three states the visitor has to keep in their head.
|
|
437
|
-
|
|
438
|
-
```html
|
|
439
|
-
<div
|
|
440
|
-
class="mw-segmented mw-segmented-auto"
|
|
441
|
-
role="group"
|
|
442
|
-
aria-label="Billing cycle"
|
|
443
|
-
>
|
|
444
|
-
<button type="button" class="mw-segmented-item mw-active" aria-pressed="true">
|
|
445
|
-
Monthly
|
|
446
|
-
</button>
|
|
447
|
-
<button type="button" class="mw-segmented-item" aria-pressed="false">
|
|
448
|
-
6 months <span class="mw-tag mw-tag-secondary">-10%</span>
|
|
449
|
-
</button>
|
|
450
|
-
<button type="button" class="mw-segmented-item" aria-pressed="false">
|
|
451
|
-
Yearly <span class="mw-tag mw-tag-secondary">-20%</span>
|
|
452
|
-
</button>
|
|
453
|
-
</div>
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
- The headline figure stays **per month** on every term. "288 Euro" next to
|
|
457
|
-
"30 Euro" is not a comparison a reader can make in their head; the amount that
|
|
458
|
-
actually leaves the account belongs in the `mw-price-note` below
|
|
459
|
-
("288 Euro charged once for the year, cancel monthly").
|
|
460
|
-
- The full monthly rate goes in `mw-price-original`, the saving in a `mw-tag`
|
|
461
|
-
next to it. Both carry `hidden` on the monthly term - the framework resets
|
|
462
|
-
`[hidden]` to `display: none !important`, so it beats a component's own
|
|
463
|
-
`display` and an `el.hidden = true` or an Angular `[hidden]` binding works on
|
|
464
|
-
a `mw-tag` too.
|
|
465
|
-
- Wrap the price in `aria-live="polite"` and put `aria-pressed` on the switch
|
|
466
|
-
items. The figure changes without the page moving, which a screen reader
|
|
467
|
-
otherwise never hears.
|
|
468
|
-
- Drop trailing zero cents: "24 Euro", not "24,00 Euro". `mw-price-amount` sets
|
|
469
|
-
tabular figures, so the row does not jump when the term changes.
|
|
470
|
-
- A tag inside the **active** item drops its colour and outlines itself in the
|
|
471
|
-
item's ink - the colour variants mix their background from the page, and an
|
|
472
|
-
orange pill on the blue fill comes out purple. Inactive items keep the colour,
|
|
473
|
-
which is what makes a visitor click them.
|
|
474
|
-
|
|
475
|
-
The switching itself is the application's job - the framework ships the look,
|
|
476
|
-
not the arithmetic. The showcase wires it in a few lines at the bottom of
|
|
477
|
-
`index.html`.
|
|
478
|
-
|
|
479
|
-
## Panels
|
|
480
|
-
|
|
481
|
-
A bordered box with a header rule - the "titled section" of an application.
|
|
482
|
-
|
|
483
|
-
```html
|
|
484
|
-
<div class="mw-panel mw-panel-primary">
|
|
485
|
-
<h4 class="mw-panel-header">Filters</h4>
|
|
486
|
-
<div class="mw-panel-body">...</div>
|
|
487
|
-
<div class="mw-panel-footer">
|
|
488
|
-
<button class="mw-btn mw-btn-outline">Reset</button>
|
|
489
|
-
<button class="mw-btn mw-btn-primary">Apply</button>
|
|
490
|
-
</div>
|
|
491
|
-
</div>
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
- Header colour: `mw-panel-primary`, `mw-panel-secondary`; without them the
|
|
495
|
-
header is neutral. Title and action sit at opposite ends and wrap to two rows
|
|
496
|
-
when the panel gets too narrow for both.
|
|
497
|
-
- Height: `mw-panel-scrollable` (500px), `mw-panel-max-height-sm` (300px),
|
|
498
|
-
`-md` (500px), `-lg` (700px) - the body scrolls, header and footer stay.
|
|
499
|
-
- Panels in a grid row end up equally tall on their own - a grid item stretches.
|
|
500
|
-
A height variant opts out with `align-self: start`.
|
|
501
|
-
|
|
502
|
-
## Tiles
|
|
503
|
-
|
|
504
|
-
Compact info blocks. The look is on the class, the click is on the element: put
|
|
505
|
-
`mw-tile` on an `<a>` or a `<button>` and it gets the hand cursor, the focus ring
|
|
506
|
-
and the tap handling. On a `<div>` it stays a plain surface - no cursor, no ring,
|
|
507
|
-
because there would be nothing to reach by keyboard.
|
|
508
|
-
|
|
509
|
-
```html
|
|
510
|
-
<div class="mw-tiles mw-tiles-2col">
|
|
511
|
-
<a href="/billing" class="mw-tile">
|
|
512
|
-
<div class="mw-tile-img"><img src="icon.png" alt="" /></div>
|
|
513
|
-
<h3 class="mw-tile-header">Billing</h3>
|
|
514
|
-
<div class="mw-tile-body">
|
|
515
|
-
<p>Invoices and payments</p>
|
|
516
|
-
</div>
|
|
517
|
-
<div class="mw-tile-footer">Updated 2026-01-02</div>
|
|
518
|
-
</a>
|
|
519
|
-
</div>
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
Container: `mw-tiles` (1 / 2 at `sm` / 3 at `md`), `mw-tiles-2col` (1 / 2 at
|
|
523
|
-
`sm`), `mw-tiles-4col` (1 / 2 at `sm` / 4 at `lg`) - the same ramps `mw-grid-3`,
|
|
524
|
-
`-2` and `-4` use, and the full container width like any other grid.
|
|
525
|
-
Tile sizes: `mw-tile-sm`, `mw-tile-lg`.
|
|
526
|
-
|
|
527
|
-
## Accordion
|
|
528
|
-
|
|
529
|
-
```html
|
|
530
|
-
<div class="mw-accordion">
|
|
531
|
-
<div class="mw-accordion-item">
|
|
532
|
-
<button
|
|
533
|
-
type="button"
|
|
534
|
-
class="mw-accordion-header mw-active"
|
|
535
|
-
aria-expanded="true"
|
|
536
|
-
aria-controls="faq-1"
|
|
537
|
-
>
|
|
538
|
-
<span>Question</span>
|
|
539
|
-
<i class="fas fa-chevron-down mw-accordion-icon"></i>
|
|
540
|
-
</button>
|
|
541
|
-
<div class="mw-accordion-content mw-active" id="faq-1">
|
|
542
|
-
<div class="mw-accordion-content-inner">Answer</div>
|
|
543
|
-
</div>
|
|
544
|
-
</div>
|
|
545
|
-
</div>
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
The header is a `<button>` - it is the control that opens the panel, and a
|
|
549
|
-
`<div>` is not focusable. A heading is not allowed inside a button, so the
|
|
550
|
-
question is a `<span>` and the header carries the type itself; a nested `h3`
|
|
551
|
-
still renders the same, for markup written before 4.12.
|
|
552
|
-
|
|
553
|
-
Open state = `mw-active` on header **and** content (bare `active` still works
|
|
554
|
-
but is deprecated). The icon rotates via the **header** state - putting the
|
|
555
|
-
class on the icon instead does nothing. Content taller than 500px scrolls.
|
|
556
|
-
The `mw-accordion-content-inner` wrapper is required and not decoration: the
|
|
557
|
-
panel opens by animating a grid row and that wrapper is the row, so content
|
|
558
|
-
placed straight into `mw-accordion-content` never collapses when closed.
|
|
559
|
-
Toggling is JS - see `references/javascript.md`; the shipped script keeps
|
|
560
|
-
`aria-expanded` in step when the header is a button.
|
|
561
|
-
|
|
562
|
-
## Tabs
|
|
563
|
-
|
|
564
|
-
```html
|
|
565
|
-
<div class="mw-tabs">
|
|
566
|
-
<div class="mw-tabs-nav">
|
|
567
|
-
<button type="button" class="mw-tabs-nav-item mw-active" data-tab="tab1">
|
|
568
|
-
Details
|
|
569
|
-
</button>
|
|
570
|
-
<button type="button" class="mw-tabs-nav-item" data-tab="tab2">
|
|
571
|
-
History
|
|
572
|
-
</button>
|
|
573
|
-
</div>
|
|
574
|
-
<div class="mw-tabs-content">
|
|
575
|
-
<div class="mw-tabs-panel mw-active" id="tab1">...</div>
|
|
576
|
-
<div class="mw-tabs-panel" id="tab2">...</div>
|
|
577
|
-
</div>
|
|
578
|
-
</div>
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
- Variants: `mw-tabs-vertical` (nav on the left, horizontal again below `sm`),
|
|
582
|
-
`mw-tabs-pills`.
|
|
583
|
-
- `data-tab` matches the panel `id` - that pairing is only needed for the
|
|
584
|
-
shipped JS. In a SPA, bind `mw-active` yourself and drop the attribute.
|
|
585
|
-
- The shipped JS turns the strip into a real tablist: `role`, `aria-selected`,
|
|
586
|
-
`aria-controls`, `aria-labelledby`, a roving `tabindex` and arrow/Home/End
|
|
587
|
-
keys. Without it, that is yours to add - see `references/javascript.md`.
|
|
588
|
-
- The nav scrolls horizontally instead of wrapping, and says so: a shadow shows
|
|
589
|
-
on whichever side still has tabs behind it and disappears once that end is
|
|
590
|
-
reached. Pure CSS, no scroll listener. The fade colour comes from
|
|
591
|
-
`--mw-scroll-hint-cover`, which is preset to the page background and
|
|
592
|
-
re-pointed to the card background inside `mw-card`, `mw-panel`, `mw-modal`,
|
|
593
|
-
`mw-tile` and `mw-calendar` - override it if the strip sits on some other
|
|
594
|
-
surface.
|
|
595
|
-
- Tab items reach a 2.75rem minimum height on a coarse pointer.
|
|
596
|
-
|
|
597
|
-
## Modal
|
|
598
|
-
|
|
599
|
-
```html
|
|
600
|
-
<dialog id="delete-modal" class="mw-modal mw-modal-sm" closedby="any">
|
|
601
|
-
<div class="mw-modal-header">
|
|
602
|
-
<h4 class="mw-modal-title">Delete invoice</h4>
|
|
603
|
-
<button class="mw-modal-close" type="button" aria-label="Close">
|
|
604
|
-
𝗫
|
|
605
|
-
</button>
|
|
606
|
-
</div>
|
|
607
|
-
<div class="mw-modal-body">
|
|
608
|
-
<p>Invoice 2026-0042 will be removed.</p>
|
|
609
|
-
</div>
|
|
610
|
-
<div class="mw-modal-footer">
|
|
611
|
-
<p class="mw-actions-note">This action cannot be undone.</p>
|
|
612
|
-
<button class="mw-modal-close mw-btn mw-btn-outline">Cancel</button>
|
|
613
|
-
<button class="mw-btn mw-btn-danger">Delete</button>
|
|
614
|
-
</div>
|
|
615
|
-
</dialog>
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
- Escape, the focus trap, `inert` on the page behind it and the body scroll lock
|
|
619
|
-
all come from the element. There is no overlay wrapper and no backdrop div.
|
|
620
|
-
- Opened with `showModal()`, closed with `close()`. `main.js` wires the close
|
|
621
|
-
buttons and exposes `mwOpenModal(id)` / `mwCloseModal(id)`; a trigger can also
|
|
622
|
-
carry `data-mw-modal="delete-modal"` and needs no script at all.
|
|
623
|
-
- `closedby="any"` dismisses it on a backdrop click; where that attribute is not
|
|
624
|
-
understood yet the script handles the click instead.
|
|
625
|
-
- Sizes: `mw-modal-sm` 370px, default 520px, `mw-modal-lg` 720px,
|
|
626
|
-
`mw-modal-xl` 960px. Height is capped at 80-92dvh (`--mw-modal-max-height`),
|
|
627
|
-
the body scrolls. Below 576px it becomes a bottom sheet.
|
|
628
|
-
- `mw-modal-close` is the hook, not a look: on the header X it styles the button,
|
|
629
|
-
on a footer action it only closes. Combine it with `mw-btn mw-btn-primary` and
|
|
630
|
-
the button keeps its own paint.
|
|
631
|
-
- `mw-modal-body` takes a `mw-form` directly - the form brings the field gaps,
|
|
632
|
-
the body brings the padding.
|
|
633
|
-
- `mw-actions-note` is the muted line above the buttons - see below.
|
|
634
|
-
- Name it with `aria-labelledby` pointing at the `mw-modal-title`; `role` and
|
|
635
|
-
`aria-modal` come from `showModal()`. Without `autofocus` the browser focuses
|
|
636
|
-
the first focusable element, usually the close button.
|
|
637
|
-
|
|
638
|
-
Angular: `[open]="isOpen()"` does **not** work - the attribute opens a non-modal
|
|
639
|
-
dialog with no top layer, backdrop or focus trap. It takes a directive calling
|
|
640
|
-
`showModal()`, see `examples/angular-services.md`.
|
|
641
|
-
|
|
642
|
-
### The older overlay div
|
|
643
|
-
|
|
644
|
-
Still styled, for markup that predates the `<dialog>` above. It cannot trap
|
|
645
|
-
focus or make the page inert, so it is not what to write now.
|
|
646
|
-
|
|
647
|
-
```html
|
|
648
|
-
<div id="delete-modal" class="mw-modal-overlay">
|
|
649
|
-
<div class="mw-modal mw-modal-sm">…</div>
|
|
650
|
-
<div class="mw-modal-backdrop"></div>
|
|
651
|
-
</div>
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
- `display: none` until `mw-modal-open` is added to the overlay - that class is
|
|
655
|
-
the whole open/close mechanism, in Angular
|
|
656
|
-
`[class.mw-modal-open]="isOpen()"`.
|
|
657
|
-
- Body scroll lock comes from `body:has(.mw-modal-open)`.
|
|
658
|
-
- `mw-modal-backdrop` is the click-to-close surface; put the handler on it.
|
|
659
|
-
|
|
660
|
-
## Alerts & toasts
|
|
661
|
-
|
|
662
|
-
```html
|
|
663
|
-
<div class="mw-alert mw-alert-success">
|
|
664
|
-
<div class="mw-alert-icon"><i class="fas fa-check-circle"></i></div>
|
|
665
|
-
<div class="mw-alert-content">
|
|
666
|
-
<span class="mw-alert-title">Saved</span>
|
|
667
|
-
<p>The invoice was created.</p>
|
|
668
|
-
</div>
|
|
669
|
-
<button type="button" class="mw-btn-mini mw-btn-mini-danger mw-alert-close">
|
|
670
|
-
<i class="fas fa-times"></i>
|
|
671
|
-
</button>
|
|
672
|
-
</div>
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
Variants: `mw-alert-primary`, `-secondary`, `-success`, `-warning`, `-danger`,
|
|
676
|
-
`-info`. `mw-alert-title` and the close button are optional. Dismissal runs in
|
|
677
|
-
two steps: `mw-alert-closing` fades the alert out over `--mw-duration-base`, then
|
|
678
|
-
`mw-alert-closed` hides it (`display: none`) - in a SPA prefer removing it from
|
|
679
|
-
the list instead.
|
|
680
|
-
|
|
681
|
-
**Toasts** are alerts inside a fixed stack:
|
|
682
|
-
|
|
683
|
-
```html
|
|
684
|
-
<div class="mw-toast-stack mw-toast-stack-top-right">
|
|
685
|
-
<div class="mw-alert mw-alert-success">...</div>
|
|
686
|
-
</div>
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
Positions: `mw-toast-stack-top-right` (also the default without a position
|
|
690
|
-
class), `-top-center`, `-bottom-right`. Width is capped at
|
|
691
|
-
`min(380px, 100vw - 2rem)`, clicks pass through everywhere except on a toast,
|
|
692
|
-
the entry animation respects `prefers-reduced-motion`. Auto-dismiss is the
|
|
693
|
-
application's job.
|
|
694
|
-
|
|
695
|
-
## Announcement
|
|
696
|
-
|
|
697
|
-
The ribbon glued under the fixed header - a discount, a launch note, a
|
|
698
|
-
maintenance window. One per page.
|
|
699
|
-
|
|
700
|
-
```html
|
|
701
|
-
<aside class="mw-announcement mw-announcement-secondary">
|
|
702
|
-
<a class="mw-announcement-content" href="#pricing">
|
|
703
|
-
<span class="mw-announcement-highlight">-20 %</span>
|
|
704
|
-
<span>Summer discount on all coachings until 06.09.</span>
|
|
705
|
-
</a>
|
|
706
|
-
</aside>
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
Positioned fixed below the header and one z-index layer under it, and it stays
|
|
710
|
-
there - the page scrolls beneath it. Variants: `mw-announcement-secondary`,
|
|
711
|
-
`-success`, `-warning`, `-danger`, `-info` (primary is the default).
|
|
712
|
-
`mw-announcement-highlight` is the pill in front of the text; the content
|
|
713
|
-
element is usually an `<a>` but a plain `<div>` works.
|
|
714
|
-
`mw-announcement-static` puts the ribbon into the document flow instead - it
|
|
715
|
-
then scrolls with the content and shifts no anchor offset.
|
|
716
|
-
|
|
717
|
-
The height lives in `--mw-announcement-height` and drives the anchor scroll
|
|
718
|
-
offset (`scroll-padding-top`) automatically - a taller ribbon raises the token
|
|
719
|
-
instead of fighting the offset.
|
|
720
|
-
|
|
721
|
-
On a page whose header carries `mw-header-reveal` the ribbon leaves and arrives
|
|
722
|
-
with it, without a class of its own - both cover the height of the pair, so they
|
|
723
|
-
come in as one block. See `references/layout.md`.
|
|
724
|
-
|
|
725
|
-
## Empty state
|
|
726
|
-
|
|
727
|
-
```html
|
|
728
|
-
<div class="mw-empty-state mw-empty-state-warning mw-empty-state-sm">
|
|
729
|
-
<div class="mw-empty-state-icon"><i class="fas fa-inbox"></i></div>
|
|
730
|
-
<p class="mw-empty-state-title">No results</p>
|
|
731
|
-
<p class="mw-empty-state-desc">Try a different filter.</p>
|
|
732
|
-
<button class="mw-btn mw-btn-outline">Clear filters</button>
|
|
733
|
-
</div>
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
Variants: `mw-empty-state-primary`, `-success`, `-warning`, `-danger`. Size:
|
|
737
|
-
`mw-empty-state-sm`.
|
|
738
|
-
|
|
739
|
-
## Spinners
|
|
740
|
-
|
|
741
|
-
```html
|
|
742
|
-
<div class="mw-spinner-container">
|
|
743
|
-
<div class="mw-spinner-border mw-spinner-primary"></div>
|
|
744
|
-
</div>
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
- Shapes (one is mandatory): `mw-spinner-border`, `mw-spinner-dual-ring`,
|
|
748
|
-
`mw-spinner-dots` (needs three empty `<div>` children).
|
|
749
|
-
- Sizes `mw-spinner-sm`, `mw-spinner-lg`; colours `mw-spinner-primary`,
|
|
750
|
-
`mw-spinner-secondary`.
|
|
751
|
-
- `mw-spinner-container` centers in the available space.
|
|
752
|
-
`mw-spinner-container-overlay` lays a dimmed, blurred scrim over the parent -
|
|
753
|
-
the parent needs `position: relative`.
|
|
754
|
-
|
|
755
|
-
## Skeleton
|
|
756
|
-
|
|
757
|
-
```html
|
|
758
|
-
<div class="mw-skeleton-card">
|
|
759
|
-
<div class="mw-skeleton-row">
|
|
760
|
-
<span class="mw-skeleton mw-skeleton-circle"></span>
|
|
761
|
-
<div class="mw-skeleton-body">
|
|
762
|
-
<span class="mw-skeleton mw-skeleton-text" style="width: 55%"></span>
|
|
763
|
-
<span class="mw-skeleton mw-skeleton-text"></span>
|
|
764
|
-
</div>
|
|
765
|
-
</div>
|
|
766
|
-
<span class="mw-skeleton mw-skeleton-rect"></span>
|
|
767
|
-
<span class="mw-skeleton mw-skeleton-title"></span>
|
|
768
|
-
<span class="mw-skeleton mw-skeleton-text"></span>
|
|
769
|
-
</div>
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
Shapes: `mw-skeleton-title`, `-text`, `-circle`, `-rect` (+ `-rect-sm`,
|
|
773
|
-
`-rect-lg`). Widths are set inline when a line should look ragged.
|
|
774
|
-
|
|
775
|
-
## Tables
|
|
776
|
-
|
|
777
|
-
```html
|
|
778
|
-
<div class="mw-table-responsive">
|
|
779
|
-
<table class="mw-table mw-table-cards">
|
|
780
|
-
<thead>
|
|
781
|
-
<tr>
|
|
782
|
-
<th>Name</th>
|
|
783
|
-
<th>Status</th>
|
|
784
|
-
<th class="mw-text-numeric">Amount</th>
|
|
785
|
-
</tr>
|
|
786
|
-
</thead>
|
|
787
|
-
<tbody>
|
|
788
|
-
<tr>
|
|
789
|
-
<td data-label="Name">Jane Doe</td>
|
|
790
|
-
<td data-label="Status">
|
|
791
|
-
<span class="mw-tag mw-tag-success">Paid</span>
|
|
792
|
-
</td>
|
|
793
|
-
<td data-label="Amount" class="mw-text-numeric">1.204,50</td>
|
|
794
|
-
</tr>
|
|
795
|
-
</tbody>
|
|
796
|
-
</table>
|
|
797
|
-
</div>
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
| Class | Effect |
|
|
801
|
-
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
802
|
-
| `mw-table` | Base: zebra rows, row hover, primary-coloured header |
|
|
803
|
-
| `mw-table-subtle` | Quiet grey header with an accent rule - for data-heavy lists |
|
|
804
|
-
| `mw-table-compact` | Less padding, smaller type |
|
|
805
|
-
| `mw-table-hover` | Stronger row hover |
|
|
806
|
-
| `mw-table-cards` | Below `md` every row becomes a card; keep `<thead>` (hidden via CSS) and give each cell a `data-label` |
|
|
807
|
-
| `mw-table-responsive` | Wrapper, horizontal scroll |
|
|
808
|
-
| `mw-table-responsive-hint` | On top of the wrapper: a soft right edge showing there are more columns. Opt-in - see below |
|
|
809
|
-
| `mw-table-responsive-scroll` | Wrapper with a height cap (`--mw-table-scroll-height`, 400px / 260px below `sm`) and vertical scroll |
|
|
810
|
-
| `mw-table-sticky-head` | Header stays put while the body scrolls - only works inside a height-limited wrapper, i.e. `mw-table-responsive-scroll` |
|
|
811
|
-
|
|
812
|
-
`mw-table-responsive-hint` is opt-in, unlike the automatic hint on a tab bar,
|
|
813
|
-
and works differently for a reason: a table paints its own opaque surface, so
|
|
814
|
-
the gradient trick used on `mw-tabs-nav` would sit _behind_ the rows and never
|
|
815
|
-
show. A mask sits in front and does show - but a mask has no `local` attachment,
|
|
816
|
-
so it cannot fade away once the last column is reached. Put the class on the
|
|
817
|
-
tables you know overflow, leave it off the ones that fit.
|
|
818
|
-
|
|
819
|
-
```html
|
|
820
|
-
<div class="mw-table-responsive-scroll" style="--mw-table-scroll-height: 250px">
|
|
821
|
-
<table class="mw-table mw-table-subtle mw-table-sticky-head">
|
|
822
|
-
...
|
|
823
|
-
</table>
|
|
824
|
-
</div>
|
|
825
|
-
```
|
|
826
|
-
|
|
827
|
-
## Kanban
|
|
828
|
-
|
|
829
|
-
A board is a grid of equally wide lanes; a ticket is a plain `mw-card` with
|
|
830
|
-
`mw-kanban-card` on top. Everything except the counters and the composer is CSS.
|
|
831
|
-
|
|
832
|
-
The board puts as many lanes in a row as fit at 260px each and wraps the rest
|
|
833
|
-
onto the next. It measures its own width for that, so a board inside a panel or
|
|
834
|
-
a half-width column wraps on a desktop too.
|
|
835
|
-
|
|
836
|
-
```html
|
|
837
|
-
<div class="mw-kanban" style="--mw-kanban-column-min-height: 390px">
|
|
838
|
-
<div class="mw-kanban-column">
|
|
839
|
-
<div class="mw-kanban-column-header">
|
|
840
|
-
<h5 class="mw-kanban-title">In Progress</h5>
|
|
841
|
-
<span class="mw-kanban-count">2</span>
|
|
842
|
-
<button
|
|
843
|
-
type="button"
|
|
844
|
-
class="mw-btn mw-btn-secondary mw-btn-sm mw-kanban-add"
|
|
845
|
-
aria-label="Add ticket to In Progress"
|
|
846
|
-
>
|
|
847
|
-
<i class="fas fa-plus"></i>
|
|
848
|
-
</button>
|
|
849
|
-
</div>
|
|
850
|
-
|
|
851
|
-
<div class="mw-kanban-column-body">
|
|
852
|
-
<article class="mw-card mw-kanban-card">
|
|
853
|
-
<div class="mw-card-ribbon mw-card-addon-danger">High</div>
|
|
854
|
-
<p class="mw-kanban-card-title">Sticky table header jitters</p>
|
|
855
|
-
<p class="mw-kanban-card-text">
|
|
856
|
-
On Safari the header shifts by a pixel.
|
|
857
|
-
</p>
|
|
858
|
-
<div class="mw-kanban-card-footer">
|
|
859
|
-
<span class="mw-kanban-card-id">MW-102</span>
|
|
860
|
-
<div class="mw-kanban-card-actions">
|
|
861
|
-
<div class="mw-avatar mw-avatar-xs mw-avatar-initials">jd</div>
|
|
862
|
-
<button
|
|
863
|
-
type="button"
|
|
864
|
-
class="mw-kanban-action"
|
|
865
|
-
aria-label="Move MW-102 one lane left"
|
|
866
|
-
>
|
|
867
|
-
<i class="fas fa-chevron-left"></i>
|
|
868
|
-
</button>
|
|
869
|
-
<button
|
|
870
|
-
type="button"
|
|
871
|
-
class="mw-kanban-action"
|
|
872
|
-
aria-label="Move MW-102 one lane right"
|
|
873
|
-
>
|
|
874
|
-
<i class="fas fa-chevron-right"></i>
|
|
875
|
-
</button>
|
|
876
|
-
<button
|
|
877
|
-
type="button"
|
|
878
|
-
class="mw-kanban-action"
|
|
879
|
-
aria-label="Edit MW-102"
|
|
880
|
-
>
|
|
881
|
-
<i class="fas fa-pen"></i>
|
|
882
|
-
</button>
|
|
883
|
-
<button
|
|
884
|
-
type="button"
|
|
885
|
-
class="mw-kanban-action mw-kanban-action-danger"
|
|
886
|
-
aria-label="Delete MW-102"
|
|
887
|
-
>
|
|
888
|
-
<i class="fas fa-trash"></i>
|
|
889
|
-
</button>
|
|
890
|
-
</div>
|
|
891
|
-
</div>
|
|
892
|
-
</article>
|
|
893
|
-
|
|
894
|
-
<div class="mw-kanban-empty">No tickets</div>
|
|
895
|
-
</div>
|
|
896
|
-
</div>
|
|
897
|
-
...
|
|
898
|
-
</div>
|
|
899
|
-
```
|
|
900
|
-
|
|
901
|
-
| Class | Role |
|
|
902
|
-
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
903
|
-
| `mw-kanban` | Board. Grid, one column per lane, same width for all of them |
|
|
904
|
-
| `mw-kanban-plain` | Board without its own surface or padding - for a board that already sits on a panel |
|
|
905
|
-
| `mw-kanban-compact` | Tighter padding, description clamped to 2 lines instead of 4, lane floor 90px |
|
|
906
|
-
| `mw-kanban-column` | Lane: dashed border, flex column |
|
|
907
|
-
| `mw-kanban-column-header` | Title + counter + add button in one row |
|
|
908
|
-
| `mw-kanban-title` | Lane title, uppercase, truncates |
|
|
909
|
-
| `mw-kanban-count` | Ticket counter pill |
|
|
910
|
-
| `mw-kanban-add` | Sits **on** `mw-btn` - only trims it to the header line height |
|
|
911
|
-
| `mw-kanban-column-body` | Ticket stack; fills the lane so the empty state stays centred |
|
|
912
|
-
| `mw-kanban-card` | Ticket. Needs `mw-card` next to it |
|
|
913
|
-
| `mw-kanban-card-title` | Ticket title |
|
|
914
|
-
| `mw-kanban-card-text` | Description, clamped to 4 lines (2 on a compact board) |
|
|
915
|
-
| `mw-kanban-card-footer` | Rule + key on the left, avatar and actions on the right |
|
|
916
|
-
| `mw-kanban-card-id` | Ticket key, monospaced so equal-length keys line up across cards |
|
|
917
|
-
| `mw-kanban-card-actions` | Right-hand group; resets the avatar margin |
|
|
918
|
-
| `mw-kanban-action` | 26px square icon button (32px below `md`, hit area grown to 44px), `mw-kanban-action-danger` turns the hover red |
|
|
919
|
-
| `mw-kanban-empty` | Placeholder; hides itself as soon as the lane holds a ticket or an open composer |
|
|
920
|
-
| `mw-kanban-card-in` | One-shot entry animation for a freshly created ticket |
|
|
921
|
-
|
|
922
|
-
- **Priority is the regular `mw-card-ribbon`** and brings its own colour through
|
|
923
|
-
`mw-card-addon-danger|warning|info`. It has to be a **direct child** of the
|
|
924
|
-
card - that is what reserves the space next to the title. No ribbon means no
|
|
925
|
-
priority; there is no separate priority class.
|
|
926
|
-
- Lanes are as tall as the fullest one, with `--mw-kanban-column-min-height` as
|
|
927
|
-
the floor (120px, 90px compact). Raise it per board so a board that starts out
|
|
928
|
-
empty still reads as a board.
|
|
929
|
-
- Board surface and lane border are `--mw-kanban-background` and
|
|
930
|
-
`--mw-kanban-lane-border` - set them on the board, or drop the surface with
|
|
931
|
-
`mw-kanban-plain`.
|
|
932
|
-
- Responsive: below `lg` the lanes reflow into two columns, below `sm` into one.
|
|
933
|
-
- The actions are ordinary buttons - which of them exist is your decision. Give
|
|
934
|
-
every one an `aria-label` naming the ticket; the icon alone has no accessible
|
|
935
|
-
name.
|
|
936
|
-
|
|
937
|
-
### Composer
|
|
938
|
-
|
|
939
|
-
The inline form for creating and editing a ticket. One per board, moved into the
|
|
940
|
-
lane it is needed in.
|
|
941
|
-
|
|
942
|
-
```html
|
|
943
|
-
<form class="mw-kanban-composer mw-active">
|
|
944
|
-
<input type="text" class="mw-input mw-input-sm mw-kanban-composer-title" />
|
|
945
|
-
<textarea class="mw-textarea mw-kanban-composer-text" rows="2"></textarea>
|
|
946
|
-
<div class="mw-kanban-composer-row">
|
|
947
|
-
<select class="mw-select mw-select-sm mw-kanban-composer-priority">
|
|
948
|
-
...
|
|
949
|
-
</select>
|
|
950
|
-
<select class="mw-select mw-select-sm mw-kanban-composer-assignee">
|
|
951
|
-
...
|
|
952
|
-
</select>
|
|
953
|
-
</div>
|
|
954
|
-
<div class="mw-kanban-composer-actions">
|
|
955
|
-
<button type="button" class="mw-btn mw-btn-outline mw-btn-sm">
|
|
956
|
-
Cancel
|
|
957
|
-
</button>
|
|
958
|
-
<button type="submit" class="mw-btn mw-btn-primary mw-btn-sm">Save</button>
|
|
959
|
-
</div>
|
|
960
|
-
</form>
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
- **`mw-kanban-composer` is `display: none` until it also carries `mw-active`.**
|
|
964
|
-
Rendering it conditionally is not enough - the class has to be there too, or
|
|
965
|
-
the form stays invisible.
|
|
966
|
-
- `mw-kanban-composer-row` puts priority and assignee side by side and stacks
|
|
967
|
-
them once the lane gets too narrow; `mw-kanban-composer-actions` is the button
|
|
968
|
-
row and gives its buttons the same 80px floor as a modal footer.
|
|
969
|
-
- The `-title`, `-text`, `-priority`, `-assignee` and `-cancel` classes carry no
|
|
970
|
-
styling of their own beyond a min-height on the textarea - they are the hooks
|
|
971
|
-
the shipped JS queries. In a SPA you bind the controls yourself and can drop
|
|
972
|
-
them.
|
|
973
|
-
- `mw-kanban-editing` hides the ticket the composer is currently replacing.
|
|
974
|
-
|
|
975
|
-
## Calendar
|
|
976
|
-
|
|
977
|
-
A card-shaped surface with a header and a seven column grid. A month is six
|
|
978
|
-
rows, a week is one - same classes, same cell size, the only difference is how
|
|
979
|
-
many cells you render. Which page is on screen is application state: the shipped
|
|
980
|
-
JS renders it from `data-calendar`, an Angular app renders the same markup
|
|
981
|
-
itself.
|
|
982
|
-
|
|
983
|
-
```html
|
|
984
|
-
<div
|
|
985
|
-
class="mw-calendar"
|
|
986
|
-
data-calendar="month"
|
|
987
|
-
data-calendar-markers='{"2026-08-19": ["success", "warning"]}'
|
|
988
|
-
>
|
|
989
|
-
<div class="mw-calendar-header">
|
|
990
|
-
<button
|
|
991
|
-
type="button"
|
|
992
|
-
class="mw-btn mw-btn-outline mw-btn-sm mw-calendar-nav"
|
|
993
|
-
data-calendar-nav="-1"
|
|
994
|
-
aria-label="Previous month"
|
|
995
|
-
>
|
|
996
|
-
<i class="fas fa-chevron-left"></i>
|
|
997
|
-
</button>
|
|
998
|
-
|
|
999
|
-
<div class="mw-calendar-title" aria-live="polite">August 2026</div>
|
|
1000
|
-
|
|
1001
|
-
<button
|
|
1002
|
-
type="button"
|
|
1003
|
-
class="mw-btn mw-btn-outline mw-btn-sm mw-calendar-nav"
|
|
1004
|
-
data-calendar-nav="1"
|
|
1005
|
-
aria-label="Next month"
|
|
1006
|
-
>
|
|
1007
|
-
<i class="fas fa-chevron-right"></i>
|
|
1008
|
-
</button>
|
|
1009
|
-
</div>
|
|
1010
|
-
|
|
1011
|
-
<div class="mw-calendar-grid">
|
|
1012
|
-
<div class="mw-calendar-weekday" aria-hidden="true">Mon</div>
|
|
1013
|
-
...
|
|
1014
|
-
<div class="mw-calendar-weekday mw-calendar-weekend" aria-hidden="true">
|
|
1015
|
-
Sat
|
|
1016
|
-
</div>
|
|
1017
|
-
|
|
1018
|
-
<button
|
|
1019
|
-
type="button"
|
|
1020
|
-
class="mw-calendar-day mw-calendar-adjacent"
|
|
1021
|
-
aria-label="Monday, 27 July 2026"
|
|
1022
|
-
>
|
|
1023
|
-
<span class="mw-calendar-date">27</span>
|
|
1024
|
-
</button>
|
|
1025
|
-
|
|
1026
|
-
<button
|
|
1027
|
-
type="button"
|
|
1028
|
-
class="mw-calendar-day mw-calendar-today"
|
|
1029
|
-
aria-current="date"
|
|
1030
|
-
aria-pressed="false"
|
|
1031
|
-
aria-label="Wednesday, 19 August 2026"
|
|
1032
|
-
>
|
|
1033
|
-
<span class="mw-calendar-date">19</span>
|
|
1034
|
-
<span class="mw-calendar-dots">
|
|
1035
|
-
<span class="mw-calendar-dot mw-calendar-dot-success"></span>
|
|
1036
|
-
<span class="mw-calendar-dot mw-calendar-dot-warning"></span>
|
|
1037
|
-
</span>
|
|
1038
|
-
</button>
|
|
1039
|
-
...
|
|
1040
|
-
</div>
|
|
1041
|
-
|
|
1042
|
-
<div class="mw-calendar-legend">
|
|
1043
|
-
<span class="mw-calendar-legend-item">
|
|
1044
|
-
<span class="mw-calendar-dot mw-calendar-dot-success"></span>
|
|
1045
|
-
Slots free
|
|
1046
|
-
</span>
|
|
1047
|
-
...
|
|
1048
|
-
</div>
|
|
1049
|
-
</div>
|
|
1050
|
-
```
|
|
1051
|
-
|
|
1052
|
-
| Class | Role |
|
|
1053
|
-
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1054
|
-
| `mw-calendar` | Surface. Same border, radius and shadow as a card, without the hover lift |
|
|
1055
|
-
| `mw-calendar-plain` | Drops the surface - for a calendar already sitting on a card or panel |
|
|
1056
|
-
| `mw-calendar-compact` | Date picker density: dots hidden, 29px cells, tighter header and labels |
|
|
1057
|
-
| `mw-calendar-header` | Arrow, title, arrow |
|
|
1058
|
-
| `mw-calendar-nav` | Sits **on** `mw-btn` - only squares it off around the chevron |
|
|
1059
|
-
| `mw-calendar-title` | Takes the space between the arrows, stays optically centred |
|
|
1060
|
-
| `mw-calendar-grid` | The seven column grid; weekday labels and day cells are its only children |
|
|
1061
|
-
| `mw-calendar-weekday` | Column label. Decorative - the day buttons carry the weekday themselves |
|
|
1062
|
-
| `mw-calendar-day` | One day. A `button`, so `:disabled` gives you an unavailable day |
|
|
1063
|
-
| `mw-calendar-date` | The number |
|
|
1064
|
-
| `mw-calendar-adjacent` | Day of the neighbouring month - muted, still readable |
|
|
1065
|
-
| `mw-calendar-weekend` | Saturday/Sunday. Tinted cell; on the label it turns the text secondary |
|
|
1066
|
-
| `mw-calendar-today` | Outlined in the secondary colour, bold |
|
|
1067
|
-
| `mw-selected` | Filled with the primary colour |
|
|
1068
|
-
| `mw-calendar-dots` | Dot row, absolutely placed at the bottom of the cell |
|
|
1069
|
-
| `mw-calendar-dot` | 5px dot. Colour comes from `--mw-calendar-dot`; the `-primary`, `-secondary`, `-success`, `-warning`, `-danger`, `-info` classes are presets for it |
|
|
1070
|
-
| `mw-calendar-legend` | Rule plus a row of dot/label pairs below the grid |
|
|
1071
|
-
| `mw-calendar-legend-item` | One dot/label pair |
|
|
1072
|
-
|
|
1073
|
-
- **Today is outlined in secondary, the picked day is filled with primary.** Two
|
|
1074
|
-
different colours on purpose: an outline and a fill in the same colour read as
|
|
1075
|
-
two states of the same thing. Do not give one day both.
|
|
1076
|
-
- The dots are positioned absolutely, so a day without any keeps the exact same
|
|
1077
|
-
height and its number sits on the same line as every other. Two or three per
|
|
1078
|
-
day stay readable, more do not. A dot on a picked day keeps its status colour;
|
|
1079
|
-
only the untoned one flips to the accent text colour.
|
|
1080
|
-
- **What a dot means belongs to the caller.** The same calendar reads "request
|
|
1081
|
-
pending / booked / time offered" in a scheduling app and something entirely
|
|
1082
|
-
different on a public booking page, so the colour is a custom property, not a
|
|
1083
|
-
fixed set: `style="--mw-calendar-dot: #7a4fd4"` on the dot, or on the cell to
|
|
1084
|
-
colour all of its dots. The six tone classes are named presets for exactly
|
|
1085
|
-
that property - use them when they fit, ignore them when they do not.
|
|
1086
|
-
- **Render the days spilling in from the neighbouring month.** An empty first
|
|
1087
|
-
row reads like a broken calendar, not like a short month. `mw-calendar-adjacent`
|
|
1088
|
-
is what pushes them back.
|
|
1089
|
-
- A month gets six rows even when five would do, so the calendar - and
|
|
1090
|
-
everything under it - keeps its height while you page through.
|
|
1091
|
-
- Cell height is fixed (38px, 44px on a coarse pointer, 29px compact), width
|
|
1092
|
-
follows the container. No aspect ratio: a full-width calendar would otherwise
|
|
1093
|
-
grow rows several hundred pixels tall. A calendar therefore wants a column,
|
|
1094
|
-
not the full page width - give it one, or cap it.
|
|
1095
|
-
- **A week has no class of its own.** Render seven cells instead of forty-two
|
|
1096
|
-
and you have one; everything else is identical.
|
|
1097
|
-
- `mw-calendar-compact` hides the dots and shortens the cell - the dots are what
|
|
1098
|
-
a full cell needs its height for, so the two go together. It costs about a
|
|
1099
|
-
third of the calendar height and leaves a plain date picker; drop the legend
|
|
1100
|
-
there, it has nothing left to explain. 29px is below a comfortable tap target,
|
|
1101
|
-
so keep it for pointer-first surfaces. Its grid gap drops to 2px as well, which
|
|
1102
|
-
puts the space back into the cells.
|
|
1103
|
-
- Give every day an `aria-label` with the full date - the bare number is not an
|
|
1104
|
-
accessible name. The weekday labels are `aria-hidden`, they would only repeat
|
|
1105
|
-
it. With a `button` per day, keep one tab stop for the grid (roving
|
|
1106
|
-
`tabindex`) instead of 42.
|
|
1107
|
-
|
|
1108
|
-
## Tags
|
|
1109
|
-
|
|
1110
|
-
Two forms, picked by count - see also the pitfall list in `SKILL.md`.
|
|
1111
|
-
|
|
1112
|
-
**Single chip** (table cells, status columns) - carries its colour itself:
|
|
1113
|
-
|
|
1114
|
-
```html
|
|
1115
|
-
<span class="mw-tag mw-tag-success">Paid</span>
|
|
1116
|
-
```
|
|
1117
|
-
|
|
1118
|
-
Variants: `mw-tag-primary`, `-secondary`, `-success`, `-info`, `-warning`,
|
|
1119
|
-
`-danger`, `-muted`, `-neutral`. Size: `mw-tag-lg`.
|
|
1120
|
-
|
|
1121
|
-
`mw-tag-muted` and `mw-tag-neutral` are not the same and the difference carries
|
|
1122
|
-
meaning. **Muted** means the label steps back - draft, archived, a shortcoming
|
|
1123
|
-
("no e-invoice"). **Neutral** is an identifier with no judgement attached and
|
|
1124
|
-
keeps the normal text colour - a customer number, a document type, a price on a
|
|
1125
|
-
category ("XRechnung" is a property, not a verdict). Both are also available on
|
|
1126
|
-
the list container: `mw-tags-muted`, `mw-tags-neutral`.
|
|
1127
|
-
|
|
1128
|
-
**List** - the container carries the colour for all its items:
|
|
1129
|
-
|
|
1130
|
-
```html
|
|
1131
|
-
<div class="mw-tags mw-tags-primary">
|
|
1132
|
-
<span class="mw-tags-item"
|
|
1133
|
-
><i class="fas fa-check mw-tags-icon"></i>Angular</span
|
|
1134
|
-
>
|
|
1135
|
-
<span class="mw-tags-item mw-tags-removable">
|
|
1136
|
-
Kotlin
|
|
1137
|
-
<button type="button" class="mw-btn-mini mw-btn-mini-danger mw-tags-remove">
|
|
1138
|
-
<i class="fas fa-times"></i>
|
|
1139
|
-
</button>
|
|
1140
|
-
</span>
|
|
1141
|
-
</div>
|
|
1142
|
-
```
|
|
1143
|
-
|
|
1144
|
-
Container variants: `mw-tags-primary`, `-secondary`, `-success`, `-info`,
|
|
1145
|
-
`-warning`, `-danger`, `-muted`, size `mw-tags-lg`. `mw-tags-remove` only
|
|
1146
|
-
positions the button - its look comes from `mw-btn-mini` plus a colour variant.
|
|
1147
|
-
|
|
1148
|
-
## Badges
|
|
1149
|
-
|
|
1150
|
-
A count or a status, not a label. See the pitfall list in `SKILL.md` for the
|
|
1151
|
-
badge-versus-tag rule: a tag names something and sits in a row of its own kind,
|
|
1152
|
-
a badge carries a number or a state and usually sits _on_ something.
|
|
1153
|
-
|
|
1154
|
-
```html
|
|
1155
|
-
<span class="mw-badge mw-badge-danger">99+</span>
|
|
1156
|
-
```
|
|
1157
|
-
|
|
1158
|
-
Colours: `mw-badge-primary` (the default), `-secondary`, `-success`, `-warning`,
|
|
1159
|
-
`-danger`, `-info`, `-muted`, plus `mw-badge-outline`. Sizes: `mw-badge-sm`,
|
|
1160
|
-
`mw-badge-lg`.
|
|
1161
|
-
|
|
1162
|
-
Round on one character, a pill from two on - a `min-width` the padding grows
|
|
1163
|
-
past. The digits are `tabular-nums`, so a counter ticking 9 to 10 does not shift
|
|
1164
|
-
what sits beside it.
|
|
1165
|
-
|
|
1166
|
-
**On an icon or a button:**
|
|
1167
|
-
|
|
1168
|
-
```html
|
|
1169
|
-
<span class="mw-badge-anchor">
|
|
1170
|
-
<button class="mw-btn mw-btn-outline">
|
|
1171
|
-
<i class="fas fa-inbox"></i> Inbox
|
|
1172
|
-
</button>
|
|
1173
|
-
<span class="mw-badge mw-badge-danger mw-badge-float">5</span>
|
|
1174
|
-
</span>
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
`mw-badge-anchor` on the thing being badged, `mw-badge-float` on the badge. It
|
|
1178
|
-
hangs half off the corner and carries a ring in the surface colour, so it reads
|
|
1179
|
-
as a separate object rather than a blob on the icon. The ring follows the
|
|
1180
|
-
surface automatically inside a card, panel, modal, tile or the header.
|
|
1181
|
-
|
|
1182
|
-
**Status:**
|
|
1183
|
-
|
|
1184
|
-
```html
|
|
1185
|
-
<span class="mw-badge-status mw-badge-status-success">
|
|
1186
|
-
<span class="mw-badge-dot"></span> Online
|
|
1187
|
-
</span>
|
|
1188
|
-
```
|
|
1189
|
-
|
|
1190
|
-
`mw-badge-status-{primary|secondary|success|warning|danger|info|muted}` colours
|
|
1191
|
-
the dot inside it. A bare `mw-badge-dot-{colour}` works on its own too. Add
|
|
1192
|
-
`mw-badge-pulse` for something actually running - a job, an open connection: a
|
|
1193
|
-
ring grows out and fades while the dot underneath stays put, so a column of them
|
|
1194
|
-
in a table stays readable. Not for decoration.
|
|
1195
|
-
|
|
1196
|
-
## Dropdown
|
|
1197
|
-
|
|
1198
|
-
Built on `<details>` / `<summary>`. That is where the keyboard handling, the
|
|
1199
|
-
focus behaviour and the open state a screen reader can see come from, and it
|
|
1200
|
-
works with JavaScript switched off. The framework script only adds Escape and
|
|
1201
|
-
click-outside.
|
|
1202
|
-
|
|
1203
|
-
```html
|
|
1204
|
-
<details class="mw-dropdown">
|
|
1205
|
-
<summary class="mw-btn mw-btn-primary">
|
|
1206
|
-
Actions <i class="fas fa-chevron-down mw-dropdown-caret"></i>
|
|
1207
|
-
</summary>
|
|
1208
|
-
<div class="mw-dropdown-menu">
|
|
1209
|
-
<div class="mw-dropdown-label">Manage</div>
|
|
1210
|
-
<button class="mw-dropdown-item"><i class="fas fa-pen"></i> Edit</button>
|
|
1211
|
-
<button class="mw-dropdown-item mw-active">Duplicate</button>
|
|
1212
|
-
<button class="mw-dropdown-item" disabled>Move</button>
|
|
1213
|
-
<hr class="mw-dropdown-divider" />
|
|
1214
|
-
<button class="mw-dropdown-item mw-dropdown-item-danger">
|
|
1215
|
-
<i class="fas fa-trash"></i> Delete
|
|
1216
|
-
</button>
|
|
1217
|
-
</div>
|
|
1218
|
-
</details>
|
|
1219
|
-
```
|
|
1220
|
-
|
|
1221
|
-
Parts: `mw-dropdown-menu`, `-item`, `-item-danger`, `-divider`, `-label`,
|
|
1222
|
-
`-caret` (rotates with the open state on its own). Alignment:
|
|
1223
|
-
`mw-dropdown-end` anchors the menu to the trigger's right edge,
|
|
1224
|
-
`mw-dropdown-up` opens it upward.
|
|
1225
|
-
|
|
1226
|
-
`mw-active` (or `aria-checked="true"`) marks the current choice, for a menu that
|
|
1227
|
-
picks rather than acts. Icons inside items keep one column, so labels line up
|
|
1228
|
-
whether or not every item has one.
|
|
1229
|
-
|
|
1230
|
-
Where the browser supports anchor positioning the menu is `position: fixed`,
|
|
1231
|
-
anchored to its trigger and flipped into whichever side has room, so it escapes
|
|
1232
|
-
a clipping ancestor by itself. Everywhere else it is absolutely positioned and
|
|
1233
|
-
is clipped by any ancestor that hides its overflow. The framework's own containers lift that clip while a menu is open; on
|
|
1234
|
-
your own it is `:has(.mw-dropdown[open]) { overflow: visible }`.
|
|
1235
|
-
|
|
1236
|
-
The menu is at least 200px wide; `--mw-dropdown-min-width` on the dropdown or a
|
|
1237
|
-
wrapper changes that. On a coarse pointer the rows grow to 2.75rem and the menu
|
|
1238
|
-
takes at least the trigger's full width.
|
|
1239
|
-
|
|
1240
|
-
## Language switcher
|
|
1241
|
-
|
|
1242
|
-
A `mw-dropdown` with a quieter trigger: no fill and no border until it is
|
|
1243
|
-
hovered, so a header bar does not turn into a row of competing buttons.
|
|
1244
|
-
|
|
1245
|
-
```html
|
|
1246
|
-
<details class="mw-dropdown mw-dropdown-end mw-lang-switch">
|
|
1247
|
-
<summary aria-label="Language: Deutsch">
|
|
1248
|
-
<span class="mw-flag mw-flag-de"></span>
|
|
1249
|
-
<span class="mw-lang-switch-code">de</span>
|
|
1250
|
-
<i class="fas fa-chevron-down mw-dropdown-caret"></i>
|
|
1251
|
-
</summary>
|
|
1252
|
-
<div class="mw-dropdown-menu">
|
|
1253
|
-
<button
|
|
1254
|
-
type="button"
|
|
1255
|
-
class="mw-dropdown-item mw-active"
|
|
1256
|
-
aria-current="true"
|
|
1257
|
-
data-mw-lang="de"
|
|
1258
|
-
>
|
|
1259
|
-
<span class="mw-flag mw-flag-de"></span>
|
|
1260
|
-
<span class="mw-lang-switch-name" lang="de">Deutsch</span>
|
|
1261
|
-
<i class="fas fa-check mw-lang-switch-check"></i>
|
|
1262
|
-
</button>
|
|
1263
|
-
<button type="button" class="mw-dropdown-item" data-mw-lang="en">
|
|
1264
|
-
<span class="mw-flag mw-flag-gb"></span>
|
|
1265
|
-
<span class="mw-lang-switch-name" lang="en">English</span>
|
|
1266
|
-
<i class="fas fa-check mw-lang-switch-check"></i>
|
|
1267
|
-
</button>
|
|
1268
|
-
</div>
|
|
1269
|
-
</details>
|
|
1270
|
-
```
|
|
1271
|
-
|
|
1272
|
-
The two-letter code carries the state, not the flag: a flag is a country and
|
|
1273
|
-
never a language - no country stands for English. Write each language name in
|
|
1274
|
-
its own language and put `lang="fr"` on it, so a screen reader pronounces it
|
|
1275
|
-
instead of spelling it out.
|
|
1276
|
-
|
|
1277
|
-
Parts: `mw-lang-switch-code`, `-name`, `-check` (the tick, shown by `mw-active`
|
|
1278
|
-
on the item and kept in the layout while hidden). `mw-lang-switch-inverted` is
|
|
1279
|
-
for a dark bar built without `mw-header`; inside `mw-header` those colours apply
|
|
1280
|
-
on their own.
|
|
1281
|
-
|
|
1282
|
-
Flags: `mw-flag` plus `mw-flag-<iso>` for de, at, ch, gb, us, ie, fr, it, es,
|
|
1283
|
-
pt, nl, be, pl, hu, ro, bg, ua, se, dk, no, fi, tr and jp - CSS gradients, all
|
|
1284
|
-
in the same 4:3 box whatever the real ratio. Never an emoji flag: Windows ships
|
|
1285
|
-
no glyphs for them. Anything outside the set goes as an `<img>` or an inline
|
|
1286
|
-
`<svg>` inside `mw-flag`.
|
|
1287
|
-
|
|
1288
|
-
## Keyboard keys
|
|
1289
|
-
|
|
1290
|
-
`<kbd>` is styled directly, so a shortcut in prose needs no class:
|
|
1291
|
-
|
|
1292
|
-
```html
|
|
1293
|
-
<p>
|
|
1294
|
-
Press
|
|
1295
|
-
<span class="mw-kbd-group"
|
|
1296
|
-
><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd></span
|
|
1297
|
-
>
|
|
1298
|
-
to open the palette, <kbd>Esc</kbd> to close.
|
|
1299
|
-
</p>
|
|
1300
|
-
```
|
|
1301
|
-
|
|
1302
|
-
`mw-kbd` is the same look for a `<span>` when the markup is not yours to change.
|
|
1303
|
-
`mw-kbd-group` wraps a combination so the pluses are spaced and the whole thing
|
|
1304
|
-
cannot wrap apart. `mw-kbd-pressed` shows a key mid-press - the raised bottom
|
|
1305
|
-
edge drops and the key sits 1px lower.
|
|
1306
|
-
|
|
1307
|
-
## Info badges & counters
|
|
1308
|
-
|
|
1309
|
-
```html
|
|
1310
|
-
<span class="mw-info mw-info-primary">Active</span>
|
|
1311
|
-
|
|
1312
|
-
<a href="/invoices" class="mw-info mw-info-counter mw-info-success">
|
|
1313
|
-
<div class="mw-info-value">150</div>
|
|
1314
|
-
<div class="mw-info-label">Invoices</div>
|
|
1315
|
-
</a>
|
|
1316
|
-
|
|
1317
|
-
<span class="mw-info-mini mw-info-mini-danger"
|
|
1318
|
-
><i class="fas fa-times"></i
|
|
1319
|
-
></span>
|
|
1320
|
-
```
|
|
1321
|
-
|
|
1322
|
-
- `mw-info`: sizes `mw-info-sm`, `-lg`, `-xl`; colours `mw-info-primary`,
|
|
1323
|
-
`-secondary`, `-success`, `-warning`, `-danger`, `-info` (tinted background +
|
|
1324
|
-
a 6px left border).
|
|
1325
|
-
- `mw-info-counter` turns it into a big number over a small label
|
|
1326
|
-
(`mw-info-value`, `mw-info-label`).
|
|
1327
|
-
- The cursor is `default` because a figure is usually not clickable. On `<a>`
|
|
1328
|
-
and `<button>` it turns into a pointer automatically; on anything else use
|
|
1329
|
-
`mw-info-clickable`.
|
|
1330
|
-
- `mw-info-block` is the same surface carrying a paragraph: 68 characters
|
|
1331
|
-
wide, left-aligned, and it does not lift on hover. It sits at the left edge
|
|
1332
|
-
like everything else with running text in it - `mw-info-block-center` puts it
|
|
1333
|
-
back in the middle of its container.
|
|
1334
|
-
- `mw-info-mini` is the 20px status dot: sizes `-sm`, `-lg`, `-xl`, colours
|
|
1335
|
-
`-primary`, `-secondary`, `-success`, `-warning`, `-danger`, `-info`.
|
|
1336
|
-
|
|
1337
|
-
## Progress
|
|
1338
|
-
|
|
1339
|
-
```html
|
|
1340
|
-
<div class="mw-progress-container">
|
|
1341
|
-
<div class="mw-progress-barinfo">
|
|
1342
|
-
<span class="mw-progress-label">Completion</span>
|
|
1343
|
-
<span class="mw-progress-value mw-progress-percent">75</span>
|
|
1344
|
-
</div>
|
|
1345
|
-
<div class="mw-progress-bar">
|
|
1346
|
-
<div class="mw-progress-fill mw-progress-primary" style="width: 75%"></div>
|
|
1347
|
-
</div>
|
|
1348
|
-
</div>
|
|
1349
|
-
```
|
|
1350
|
-
|
|
1351
|
-
- The value element only prints what you put in it; `mw-progress-percent`
|
|
1352
|
-
appends `%`, `mw-progress-numeric` appends `/10`. Neither computes anything.
|
|
1353
|
-
- The fill width is set by you (`style="width: 75%"`). The shipped JS instead
|
|
1354
|
-
reads `data-value="75"` and animates it into view - in a SPA bind the style.
|
|
1355
|
-
- Colours: `mw-progress-primary`, `-secondary`, `-success`, `-warning`,
|
|
1356
|
-
`-danger`, `-info` (default fill is the secondary colour).
|
|
1357
|
-
- Sizes on the container: `mw-progress-sm`, `mw-progress-lg`.
|
|
1358
|
-
- `mw-progress-hide-info` on the info row hides the labels while keeping the
|
|
1359
|
-
layout; `mw-progress-inline-label` on the container puts the text inside the
|
|
1360
|
-
bar.
|
|
1361
|
-
|
|
1362
|
-
## Rating
|
|
1363
|
-
|
|
1364
|
-
```html
|
|
1365
|
-
<div class="mw-rating-container">
|
|
1366
|
-
<p class="mw-rating-info">Product rating</p>
|
|
1367
|
-
<div class="mw-rating mw-rating-lg" data-rating="4">
|
|
1368
|
-
<i class="mw-rating-icon fas fa-thumbs-up" aria-hidden="true"></i>
|
|
1369
|
-
<!-- five icons total -->
|
|
1370
|
-
</div>
|
|
1371
|
-
</div>
|
|
1372
|
-
```
|
|
1373
|
-
|
|
1374
|
-
`data-rating` is `0`-`5` and highlights the first N icons - pure CSS, any icon
|
|
1375
|
-
works. Sizes: `mw-rating-sm`, `mw-rating-lg`.
|
|
1376
|
-
|
|
1377
|
-
## Meta header
|
|
1378
|
-
|
|
1379
|
-
Small icon + text counters, e.g. under a page title.
|
|
1380
|
-
|
|
1381
|
-
```html
|
|
1382
|
-
<div class="mw-meta-header">
|
|
1383
|
-
<div class="mw-meta-item">
|
|
1384
|
-
<i class="fas fa-list"></i><span>Total: 14</span>
|
|
1385
|
-
</div>
|
|
1386
|
-
<div class="mw-meta-item">
|
|
1387
|
-
<i class="fas fa-pen"></i><span>Drafts: 3</span>
|
|
1388
|
-
</div>
|
|
1389
|
-
</div>
|
|
1390
|
-
```
|
|
1391
|
-
|
|
1392
|
-
## Stepper
|
|
1393
|
-
|
|
1394
|
-
```html
|
|
1395
|
-
<div class="mw-stepper">
|
|
1396
|
-
<div class="mw-stepper-step">
|
|
1397
|
-
<div class="mw-stepper-indicator mw-done"><i class="fas fa-check"></i></div>
|
|
1398
|
-
<span class="mw-stepper-label mw-done">Account</span>
|
|
1399
|
-
</div>
|
|
1400
|
-
<div class="mw-stepper-connector mw-done"></div>
|
|
1401
|
-
<div class="mw-stepper-step">
|
|
1402
|
-
<div class="mw-stepper-indicator mw-active">2</div>
|
|
1403
|
-
<span class="mw-stepper-label mw-active">Details</span>
|
|
1404
|
-
</div>
|
|
1405
|
-
<div class="mw-stepper-connector"></div>
|
|
1406
|
-
<div class="mw-stepper-step">
|
|
1407
|
-
<div class="mw-stepper-indicator">3</div>
|
|
1408
|
-
<span class="mw-stepper-label">Review</span>
|
|
1409
|
-
</div>
|
|
1410
|
-
</div>
|
|
1411
|
-
```
|
|
1412
|
-
|
|
1413
|
-
State classes here are prefixed: `mw-done`, `mw-active`. The horizontal variant
|
|
1414
|
-
needs the explicit `mw-stepper-connector` elements between the steps; the
|
|
1415
|
-
vertical one (`mw-stepper-vertical`) draws the line itself and takes
|
|
1416
|
-
`mw-stepper-content` with `mw-stepper-content-title` / `-desc` instead of a
|
|
1417
|
-
label:
|
|
1418
|
-
|
|
1419
|
-
```html
|
|
1420
|
-
<div class="mw-stepper-vertical">
|
|
1421
|
-
<div class="mw-stepper-step mw-done">
|
|
1422
|
-
<div class="mw-stepper-indicator mw-done"><i class="fas fa-check"></i></div>
|
|
1423
|
-
<div class="mw-stepper-content">
|
|
1424
|
-
<p class="mw-stepper-content-title">Dependencies installed</p>
|
|
1425
|
-
<p class="mw-stepper-content-desc">All packages resolved.</p>
|
|
1426
|
-
</div>
|
|
1427
|
-
</div>
|
|
1428
|
-
</div>
|
|
1429
|
-
```
|
|
1430
|
-
|
|
1431
|
-
## Timelines
|
|
1432
|
-
|
|
1433
|
-
Both are chronicles (CV, changelog), not schedulable time axes.
|
|
1434
|
-
|
|
1435
|
-
```html
|
|
1436
|
-
<div class="mw-timeline-big">
|
|
1437
|
-
<div class="mw-timeline-big-step mw-active">
|
|
1438
|
-
<div class="mw-timeline-big-date-container">
|
|
1439
|
-
<div class="mw-timeline-big-date-main">01/2023 - today</div>
|
|
1440
|
-
<div class="mw-timeline-big-date-sub">(3 years)</div>
|
|
1441
|
-
</div>
|
|
1442
|
-
<div class="mw-timeline-big-content">
|
|
1443
|
-
<div class="mw-card">...</div>
|
|
1444
|
-
<div class="mw-timeline-big-event">
|
|
1445
|
-
<span class="mw-timeline-big-event-date">05/2023</span>
|
|
1446
|
-
<span class="mw-timeline-big-event-title">Certification</span>
|
|
1447
|
-
</div>
|
|
1448
|
-
</div>
|
|
1449
|
-
</div>
|
|
1450
|
-
</div>
|
|
1451
|
-
|
|
1452
|
-
<div class="mw-timeline-simple">
|
|
1453
|
-
<div class="mw-timeline-simple-step mw-active">
|
|
1454
|
-
<div class="mw-timeline-simple-date">2024-03-22</div>
|
|
1455
|
-
<div class="mw-timeline-simple-content">
|
|
1456
|
-
<div class="mw-card mw-card-simple">...</div>
|
|
1457
|
-
</div>
|
|
1458
|
-
</div>
|
|
1459
|
-
</div>
|
|
1460
|
-
```
|
|
1461
|
-
|
|
1462
|
-
The current step gets `mw-active` (bare `active` still works but is
|
|
1463
|
-
deprecated). Any card fits into the content wrapper.
|
|
1464
|
-
|
|
1465
|
-
## Avatars
|
|
1466
|
-
|
|
1467
|
-
```html
|
|
1468
|
-
<div class="mw-avatar mw-avatar-lg mw-avatar-primary">
|
|
1469
|
-
<img src="me.jpg" alt="" />
|
|
1470
|
-
</div>
|
|
1471
|
-
|
|
1472
|
-
<div class="mw-avatar mw-avatar-initials mw-avatar-xs">MW</div>
|
|
1473
|
-
```
|
|
1474
|
-
|
|
1475
|
-
- Sizes: `mw-avatar-xs` 32, `-sm` 64, default 96, `-lg` 144, `-xl` 210 px.
|
|
1476
|
-
- Shape: `mw-avatar-square`.
|
|
1477
|
-
- `mw-avatar-landscape` (3:2) and `mw-avatar-portrait` (9:16) for a photo that
|
|
1478
|
-
is not square - meant for `-lg` and `-xl`, with 20px corners that
|
|
1479
|
-
`mw-avatar-square` still sharpens. Both run larger than the round step:
|
|
1480
|
-
landscape `-lg` 273, `-xl` 399 px wide, shrinking to fit a narrower column;
|
|
1481
|
-
portrait `-xl` 242 px wide at 9:16, `-lg` 2:3 at 295 px tall.
|
|
1482
|
-
- Every avatar carries the accent arc on its top-right corner, inside the frame
|
|
1483
|
-
(`--mw-corner-accent`). It stays as it is on hover.
|
|
1484
|
-
- Colours `mw-avatar-primary`, `-secondary`, `-success`, `-warning`, `-danger`,
|
|
1485
|
-
`-info` tint the **border**. On `mw-avatar-initials` the same class also tints
|
|
1486
|
-
the **surface**, so both can be combined.
|
|
1487
|
-
- `mw-avatar-initials` centers text and scales the font with the size class -
|
|
1488
|
-
the standard display for a signed-in user without a picture.
|
|
1489
|
-
- `mw-avatar-group` overlaps its avatars; `mw-avatar-group-sm` / `-lg` change
|
|
1490
|
-
the overlap.
|
|
1491
|
-
- Hover on an avatar **with an image** zooms the picture and sweeps a light
|
|
1492
|
-
across it. Nothing to switch on. It is skipped on `mw-avatar-initials`, on
|
|
1493
|
-
touch, under `prefers-reduced-motion` and under `mw-hover-static`.
|
|
1494
|
-
|
|
1495
|
-
## Date stamp
|
|
1496
|
-
|
|
1497
|
-
The date on a dated card - an event, a changelog entry, a day on a menu. It
|
|
1498
|
-
answers to the **card**, not the viewport: narrow, it is a muted line above the
|
|
1499
|
-
title; once the card is wide enough that a single column would leave its right
|
|
1500
|
-
half empty, the same markup becomes a column on the left.
|
|
1501
|
-
|
|
1502
|
-
```html
|
|
1503
|
-
<article class="mw-card mw-offer">
|
|
1504
|
-
<div class="mw-card-body">
|
|
1505
|
-
<p class="mw-date-stamp mw-date-stamp-today">
|
|
1506
|
-
<span class="mw-date-stamp-day">Fri</span>
|
|
1507
|
-
<span class="mw-date-stamp-date">18.09.</span>
|
|
1508
|
-
</p>
|
|
1509
|
-
<div class="mw-date-stamp-body">
|
|
1510
|
-
<h4 class="mw-text-base mw-text-bold mw-mb-2">Baked fish fillet</h4>
|
|
1511
|
-
<p class="mw-text-sm mw-text-muted mw-mb-0">In beer batter.</p>
|
|
1512
|
-
</div>
|
|
1513
|
-
<p class="mw-price mw-price-xs">
|
|
1514
|
-
<span class="mw-price-amount">11.90</span>
|
|
1515
|
-
<span class="mw-price-currency">Euro</span>
|
|
1516
|
-
</p>
|
|
1517
|
-
</div>
|
|
1518
|
-
</article>
|
|
1519
|
-
```
|
|
1520
|
-
|
|
1521
|
-
- `mw-date-stamp-body` wraps everything that is not the stamp or the price. It
|
|
1522
|
-
is `display: contents` on a narrow card, so the body still sees the title and
|
|
1523
|
-
the price as its own children - which is what `mw-offer` selects on. On a wide
|
|
1524
|
-
card it becomes the middle column.
|
|
1525
|
-
- The price is a **sibling** of the wrapper, not inside it. Give the card one
|
|
1526
|
-
and it gets a third column at the right edge; leave it out and the card is two
|
|
1527
|
-
columns. With more than one dish per card, put the price on a
|
|
1528
|
-
`mw-leader-row` with the dish instead - an empty third track would still
|
|
1529
|
-
charge the card a gap.
|
|
1530
|
-
- Write the day as `Fri`, not `FRI` - the uppercase is `text-transform`, so a
|
|
1531
|
-
screen reader still reads a word rather than three letters.
|
|
1532
|
-
- Day column width, day font size, the column gap and the card's block padding
|
|
1533
|
-
all ramp with the **card**, from 420px to 900px wide, via
|
|
1534
|
-
`fluid-container()`. A narrow card in a wide window gets narrow-card sizing -
|
|
1535
|
-
which is the point of putting the switch in a container query at all.
|
|
1536
|
-
- `mw-date-stamp-today` drops the muting for a **fill**: on a wide card it spans
|
|
1537
|
-
the full card height, rounded to the card's own left corners and cut square
|
|
1538
|
-
against the content. A text colour would compete with the price, which is
|
|
1539
|
-
already the brand colour.
|
|
1540
|
-
- Cards in a stack want `mw-grid-even` next to `mw-grid-1`, or the card with the
|
|
1541
|
-
longer description ends up taller than the rest of the week.
|
|
1542
|
-
|
|
1543
|
-
## Contact
|
|
1544
|
-
|
|
1545
|
-
How to reach someone - postal address, phone, mail. It belongs on an
|
|
1546
|
-
`<address>`, and undoing that element's italics is most of what the component
|
|
1547
|
-
does; the rest is a label column wide enough that the values line up.
|
|
1548
|
-
|
|
1549
|
-
```html
|
|
1550
|
-
<address class="mw-contact">
|
|
1551
|
-
<p class="mw-text-bold">Gasthaus Lamm</p>
|
|
1552
|
-
<p>Marktplatz 3<br />72622 Musterhausen</p>
|
|
1553
|
-
<p class="mw-contact-row">
|
|
1554
|
-
<span class="mw-contact-label">Phone</span>
|
|
1555
|
-
<a class="mw-link" href="tel:+497123456789">07123 456789</a>
|
|
1556
|
-
</p>
|
|
1557
|
-
</address>
|
|
1558
|
-
```
|
|
1559
|
-
|
|
1560
|
-
- `<address>` is for the contact details of its nearest `<article>` or of the
|
|
1561
|
-
page - not for any postal address that happens to be on it.
|
|
1562
|
-
- The label column is 5rem and fixed. An auto column is as wide as its own word,
|
|
1563
|
-
so "Phone" and "Email" would start their values in different places.
|
|
1564
|
-
- Wrap phone and mail in `tel:` and `mailto:` links and mark them `mw-link` -
|
|
1565
|
-
in a block of plain lines an unstyled link reads as text.
|
|
1566
|
-
- Opening hours are **not** part of this. A day, its times and a line between
|
|
1567
|
-
them is a `mw-leader-row` in a `<dl>`, one row per day, and a note for the
|
|
1568
|
-
rest.
|
|
1569
|
-
|
|
1570
|
-
## Item lists
|
|
1571
|
-
|
|
1572
|
-
Layout containers for arbitrary children - the children need no classes.
|
|
1573
|
-
|
|
1574
|
-
| Class | Look |
|
|
1575
|
-
| ------------------------- | ------------------------------------------------------------- |
|
|
1576
|
-
| `mw-item-list` | Plain flex column with gaps |
|
|
1577
|
-
| `mw-item-list-horizontal` | Row, wrapping; column below `sm` |
|
|
1578
|
-
| `mw-item-list-cards` | Wider gaps, for cards |
|
|
1579
|
-
| `mw-item-list-compact` | Bordered card-surface box, divided rows, hover indent (nests) |
|
|
1580
|
-
| `mw-item-list-menu` | Elevated menu card, pointer cursor, press feedback |
|
|
1581
|
-
| `mw-item-list-slide` | Cards sliding sideways with an accent border on hover |
|
|
1582
|
-
| `mw-item-list-scroll` | Scroll box, 300px tall |
|
|
1583
|
-
|
|
1584
|
-
Modifiers are additive - keep the base class and add the modifier:
|
|
1585
|
-
`mw-item-list-scroll` + `-sm` (200px), `-lg` (400px) or `-xl` (500px), and
|
|
1586
|
-
`mw-item-list-slide` + `-secondary` for the secondary accent.
|
|
1587
|
-
|
|
1588
|
-
```html
|
|
1589
|
-
<div class="mw-item-list-compact">
|
|
1590
|
-
<div>Quick settings</div>
|
|
1591
|
-
<div>
|
|
1592
|
-
Privacy
|
|
1593
|
-
<div class="mw-item-list-compact">
|
|
1594
|
-
<div>Cookies</div>
|
|
1595
|
-
</div>
|
|
1596
|
-
</div>
|
|
1597
|
-
</div>
|
|
1598
|
-
```
|
|
1599
|
-
|
|
1600
|
-
**Checkbox lists** are the one variant with required inner markup - a `<ul>` of
|
|
1601
|
-
`<li>` each holding a `mw-checkbox`:
|
|
1602
|
-
|
|
1603
|
-
```html
|
|
1604
|
-
<ul class="mw-item-list-checkbox">
|
|
1605
|
-
<li class="mw-selected">
|
|
1606
|
-
<label class="mw-checkbox">
|
|
1607
|
-
<input type="checkbox" checked />
|
|
1608
|
-
<span class="mw-checkbox-box"></span>
|
|
1609
|
-
<div class="mw-checkbox-content">
|
|
1610
|
-
<h4 class="mw-checkbox-header">Task management</h4>
|
|
1611
|
-
<p class="mw-checkbox-label">Track your daily tasks</p>
|
|
1612
|
-
</div>
|
|
1613
|
-
</label>
|
|
1614
|
-
</li>
|
|
1615
|
-
</ul>
|
|
1616
|
-
```
|
|
1617
|
-
|
|
1618
|
-
Add `mw-item-list-checkbox-compact`, `-large` or `-secondary` alongside the
|
|
1619
|
-
base class; `mw-item-list-checkbox-scroll` is a standalone class that replaces
|
|
1620
|
-
it. `mw-selected` on the `<li>` is the selected-row highlight - bind it to the
|
|
1621
|
-
checkbox state yourself in a SPA. The `mw-checkbox-content` block is optional;
|
|
1622
|
-
a bare `mw-checkbox-label` renders a single-line row.
|
|
1623
|
-
|
|
1624
|
-
The `mw-checkbox` inside is the ordinary form-element checkbox, so
|
|
1625
|
-
`mw-checkbox-disabled` and the colour variants (`mw-checkbox-success` and the
|
|
1626
|
-
rest, see `references/forms.md`) work here too. Row height is a list concern -
|
|
1627
|
-
use the list's own `-compact` / `-large` modifiers rather than
|
|
1628
|
-
`mw-checkbox-sm` / `-lg`, which sit on the wrong element to reach the box.
|
|
1629
|
-
|
|
1630
|
-
## Leader row
|
|
1631
|
-
|
|
1632
|
-
A label on the left, its value on the right, a dotted line bridging the gap -
|
|
1633
|
-
the row a menu, a table of contents and an invoice all draw. The markup is two
|
|
1634
|
-
spans; the line is the row's own `::before`, so it stays out of the
|
|
1635
|
-
accessibility tree and no filler element needs `aria-hidden`.
|
|
1636
|
-
|
|
1637
|
-
```html
|
|
1638
|
-
<p class="mw-leader-row mw-leader-row-top">
|
|
1639
|
-
<span class="mw-leader-row-label mw-text-bold">Roast pork with dumpling</span>
|
|
1640
|
-
<span class="mw-leader-row-value mw-text-bold mw-text-primary">17.50</span>
|
|
1641
|
-
</p>
|
|
1642
|
-
```
|
|
1643
|
-
|
|
1644
|
-
- Label and value is what `<dl>` is for, and since HTML 5.2 a `<div>` may group
|
|
1645
|
-
a `<dt>` and its `<dd>` inside one - so the semantic version costs no extra
|
|
1646
|
-
element over the `<p>`. Put `mw-leader-row` on that `<div>`. Use it where the
|
|
1647
|
-
pair **is** the content; where every row also carries a description, a `<p>`
|
|
1648
|
-
stays simpler.
|
|
1649
|
-
- `mw-leader-row-top` keeps the value beside the **first** line of a wrapping
|
|
1650
|
-
label instead of the last. On a menu that is where the price belongs - next to
|
|
1651
|
-
the dish, not somewhere down in its description.
|
|
1652
|
-
- A second `mw-leader-row-value` needs no extra class. Both sit after the
|
|
1653
|
-
leader, with a mark in the leader's colour between them - "11.30 - 14.00 |
|
|
1654
|
-
17.00 - 23.00". Without it the pair reads as one long range.
|
|
1655
|
-
- Values are set in tabular figures and never break, so a column of them lines
|
|
1656
|
-
up. The label wraps between its words and stops at its longest one; past that
|
|
1657
|
-
the whole row wraps, with the values dropping under the label. Nothing shrinks
|
|
1658
|
-
to one letter per line.
|
|
1659
|
-
- Keep `mw-price` out of a leader row: it sets its own baseline and the leader
|
|
1660
|
-
would no longer meet it. Plain text in the value span, or the price in a
|
|
1661
|
-
column of its own.
|
|
1662
|
-
|
|
1663
|
-
`mw-leader-row-dashed` and `mw-leader-row-solid` swap the line style; a single
|
|
1664
|
-
row retunes with `--mw-leader-row-color`, `--mw-leader-row-style` and
|
|
1665
|
-
`--mw-leader-row-width`.
|
|
1666
|
-
|
|
1667
|
-
## HTML lists
|
|
1668
|
-
|
|
1669
|
-
`mw-list` is the base (spacing for `li`), combined with one of:
|
|
1670
|
-
`mw-list-unstyled`, `mw-list-inline`, `mw-list-link` (underlined anchors),
|
|
1671
|
-
`mw-list-dot` (coloured markers), `mw-list-check` (✓ prefix).
|
|
1672
|
-
|
|
1673
|
-
```html
|
|
1674
|
-
<ul class="mw-list mw-list-check">
|
|
1675
|
-
<li>Done</li>
|
|
1676
|
-
</ul>
|
|
1677
|
-
```
|
|
1678
|
-
|
|
1679
|
-
## Breadcrumbs
|
|
1680
|
-
|
|
1681
|
-
```html
|
|
1682
|
-
<nav class="mw-breadcrumbs mw-breadcrumbs-collapse" aria-label="Breadcrumb">
|
|
1683
|
-
<ol class="mw-breadcrumbs-list">
|
|
1684
|
-
<li class="mw-breadcrumbs-item">
|
|
1685
|
-
<a href="/"><i class="fas fa-home"></i></a>
|
|
1686
|
-
</li>
|
|
1687
|
-
<li class="mw-breadcrumbs-item">
|
|
1688
|
-
<a href="/invoices"><span>Invoices</span></a>
|
|
1689
|
-
</li>
|
|
1690
|
-
<li class="mw-breadcrumbs-item mw-breadcrumbs-current">
|
|
1691
|
-
<a href="#" class="mw-breadcrumbs-active"><span>2026-001</span></a>
|
|
1692
|
-
</li>
|
|
1693
|
-
</ol>
|
|
1694
|
-
</nav>
|
|
1695
|
-
```
|
|
1696
|
-
|
|
1697
|
-
The last item takes `mw-breadcrumbs-current` on the `li` and
|
|
1698
|
-
`mw-breadcrumbs-active` on the anchor. `mw-breadcrumbs-sm` is the small variant,
|
|
1699
|
-
`mw-breadcrumbs-collapse` hides middle items on narrow screens.
|
|
1700
|
-
|
|
1701
|
-
## Pagination
|
|
1702
|
-
|
|
1703
|
-
A titled content frame with prev/next controls - not a page-number list. Same
|
|
1704
|
-
surface and header as `mw-calendar`; only the body differs, and the body is
|
|
1705
|
-
yours.
|
|
1706
|
-
|
|
1707
|
-
```html
|
|
1708
|
-
<div class="mw-pagination">
|
|
1709
|
-
<div class="mw-pagination-header">
|
|
1710
|
-
<button
|
|
1711
|
-
type="button"
|
|
1712
|
-
class="mw-btn mw-btn-outline mw-btn-sm mw-pagination-nav"
|
|
1713
|
-
aria-label="Previous week"
|
|
1714
|
-
>
|
|
1715
|
-
<i class="fas fa-chevron-left"></i>
|
|
1716
|
-
</button>
|
|
1717
|
-
|
|
1718
|
-
<div class="mw-pagination-title" aria-live="polite">Week 10</div>
|
|
1719
|
-
|
|
1720
|
-
<button
|
|
1721
|
-
type="button"
|
|
1722
|
-
class="mw-btn mw-btn-outline mw-btn-sm mw-pagination-nav"
|
|
1723
|
-
aria-label="Next week"
|
|
1724
|
-
>
|
|
1725
|
-
<i class="fas fa-chevron-right"></i>
|
|
1726
|
-
</button>
|
|
1727
|
-
</div>
|
|
1728
|
-
|
|
1729
|
-
<div class="mw-pagination-content">...</div>
|
|
1730
|
-
</div>
|
|
1731
|
-
```
|
|
1732
|
-
|
|
1733
|
-
- `mw-pagination-nav` sits **on** `mw-btn` - it only squares the button off
|
|
1734
|
-
around the chevron. The arrows carry no accessible name of their own, so give
|
|
1735
|
-
each one an `aria-label`; `aria-live="polite"` on the title is what tells a
|
|
1736
|
-
screen reader where you landed.
|
|
1737
|
-
- `mw-pagination-loading` goes on `mw-pagination-content` (not on the
|
|
1738
|
-
container) and dims it while the next page is on its way.
|
|
1739
|
-
- `mw-pagination-slide-left` / `-slide-right` are the directional transitions,
|
|
1740
|
-
also on the content.
|
|
1741
|
-
- The body has a 200px floor so a short page does not collapse the frame. Paging
|
|
1742
|
-
is application state - the framework ships no JavaScript for it.
|
|
1743
|
-
|
|
1744
|
-
## Divider
|
|
1745
|
-
|
|
1746
|
-
```html
|
|
1747
|
-
<hr class="mw-divider mw-divider-dashed" />
|
|
1748
|
-
<div class="mw-divider-text"><span>or</span></div>
|
|
1749
|
-
<div class="mw-divider-vertical"></div>
|
|
1750
|
-
```
|
|
1751
|
-
|
|
1752
|
-
Variants: `mw-divider-thick`, `mw-divider-dashed`, `mw-divider-primary`,
|
|
1753
|
-
`mw-divider-secondary`. `mw-divider-text` is a labelled rule (the colour
|
|
1754
|
-
variants apply to it too), `mw-divider-vertical` needs a flex row with a height.
|
|
1755
|
-
|
|
1756
|
-
## Gallery & image slider
|
|
1757
|
-
|
|
1758
|
-
```html
|
|
1759
|
-
<div class="mw-gallery-container">
|
|
1760
|
-
<div class="mw-gallery">
|
|
1761
|
-
<button type="button" class="mw-gallery-navi mw-gallery-navi-prev">
|
|
1762
|
-
❮
|
|
1763
|
-
</button>
|
|
1764
|
-
<div class="mw-gallery-track">
|
|
1765
|
-
<div class="mw-gallery-slide" data-desc="Mountains">
|
|
1766
|
-
<img src="1.jpg" alt="" />
|
|
1767
|
-
</div>
|
|
1768
|
-
</div>
|
|
1769
|
-
<button type="button" class="mw-gallery-navi mw-gallery-navi-next">
|
|
1770
|
-
❯
|
|
1771
|
-
</button>
|
|
1772
|
-
</div>
|
|
1773
|
-
<div class="mw-gallery-desc">Mountains</div>
|
|
1774
|
-
<div class="mw-gallery-dots"></div>
|
|
1775
|
-
</div>
|
|
1776
|
-
```
|
|
1777
|
-
|
|
1778
|
-
The dots (`mw-gallery-dot`, active one gets `mw-active`) are generated by the
|
|
1779
|
-
shipped JS, as is the track transform. In a SPA, render the dots and set
|
|
1780
|
-
`transform: translateX(...)` yourself. Swipe is single-finger and decided by the
|
|
1781
|
-
dominant axis, so a page scroll that drifts sideways does not page the gallery.
|
|
1782
|
-
|
|
1783
|
-
`mw-gallery-container` is a query container (`mw-gallery`), and the caption size,
|
|
1784
|
-
the dot size and the 4:3 crop follow _its_ width - 688px and 512px - not the
|
|
1785
|
-
viewport. A gallery in a half-width column therefore takes its narrow layout
|
|
1786
|
-
there. Keep the wrapper: a container query only reaches descendants, and the
|
|
1787
|
-
caption and the dots are siblings of `mw-gallery`.
|
|
1788
|
-
|
|
1789
|
-
`mw-image-slider` swaps a base image against overlays through indexed buttons:
|
|
1790
|
-
|
|
1791
|
-
```html
|
|
1792
|
-
<div class="mw-image-slider">
|
|
1793
|
-
<div class="mw-image-slider-base" data-index="0">
|
|
1794
|
-
<img class="mw-image-slider-base-image" src="base.jpg" alt="" />
|
|
1795
|
-
</div>
|
|
1796
|
-
<div class="mw-image-slider-overlay">
|
|
1797
|
-
<div class="mw-image-slider-overlay-image mw-active" data-index="1">
|
|
1798
|
-
<img src="a.jpg" alt="" />
|
|
1799
|
-
</div>
|
|
1800
|
-
</div>
|
|
1801
|
-
<div class="mw-image-slider-controls-grid-3">
|
|
1802
|
-
<button class="mw-btn mw-btn-primary mw-active" data-index="0">Base</button>
|
|
1803
|
-
</div>
|
|
1804
|
-
</div>
|
|
1805
|
-
```
|
|
1806
|
-
|
|
1807
|
-
Control grids: `mw-image-slider-controls-grid-2`, `-grid-3`. Visible overlay and
|
|
1808
|
-
current button carry `active`. `mw-image-slider` is its own query container
|
|
1809
|
-
(`mw-slider`): the three heights (250 / 320 / 370px) and the control columns
|
|
1810
|
-
switch at 420px and 560px of slider width, because the slider caps at 600px and
|
|
1811
|
-
past a tablet the viewport says nothing about the room it has.
|
|
1812
|
-
|
|
1813
|
-
## Mosaic
|
|
1814
|
-
|
|
1815
|
-
Photos of any shape in one grid; a tap opens the lightbox.
|
|
1816
|
-
|
|
1817
|
-
```html
|
|
1818
|
-
<div class="mw-mosaic">
|
|
1819
|
-
<figure class="mw-mosaic-item mw-mosaic-big">
|
|
1820
|
-
<a href="ridge-full.jpg">
|
|
1821
|
-
<img
|
|
1822
|
-
src="ridge.jpg"
|
|
1823
|
-
alt="Mountain range at sunrise"
|
|
1824
|
-
width="800"
|
|
1825
|
-
height="450"
|
|
1826
|
-
loading="lazy"
|
|
1827
|
-
/>
|
|
1828
|
-
</a>
|
|
1829
|
-
<figcaption>First light on the ridge.</figcaption>
|
|
1830
|
-
</figure>
|
|
1831
|
-
<figure class="mw-mosaic-item">…</figure>
|
|
1832
|
-
</div>
|
|
1833
|
-
```
|
|
1834
|
-
|
|
1835
|
-
- Two columns on a phone, four at most, from the width of the box it sits in.
|
|
1836
|
-
- The script shapes each tile from the photo's proportions - the `width` and
|
|
1837
|
-
`height` attributes, else the natural size once loaded: `mw-mosaic-wide` from
|
|
1838
|
-
1.3:1, `mw-mosaic-tall` from 1:1.25, square otherwise. A shape class set by
|
|
1839
|
-
hand wins; `mw-mosaic-big` (2×2) is only ever set by hand. Without the script
|
|
1840
|
-
every tile is square.
|
|
1841
|
-
- `grid-auto-flow: dense` fills the holes and moves tiles away from their source
|
|
1842
|
-
order. `reading-flow: grid-rows` makes Tab follow the eye where the browser
|
|
1843
|
-
has it (Chromium so far).
|
|
1844
|
-
- Gaps: `mw-mosaic-gap-sm`, `-gap-lg`.
|
|
1845
|
-
- The tile crops, the lightbox does not. The `figcaption` is hidden in the tile
|
|
1846
|
-
and shown in the lightbox.
|
|
1847
|
-
|
|
1848
|
-
### Lightbox
|
|
1849
|
-
|
|
1850
|
-
A click on a link that holds an image - inside `mw-mosaic` or any element with
|
|
1851
|
-
`data-mw-lightbox` - opens the photo full screen instead of following the link.
|
|
1852
|
-
The script builds the viewer once, a `<dialog class="mw-lightbox">`, with one
|
|
1853
|
-
slide per image of the group in reading order, the full image from the link's
|
|
1854
|
-
`href` and the caption copied from the `figcaption`. Swipe, arrow keys or the
|
|
1855
|
-
arrows page through it; Escape, the cross or a click on the stage beside the
|
|
1856
|
-
photo close it, and focus returns to the tile of the last photo viewed. A click
|
|
1857
|
-
with a modifier key stays a link.
|
|
1858
|
-
|
|
1859
|
-
Without `main.js`, build it yourself: a `<dialog class="mw-lightbox">` holding
|
|
1860
|
-
`mw-lightbox-bar` (with `mw-lightbox-prev`, `mw-lightbox-count`,
|
|
1861
|
-
`mw-lightbox-next` and `mw-lightbox-close`, all `mw-overlay-btn`) and
|
|
1862
|
-
`mw-lightbox-track` with one `<figure class="mw-lightbox-slide">` per photo. The
|
|
1863
|
-
track is a scroll-snap strip; the page scroll is locked while the dialog is open.
|
|
1864
|
-
|
|
1865
|
-
## Portrait gallery
|
|
1866
|
-
|
|
1867
|
-
```html
|
|
1868
|
-
<div class="mw-portrait-gallery">
|
|
1869
|
-
<div
|
|
1870
|
-
class="mw-portrait-gallery-track"
|
|
1871
|
-
tabindex="0"
|
|
1872
|
-
aria-label="Portrait photos"
|
|
1873
|
-
>
|
|
1874
|
-
<figure class="mw-portrait-gallery-slide">
|
|
1875
|
-
<img
|
|
1876
|
-
src="cape.jpg"
|
|
1877
|
-
alt="Lighthouse on a cliff at dusk"
|
|
1878
|
-
width="600"
|
|
1879
|
-
height="800"
|
|
1880
|
-
loading="lazy"
|
|
1881
|
-
/>
|
|
1882
|
-
<figcaption>
|
|
1883
|
-
<strong>North Cape</strong>
|
|
1884
|
-
The lamp comes on just after the sun goes.
|
|
1885
|
-
</figcaption>
|
|
1886
|
-
</figure>
|
|
1887
|
-
</div>
|
|
1888
|
-
</div>
|
|
1889
|
-
```
|
|
1890
|
-
|
|
1891
|
-
- Upright 3:4 slides in a native scroll-snap strip, the next one peeking in.
|
|
1892
|
-
One slide below 560px of gallery width, two and a quarter from there, three
|
|
1893
|
-
from 880px - a container query, so a gallery in a narrow column stays at one.
|
|
1894
|
-
The height is capped at 78svh, which a landscape phone needs.
|
|
1895
|
-
- `mw-portrait-gallery-tall`: 9:16, for stories, reels and screenshots. A
|
|
1896
|
-
`<video>` works in place of an `<img>`.
|
|
1897
|
-
- Gaps: `mw-portrait-gallery-gap-sm`, `-gap-lg`.
|
|
1898
|
-
- The script adds the arrows (`mw-portrait-gallery-prev` / `-next`, for a mouse
|
|
1899
|
-
only) and the dots (`mw-portrait-gallery-dots` / `-dot`, below 560px, the
|
|
1900
|
-
current one `mw-active`).
|
|
1901
|
-
- A photo more than 1.4× off the slide's shape is shown whole over a blurred
|
|
1902
|
-
copy of itself instead of cropped: the script sets `mw-fit-contain` on the
|
|
1903
|
-
slide and prepends a `mw-fit-backdrop` image, and checks again on every
|
|
1904
|
-
resize. `mw-fit-contain` set by hand forces it, `mw-fit-cover` on the slide
|
|
1905
|
-
turns it off.
|
|
1906
|
-
|
|
1907
|
-
## Photo feed
|
|
1908
|
-
|
|
1909
|
-
The three-column grid from a phone's photo app, in a framed box that scrolls.
|
|
1910
|
-
|
|
1911
|
-
```html
|
|
1912
|
-
<div class="mw-feed">
|
|
1913
|
-
<div class="mw-feed-item">
|
|
1914
|
-
<button type="button" popovertarget="post-cape">
|
|
1915
|
-
<img
|
|
1916
|
-
src="cape.jpg"
|
|
1917
|
-
alt="Lighthouse on a cliff at dusk"
|
|
1918
|
-
width="600"
|
|
1919
|
-
height="800"
|
|
1920
|
-
loading="lazy"
|
|
1921
|
-
/>
|
|
1922
|
-
</button>
|
|
1923
|
-
<figure class="mw-feed-post" id="post-cape" popover>
|
|
1924
|
-
<button
|
|
1925
|
-
type="button"
|
|
1926
|
-
class="mw-overlay-btn mw-feed-post-close"
|
|
1927
|
-
popovertarget="post-cape"
|
|
1928
|
-
popovertargetaction="hide"
|
|
1929
|
-
aria-label="Close"
|
|
1930
|
-
>
|
|
1931
|
-
𝗫
|
|
1932
|
-
</button>
|
|
1933
|
-
<img
|
|
1934
|
-
src="cape.jpg"
|
|
1935
|
-
alt="Lighthouse on a cliff at dusk"
|
|
1936
|
-
width="600"
|
|
1937
|
-
height="800"
|
|
1938
|
-
loading="lazy"
|
|
1939
|
-
/>
|
|
1940
|
-
<figcaption>
|
|
1941
|
-
<p>Stood there until the lamp came on.</p>
|
|
1942
|
-
<time datetime="2026-08-12">12 August 2026</time>
|
|
1943
|
-
</figcaption>
|
|
1944
|
-
</figure>
|
|
1945
|
-
</div>
|
|
1946
|
-
</div>
|
|
1947
|
-
```
|
|
1948
|
-
|
|
1949
|
-
- 3:4 tiles, 2px apart; `mw-feed-square` for square ones.
|
|
1950
|
-
- Past three rows the box scrolls, with a strip of the next row in view so it
|
|
1951
|
-
reads as scrollable. `mw-feed-rows-2` shows two rows, `mw-feed-plain` drops
|
|
1952
|
-
the frame.
|
|
1953
|
-
- No script: the tile is a button with `popovertarget`, the post a `popover`.
|
|
1954
|
-
In Angular that is `[attr.popovertarget]` and a matching `[id]`. A tile can be
|
|
1955
|
-
an `<a>` to a page of its own instead.
|
|
1956
|
-
- `time` and `small` in the caption are set as a muted line. The page scroll is
|
|
1957
|
-
locked while a post is open.
|
|
1958
|
-
|
|
1959
|
-
## Story archive
|
|
1960
|
-
|
|
1961
|
-
```html
|
|
1962
|
-
<div class="mw-stories" aria-label="Story archive">
|
|
1963
|
-
<button type="button" class="mw-story" popovertarget="story-coast">
|
|
1964
|
-
<span class="mw-story-cover"><img src="beach.jpg" alt="" /></span>
|
|
1965
|
-
<span class="mw-story-label">Coast</span>
|
|
1966
|
-
</button>
|
|
1967
|
-
<div class="mw-story-reel" id="story-coast" popover>
|
|
1968
|
-
<div class="mw-story-reel-head">
|
|
1969
|
-
<span>Coast</span>
|
|
1970
|
-
<label class="mw-overlay-btn mw-story-reel-pause"
|
|
1971
|
-
><input type="checkbox" aria-label="Pause"
|
|
1972
|
-
/></label>
|
|
1973
|
-
<button
|
|
1974
|
-
type="button"
|
|
1975
|
-
class="mw-overlay-btn"
|
|
1976
|
-
popovertarget="story-coast"
|
|
1977
|
-
popovertargetaction="hide"
|
|
1978
|
-
aria-label="Close"
|
|
1979
|
-
>
|
|
1980
|
-
𝗫
|
|
1981
|
-
</button>
|
|
1982
|
-
</div>
|
|
1983
|
-
<img src="beach.jpg" alt="Beach at sunset" loading="lazy" />
|
|
1984
|
-
<video
|
|
1985
|
-
src="sunrise.mp4"
|
|
1986
|
-
poster="sunrise.jpg"
|
|
1987
|
-
autoplay
|
|
1988
|
-
muted
|
|
1989
|
-
loop
|
|
1990
|
-
playsinline
|
|
1991
|
-
></video>
|
|
1992
|
-
</div>
|
|
1993
|
-
</div>
|
|
1994
|
-
```
|
|
1995
|
-
|
|
1996
|
-
- The row scrolls sideways, with a shadow on the side that has more behind it;
|
|
1997
|
-
a mouse gets a thin scrollbar.
|
|
1998
|
-
- Playback is CSS: every frame shows for five seconds, a segmented bar in the
|
|
1999
|
-
head counts them off, and holding the story (`:active`) or ticking the pause
|
|
2000
|
-
checkbox stops it. Full screen on a phone, phone-shaped (9:16) from `sm` up.
|
|
2001
|
-
- Twelve frames at most - the timing is written out for twelve. Later frames
|
|
2002
|
-
are hidden and the story ends after the twelfth.
|
|
2003
|
-
- Under `prefers-reduced-motion` nothing plays: the frames become a vertical
|
|
2004
|
-
strip to swipe, and the bar follows the scroll where the browser has scroll
|
|
2005
|
-
timelines.
|
|
2006
|
-
- The script adds tap zones (`mw-story-reel-prev` on the left 30%, `-next` on
|
|
2007
|
-
the rest), arrow keys, focus on the close button when the story opens, a video
|
|
2008
|
-
that starts when its frame comes up and stops with the story, and closes the
|
|
2009
|
-
story at its end.
|
|
2010
|
-
|
|
2011
|
-
## Overlay button
|
|
2012
|
-
|
|
2013
|
-
`mw-overlay-btn` is the round 44px button that sits on a photo - dark and
|
|
2014
|
-
translucent in both themes, with light ink and an inline `<svg>` sized for it.
|
|
2015
|
-
`:disabled` hides it. The lightbox, the portrait gallery, the feed post and the
|
|
2016
|
-
story reel use it for their close and arrow buttons.
|
|
2017
|
-
|
|
2018
|
-
## Blog post
|
|
2019
|
-
|
|
2020
|
-
```html
|
|
2021
|
-
<article class="mw-blog-post">
|
|
2022
|
-
<h2 class="mw-blog-post-title">Title</h2>
|
|
2023
|
-
<div class="mw-blog-post-header">
|
|
2024
|
-
<div class="mw-blog-post-header-icon"><i class="fas fa-code"></i></div>
|
|
2025
|
-
<div class="mw-blog-post-meta">
|
|
2026
|
-
<div class="mw-blog-post-meta-item">
|
|
2027
|
-
<i class="far fa-calendar"></i><span>2026-04-10</span>
|
|
2028
|
-
</div>
|
|
2029
|
-
</div>
|
|
2030
|
-
</div>
|
|
2031
|
-
<img class="mw-blog-post-featured-image" src="cover.jpg" alt="" />
|
|
2032
|
-
<div class="mw-blog-post-content-markdown">
|
|
2033
|
-
<!-- raw h3/p/ul/blockquote/pre from a markdown renderer -->
|
|
2034
|
-
</div>
|
|
2035
|
-
<div class="mw-blog-post-footer">
|
|
2036
|
-
<button class="mw-btn mw-blog-post-footer-button">
|
|
2037
|
-
<i class="fas fa-arrow-left"></i><span>Previous</span>
|
|
2038
|
-
</button>
|
|
2039
|
-
</div>
|
|
2040
|
-
</article>
|
|
2041
|
-
```
|
|
2042
|
-
|
|
2043
|
-
`mw-blog-post-content-markdown` styles unclassed HTML - the target for rendered
|
|
2044
|
-
markdown. `mw-blog-post-content` is the same wrapper for hand-written markup.
|
|
2045
|
-
|
|
2046
|
-
## Testimonial
|
|
2047
|
-
|
|
2048
|
-
A quote card with its source - a voice about the product. A `figure`, because
|
|
2049
|
-
quote and attribution belong together:
|
|
2050
|
-
|
|
2051
|
-
```html
|
|
2052
|
-
<figure class="mw-testimonial">
|
|
2053
|
-
<blockquote>
|
|
2054
|
-
The workshop picked me up after a long day of talking.
|
|
2055
|
-
</blockquote>
|
|
2056
|
-
<figcaption class="mw-testimonial-source">
|
|
2057
|
-
Anna M. <span class="mw-testimonial-detail">· voice workshop</span>
|
|
2058
|
-
<time class="mw-testimonial-date" datetime="2026-03">March 2026</time>
|
|
2059
|
-
</figcaption>
|
|
2060
|
-
</figure>
|
|
2061
|
-
```
|
|
2062
|
-
|
|
2063
|
-
The card carries the surface, so inside it the blockquote drops most of its
|
|
2064
|
-
tint and keeps only the edge and the quotation marks. `mw-testimonial-source`
|
|
2065
|
-
uses `margin-top: auto` - in a grid of equal-height cards every source line
|
|
2066
|
-
sits on its card's bottom edge. `mw-testimonial-featured` sets the quote a
|
|
2067
|
-
size larger, for the one voice lifted above the grid.
|
|
2068
|
-
|
|
2069
|
-
`mw-testimonial-date` is optional: a `<time>` on the right edge of the source
|
|
2070
|
-
line, month and year - a full date is review-platform optics, a bare year
|
|
2071
|
-
reads as a footnote. It drops to its own line, still right-aligned, when the
|
|
2072
|
-
source line runs out of room. Leave it out unless the voices are kept fresh: a
|
|
2073
|
-
dated quote ages, and three from years ago say more about the page than about
|
|
2074
|
-
the product. The `datetime` attribute is what Schema.org's `datePublished`
|
|
2075
|
-
would read.
|
|
2076
|
-
|
|
2077
|
-
For a wall of testimonials of unequal length, put them in `mw-columns-2` or
|
|
2078
|
-
`mw-columns-3` (`references/layout.md`) instead of a grid.
|
|
2079
|
-
|
|
2080
|
-
## Prose
|
|
2081
|
-
|
|
2082
|
-
Long-form running text - a legal page, terms, a plain article. One class on
|
|
2083
|
-
the container, bare `h2`/`h3`/`p`/`ul` inside:
|
|
2084
|
-
|
|
2085
|
-
```html
|
|
2086
|
-
<div class="mw-card mw-card-simple mw-prose">
|
|
2087
|
-
<h2>1. Scope</h2>
|
|
2088
|
-
<p>...</p>
|
|
2089
|
-
<h2>2. Contact</h2>
|
|
2090
|
-
<p>...</p>
|
|
2091
|
-
</div>
|
|
2092
|
-
```
|
|
2093
|
-
|
|
2094
|
-
Headings are deliberately small (`lg`/`md`, primary ink) - in a document they
|
|
2095
|
-
structure the reading rather than compete with the page title. First and last
|
|
2096
|
-
child lose their outer margins, so the container's own padding rules.
|
|
2097
|
-
|
|
2098
|
-
## Media
|
|
2099
|
-
|
|
2100
|
-
Self-hosted audio and video. The native controls are the player -
|
|
2101
|
-
`accent-color` already brands them - the wrapper only makes the element sit
|
|
2102
|
-
right:
|
|
2103
|
-
|
|
2104
|
-
```html
|
|
2105
|
-
<div class="mw-media">
|
|
2106
|
-
<video src="/media/intro.mp4" controls preload="metadata"></video>
|
|
2107
|
-
<p class="mw-media-caption">Two minutes about how a session works.</p>
|
|
2108
|
-
</div>
|
|
2109
|
-
```
|
|
2110
|
-
|
|
2111
|
-
`audio` and `video` become full-width blocks; a video also gets the surface
|
|
2112
|
-
silhouette and a resting shadow. Works with `<audio controls>` the same way.
|
|
2113
|
-
|
|
2114
|
-
## Code & terminal
|
|
2115
|
-
|
|
2116
|
-
```html
|
|
2117
|
-
<div class="mw-code-block">
|
|
2118
|
-
<div class="mw-code-block-header">
|
|
2119
|
-
<span class="mw-code-block-filename">main.kt</span>
|
|
2120
|
-
<span class="mw-code-block-language">Kotlin</span>
|
|
2121
|
-
</div>
|
|
2122
|
-
<div class="mw-code-container">
|
|
2123
|
-
<pre><code>fun main() {}</code></pre>
|
|
2124
|
-
</div>
|
|
2125
|
-
</div>
|
|
2126
|
-
|
|
2127
|
-
<div class="mw-terminal">
|
|
2128
|
-
<span class="mw-terminal-line">npm install maverick-wave</span>
|
|
2129
|
-
<span class="mw-terminal-line mw-terminal-output">added 1 package</span>
|
|
2130
|
-
</div>
|
|
2131
|
-
```
|
|
2132
|
-
|
|
2133
|
-
Token classes for manual highlighting: `mw-code-keyword`, `mw-code-function`,
|
|
2134
|
-
`mw-code-string`, `mw-code-number`, `mw-code-comment`, `mw-code-operator`. There
|
|
2135
|
-
is no highlighting engine - wrap spans yourself or plug in Prism/highlight.js.
|
|
2136
|
-
`mw-code-nowrap` disables wrapping. `<code>` on its own is styled as inline code.
|
|
2137
|
-
|
|
2138
|
-
## Tech stack bucket
|
|
2139
|
-
|
|
2140
|
-
```html
|
|
2141
|
-
<div class="mw-techstack-bucket mw-techstack-bucket-dashed">
|
|
2142
|
-
<a href="https://angular.dev" class="mw-techstack-item">
|
|
2143
|
-
<img class="mw-techstack-logo" src="angular.svg" alt="Angular" />
|
|
2144
|
-
<div class="mw-techstack-info">
|
|
2145
|
-
<span class="mw-techstack-name">Angular</span>
|
|
2146
|
-
<span class="mw-techstack-desc">Frontend</span>
|
|
2147
|
-
</div>
|
|
2148
|
-
</a>
|
|
2149
|
-
<a
|
|
2150
|
-
href="#"
|
|
2151
|
-
class="mw-techstack-item mw-techstack-item-sm"
|
|
2152
|
-
data-tooltip="Docker"
|
|
2153
|
-
>
|
|
2154
|
-
<img class="mw-techstack-logo" src="docker.svg" alt="Docker" />
|
|
2155
|
-
</a>
|
|
2156
|
-
</div>
|
|
2157
|
-
```
|
|
2158
|
-
|
|
2159
|
-
`mw-techstack-item-sm` is the logo-only chip; the info block is optional. On a
|
|
2160
|
-
coarse pointer the chip itself grows from 32px to 2.5rem - it often carries a
|
|
2161
|
-
`data-tooltip`, which owns the pseudo-element a grown hit area would need.
|
|
2162
|
-
|
|
2163
|
-
## Coming soon
|
|
2164
|
-
|
|
2165
|
-
```html
|
|
2166
|
-
<div class="mw-coming-soon">
|
|
2167
|
-
<i class="mw-coming-soon-icon fas fa-tools"></i>
|
|
2168
|
-
<div class="mw-coming-soon-text">Coming soon</div>
|
|
2169
|
-
</div>
|
|
2170
|
-
```
|