@moodlehq/design-system 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +16 -6
  2. package/dist/components/_index.legacy.scss +1893 -0
  3. package/dist/components/activity-icon/ActivityIcon.d.ts +3 -3
  4. package/dist/components/activity-icon/ActivityIcon.js +5 -5
  5. package/dist/components/activity-icon/ActivityIcon.js.map +1 -1
  6. package/dist/components/activity-icon/index.css +99 -0
  7. package/dist/components/badge/Badge.d.ts +1 -1
  8. package/dist/components/badge/{Badge2.js → Badge.js} +1 -2
  9. package/dist/components/badge/Badge.js.map +1 -0
  10. package/dist/components/badge/index.css +115 -0
  11. package/dist/components/badge/index.js +1 -1
  12. package/dist/components/button/index.css +295 -0
  13. package/dist/components/checkbox/index.css +181 -0
  14. package/dist/components/choicebox/Choicebox.d.ts +21 -0
  15. package/dist/components/choicebox/Choicebox.js +55 -0
  16. package/dist/components/choicebox/Choicebox.js.map +1 -0
  17. package/dist/components/choicebox/index.css +364 -0
  18. package/dist/components/choicebox/index.d.ts +1 -0
  19. package/dist/components/choicebox/index.js +2 -0
  20. package/dist/components/close-button/CloseButton.d.ts +1 -1
  21. package/dist/components/close-button/index.css +47 -0
  22. package/dist/components/favourite-button/FavouriteButton.d.ts +15 -0
  23. package/dist/components/favourite-button/FavouriteButton.js +25 -0
  24. package/dist/components/favourite-button/FavouriteButton.js.map +1 -0
  25. package/dist/components/favourite-button/index.css +86 -0
  26. package/dist/components/favourite-button/index.d.ts +2 -0
  27. package/dist/components/favourite-button/index.js +2 -0
  28. package/dist/components/index.css +12 -0
  29. package/dist/components/index.d.ts +12 -0
  30. package/dist/components/link/Link.d.ts +11 -0
  31. package/dist/components/link/Link.js +65 -0
  32. package/dist/components/link/Link.js.map +1 -0
  33. package/dist/components/link/index.css +122 -0
  34. package/dist/components/link/index.d.ts +1 -0
  35. package/dist/components/link/index.js +2 -0
  36. package/dist/components/nav-pill/NavPill.d.ts +21 -0
  37. package/dist/components/nav-pill/NavPill.js +54 -0
  38. package/dist/components/nav-pill/NavPill.js.map +1 -0
  39. package/dist/components/nav-pill/index.css +96 -0
  40. package/dist/components/nav-pill/index.d.ts +1 -0
  41. package/dist/components/nav-pill/index.js +2 -0
  42. package/dist/components/pagination/Pagination.d.ts +32 -0
  43. package/dist/components/pagination/Pagination.js +100 -0
  44. package/dist/components/pagination/Pagination.js.map +1 -0
  45. package/dist/components/pagination/index.css +139 -0
  46. package/dist/components/pagination/index.d.ts +1 -0
  47. package/dist/components/pagination/index.js +2 -0
  48. package/dist/components/pagination/pagination.helpers.d.ts +26 -0
  49. package/dist/components/pagination/pagination.helpers.js +136 -0
  50. package/dist/components/pagination/pagination.helpers.js.map +1 -0
  51. package/dist/components/progress-bar/ProgressBar.d.ts +35 -0
  52. package/dist/components/progress-bar/ProgressBar.js +86 -0
  53. package/dist/components/progress-bar/ProgressBar.js.map +1 -0
  54. package/dist/components/progress-bar/index.css +193 -0
  55. package/dist/components/progress-bar/index.d.ts +1 -0
  56. package/dist/components/progress-bar/index.js +2 -0
  57. package/dist/components/radio/index.css +133 -0
  58. package/dist/index.css +1101 -150
  59. package/dist/index.js +8 -2
  60. package/{tokens → dist/tokens}/css/colors.css +7 -4
  61. package/{tokens → dist/tokens}/css/primitives.css +1 -1
  62. package/{tokens → dist/tokens}/scss/_colors.scss +8 -5
  63. package/{tokens → dist/tokens}/scss/_index_css_vars.scss +3 -0
  64. package/{tokens → dist/tokens}/scss/_primitives.scss +1 -1
  65. package/{tokens → dist/tokens}/scss/_typography.scss +1 -1
  66. package/package.json +16 -7
  67. package/dist/components/badge/Badge2.js.map +0 -1
  68. /package/{tokens → dist/tokens}/css/borders.css +0 -0
  69. /package/{tokens → dist/tokens}/css/breakpoints.css +0 -0
  70. /package/{tokens → dist/tokens}/css/index.css +0 -0
  71. /package/{tokens → dist/tokens}/css/shadows.css +0 -0
  72. /package/{tokens → dist/tokens}/css/sizes.css +0 -0
  73. /package/{tokens → dist/tokens}/css/spacing.css +0 -0
  74. /package/{tokens → dist/tokens}/css/typography.css +0 -0
  75. /package/{tokens → dist/tokens}/scss/_borders.scss +0 -0
  76. /package/{tokens → dist/tokens}/scss/_breakpoints.scss +0 -0
  77. /package/{tokens → dist/tokens}/scss/_index.legacy.scss +0 -0
  78. /package/{tokens → dist/tokens}/scss/_index.scss +0 -0
  79. /package/{tokens → dist/tokens}/scss/_shadows.scss +0 -0
  80. /package/{tokens → dist/tokens}/scss/_sizes.scss +0 -0
  81. /package/{tokens → dist/tokens}/scss/_spacing.scss +0 -0
@@ -0,0 +1,1893 @@
1
+ /* activity-icon/index.css */
2
+ .mds-activity-icon {
3
+ align-items: center;
4
+ /*
5
+ * content-box so block-size/inline-size declare the ICON content area.
6
+ * Padding then expands the tile beyond that — matching Figma's structure
7
+ * where "size" is the icon and padding is added by the container variant.
8
+ */
9
+ box-sizing: content-box;
10
+ display: inline-flex;
11
+ flex-shrink: 0;
12
+ justify-content: center;
13
+ overflow: hidden;
14
+ }
15
+
16
+ /* none: no background tile, no padding — tile equals the icon size */
17
+ .mds-activity-icon--none {
18
+ border-radius: var(--mds-border-radius-none);
19
+ }
20
+
21
+ /*
22
+ * default: 8px padding each side — tile = icon + 16px (e.g. lg: 24+16 = 40px total).
23
+ * large: 12px padding each side — tile = icon + 24px (e.g. lg: 24+24 = 48px total).
24
+ */
25
+ .mds-activity-icon--default {
26
+ border-radius: var(--mds-border-radius-xl);
27
+ padding: var(--mds-spacing-xs);
28
+ }
29
+
30
+ .mds-activity-icon--large {
31
+ border-radius: var(--mds-border-radius-xl);
32
+ padding: var(--mds-spacing-sm);
33
+ }
34
+
35
+ /* Figma uses a smaller outer radius for default+sm only. */
36
+ .mds-activity-icon--default.mds-activity-icon--size-sm {
37
+ border-radius: var(--mds-border-radius-lg);
38
+ }
39
+
40
+ /* Category background colors apply only when a background container is active. */
41
+ .mds-activity-icon--default.mds-activity-icon--category-assessment,
42
+ .mds-activity-icon--large.mds-activity-icon--category-assessment {
43
+ background-color: var(--mds-activity-icon-assessment-bg);
44
+ }
45
+
46
+ .mds-activity-icon--default.mds-activity-icon--category-collaboration,
47
+ .mds-activity-icon--large.mds-activity-icon--category-collaboration {
48
+ background-color: var(--mds-activity-icon-collaboration-bg);
49
+ }
50
+
51
+ .mds-activity-icon--default.mds-activity-icon--category-communication,
52
+ .mds-activity-icon--large.mds-activity-icon--category-communication {
53
+ background-color: var(--mds-activity-icon-communication-bg);
54
+ }
55
+
56
+ .mds-activity-icon--default.mds-activity-icon--category-interactive,
57
+ .mds-activity-icon--large.mds-activity-icon--category-interactive {
58
+ background-color: var(--mds-activity-icon-interactive-bg);
59
+ }
60
+
61
+ .mds-activity-icon--default.mds-activity-icon--category-other,
62
+ .mds-activity-icon--large.mds-activity-icon--category-other {
63
+ background-color: var(--mds-activity-icon-other-bg);
64
+ }
65
+
66
+ .mds-activity-icon--default.mds-activity-icon--category-resource,
67
+ .mds-activity-icon--large.mds-activity-icon--category-resource {
68
+ background-color: var(--mds-activity-icon-resource-bg);
69
+ }
70
+
71
+ /* Size variants */
72
+ .mds-activity-icon--size-sm {
73
+ block-size: var(--mds-icons-sm);
74
+ inline-size: var(--mds-icons-sm);
75
+ }
76
+
77
+ .mds-activity-icon--size-md {
78
+ /* Intentionally follows the Figma token mapping, where md uses the lg icon token. */
79
+ block-size: var(--mds-icons-lg);
80
+ inline-size: var(--mds-icons-lg);
81
+ }
82
+
83
+ .mds-activity-icon--size-lg {
84
+ /* Intentionally follows the Figma token mapping, where lg uses the xl icon token. */
85
+ block-size: var(--mds-icons-xl);
86
+ inline-size: var(--mds-icons-xl);
87
+ }
88
+
89
+ .mds-activity-icon--size-xl {
90
+ /* Intentionally follows the Figma token mapping, where xl uses the xxl icon token. */
91
+ block-size: var(--mds-icons-xxl);
92
+ inline-size: var(--mds-icons-xxl);
93
+ }
94
+
95
+ .mds-activity-icon__asset {
96
+ block-size: 100%;
97
+ border-radius: var(--mds-border-radius-xs);
98
+ display: block;
99
+ inline-size: 100%;
100
+ }
101
+
102
+ /* badge/index.css */
103
+ .mds-badge {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 0;
107
+ padding: var(--mds-spacing-xxs) var(--mds-spacing-xs);
108
+ border-radius: var(--mds-border-radius-sm);
109
+
110
+ font-family: var(--mds-font-family-base);
111
+ font-weight: var(--mds-font-weight-medium);
112
+ font-size: var(--mds-font-size-paragraph-small);
113
+ line-height: var(--mds-line-height-paragraph-xs);
114
+ letter-spacing: var(--mds-letter-spacing-default);
115
+ white-space: nowrap;
116
+ vertical-align: baseline;
117
+ }
118
+
119
+ .mds-badge--has-icon {
120
+ gap: var(--mds-spacing-xxs);
121
+ }
122
+
123
+ /* Normalize icon dimensions to match the badge icon token across <i> and <svg> usage. */
124
+ .mds-badge > i,
125
+ .mds-badge > svg {
126
+ width: var(--mds-icons-xxs);
127
+ height: var(--mds-icons-xxs);
128
+ font-size: var(--mds-icons-xxs);
129
+ flex-shrink: 0;
130
+ }
131
+
132
+ /* Pill shape overrides the default rounded corners */
133
+ .mds-badge--pill {
134
+ border-radius: var(--mds-border-radius-pill);
135
+ }
136
+
137
+ /*
138
+ * Per-variant colour rules.
139
+ * Default contrast: solid coloured background with inverse text.
140
+ * Subtle contrast (.mds-badge--subtle): light tinted background, dark feedback text, visible border.
141
+ */
142
+
143
+ .mds-badge--subtle {
144
+ border: var(--mds-stroke-weight-sm) solid transparent;
145
+ }
146
+
147
+ /* --- Primary --- */
148
+ .mds-badge--primary {
149
+ background-color: var(--mds-bg-feedback-primary-default);
150
+ color: var(--mds-text-inverse);
151
+ }
152
+
153
+ .mds-badge--primary.mds-badge--subtle {
154
+ background-color: var(--mds-bg-feedback-primary-subtle);
155
+ color: var(--mds-text-feedback-primary);
156
+ border-color: var(--mds-border-feedback-primary);
157
+ }
158
+
159
+ /* --- Secondary --- */
160
+ .mds-badge--secondary {
161
+ background-color: var(--mds-bg-feedback-secondary-default);
162
+ color: var(--mds-text-feedback-secondary);
163
+ }
164
+
165
+ .mds-badge--secondary.mds-badge--subtle {
166
+ background-color: var(--mds-bg-feedback-secondary-subtle);
167
+ color: var(--mds-text-feedback-secondary);
168
+ border-color: var(--mds-border-feedback-secondary);
169
+ }
170
+
171
+ /* --- Success --- */
172
+ .mds-badge--success {
173
+ background-color: var(--mds-bg-feedback-success-default);
174
+ color: var(--mds-text-inverse);
175
+ }
176
+
177
+ .mds-badge--success.mds-badge--subtle {
178
+ background-color: var(--mds-bg-feedback-success-subtle);
179
+ color: var(--mds-text-feedback-success);
180
+ border-color: var(--mds-border-feedback-success);
181
+ }
182
+
183
+ /* --- Danger --- */
184
+ .mds-badge--danger {
185
+ background-color: var(--mds-bg-feedback-danger-default);
186
+ color: var(--mds-text-inverse);
187
+ }
188
+
189
+ .mds-badge--danger.mds-badge--subtle {
190
+ background-color: var(--mds-bg-feedback-danger-subtle);
191
+ color: var(--mds-text-feedback-danger);
192
+ border-color: var(--mds-border-feedback-danger);
193
+ }
194
+
195
+ /* --- Warning --- */
196
+ .mds-badge--warning {
197
+ background-color: var(--mds-bg-feedback-warning-default);
198
+ color: var(--mds-text-feedback-warning);
199
+ }
200
+
201
+ .mds-badge--warning.mds-badge--subtle {
202
+ background-color: var(--mds-bg-feedback-warning-subtle);
203
+ color: var(--mds-text-feedback-warning);
204
+ border-color: var(--mds-border-feedback-warning);
205
+ }
206
+
207
+ /* --- Info --- */
208
+ .mds-badge--info {
209
+ background-color: var(--mds-bg-feedback-info-default);
210
+ color: var(--mds-text-inverse);
211
+ }
212
+
213
+ .mds-badge--info.mds-badge--subtle {
214
+ background-color: var(--mds-bg-feedback-info-subtle);
215
+ color: var(--mds-text-feedback-info);
216
+ border-color: var(--mds-border-feedback-info);
217
+ }
218
+
219
+ /* button/index.css */
220
+ .mds-btn.btn {
221
+ background-color: var(--mds-bg-interactive-primary-default);
222
+ border: none;
223
+ border-radius: var(--mds-border-radius-sm);
224
+ padding: var(--mds-spacing-xs) var(--mds-spacing-sm);
225
+
226
+ color: var(--mds-text-inverse);
227
+ font-family: var(--mds-font-family-base);
228
+ font-weight: var(--mds-font-weight-regular);
229
+ font-size: var(--mds-font-size-paragraph-default);
230
+ line-height: var(--mds-line-height-paragraph-xs);
231
+ letter-spacing: var(--mds-letter-spacing-default);
232
+
233
+ /* Keep icon/text spacing consistent with icon variants from the design matrix. */
234
+ display: inline-flex;
235
+ align-items: center;
236
+ gap: var(--mds-spacing-xs);
237
+ }
238
+
239
+ /* Icon-only sizing in Figma is not derived from text button size classes. */
240
+ .mds-btn.btn.mds-btn--icon-only {
241
+ padding: var(--mds-spacing-xs);
242
+ border-radius: var(--mds-border-radius-xl);
243
+ }
244
+
245
+ .mds-btn.btn.mds-btn--icon-only i,
246
+ .mds-btn.btn.mds-btn--icon-only svg {
247
+ inline-size: var(--mds-icons-xs);
248
+ block-size: var(--mds-icons-xs);
249
+ font-size: var(--mds-icons-xs);
250
+ line-height: 1;
251
+ flex-shrink: 0;
252
+ }
253
+
254
+ .mds-btn.btn.mds-btn--icon-only.mds-btn--size-sm {
255
+ padding: var(--mds-spacing-xxs);
256
+ }
257
+
258
+ .mds-btn.btn.mds-btn--icon-only.mds-btn--size-sm i,
259
+ .mds-btn.btn.mds-btn--icon-only.mds-btn--size-sm svg {
260
+ inline-size: var(--mds-icons-xxs);
261
+ block-size: var(--mds-icons-xxs);
262
+ font-size: var(--mds-icons-xxs);
263
+ }
264
+
265
+ .mds-btn.btn.mds-btn--icon-only.mds-btn--size-lg {
266
+ padding: var(--mds-spacing-xs);
267
+ border-radius: var(--mds-border-radius-sm);
268
+ }
269
+
270
+ .mds-btn.btn.mds-btn--size-sm i,
271
+ .mds-btn.btn.mds-btn--size-sm svg {
272
+ inline-size: var(--mds-icons-xxs);
273
+ block-size: var(--mds-icons-xxs);
274
+ font-size: var(--mds-icons-xxs);
275
+ line-height: 1;
276
+ flex-shrink: 0;
277
+ }
278
+
279
+ .mds-btn.btn.mds-btn--size-md i,
280
+ .mds-btn.btn.mds-btn--size-md svg,
281
+ .mds-btn.btn.mds-btn--size-lg i,
282
+ .mds-btn.btn.mds-btn--size-lg svg {
283
+ inline-size: var(--mds-icons-xs);
284
+ block-size: var(--mds-icons-xs);
285
+ font-size: var(--mds-icons-xs);
286
+ line-height: 1;
287
+ flex-shrink: 0;
288
+ }
289
+
290
+ .mds-btn.btn:focus {
291
+ box-shadow: none;
292
+ outline: none;
293
+ }
294
+ .mds-btn.btn:focus-visible {
295
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
296
+ outline-offset: var(--mds-spacing-offset);
297
+ box-shadow: none;
298
+ }
299
+ .mds-btn.btn:active:focus-visible {
300
+ box-shadow: none;
301
+ }
302
+ .mds-btn.btn.btn-danger:focus-visible,
303
+ .mds-btn.btn.btn-outline-danger:focus-visible {
304
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-danger);
305
+ }
306
+ .mds-btn.btn:hover {
307
+ background-color: var(--mds-bg-interactive-primary-hover);
308
+ }
309
+ .mds-btn.btn:active {
310
+ background-color: var(--mds-bg-interactive-primary-active);
311
+ box-shadow: none;
312
+ }
313
+ .mds-btn.btn:disabled {
314
+ background-color: var(--mds-bg-interactive-primary-disabled);
315
+ }
316
+
317
+ /**
318
+ * Secondary variants
319
+ */
320
+ .mds-btn.btn.btn-secondary {
321
+ background-color: var(--mds-bg-interactive-secondary-default);
322
+ color: var(--mds-text-subtle);
323
+ }
324
+ .mds-btn.btn.btn-secondary:hover {
325
+ background-color: var(--mds-bg-interactive-secondary-hover);
326
+ }
327
+ .mds-btn.btn.btn-secondary:active {
328
+ background-color: var(--mds-bg-interactive-secondary-active);
329
+ }
330
+ .mds-btn.btn.btn-secondary:disabled {
331
+ background-color: var(--mds-bg-interactive-secondary-disabled);
332
+ color: var(--mds-text-muted);
333
+ }
334
+
335
+ /**
336
+ * Danger variants
337
+ */
338
+ .mds-btn.btn-danger {
339
+ background-color: var(--mds-bg-interactive-danger-default);
340
+ }
341
+ .mds-btn.btn-danger:hover {
342
+ background-color: var(--mds-bg-interactive-danger-hover);
343
+ }
344
+ .mds-btn.btn-danger:active {
345
+ background-color: var(--mds-bg-interactive-danger-active);
346
+ }
347
+ .mds-btn.btn.btn-danger:disabled {
348
+ background-color: var(--mds-bg-interactive-danger-disabled);
349
+ }
350
+
351
+ /**
352
+ * Ghost variants
353
+ */
354
+ .mds-btn.btn.btn-ghost {
355
+ background-color: transparent;
356
+ color: var(--mds-text-subtle);
357
+ }
358
+ .mds-btn.btn.btn-ghost:hover {
359
+ background-color: var(--mds-bg-interactive-secondary-hover);
360
+ color: var(--mds-text-subtle);
361
+ }
362
+ .mds-btn.btn.btn-ghost:active {
363
+ background-color: var(--mds-bg-interactive-secondary-active);
364
+ color: var(--mds-text-subtle);
365
+ }
366
+ .mds-btn.btn.btn-ghost:disabled {
367
+ background-color: transparent;
368
+ color: var(--mds-text-muted);
369
+ }
370
+
371
+ /**
372
+ * Outline Primary variants
373
+ */
374
+ .mds-btn.btn:is(
375
+ .btn-outline-primary,
376
+ .btn-outline-secondary,
377
+ .btn-outline-danger
378
+ ) {
379
+ background-color: transparent;
380
+ border: var(--mds-stroke-weight-sm) solid transparent;
381
+ }
382
+
383
+ .mds-btn.btn.btn-outline-primary {
384
+ border-color: var(--mds-border-interactive-primary-default);
385
+ color: var(--mds-text-link-primary-default);
386
+ }
387
+ .mds-btn.btn-outline-primary:hover {
388
+ border-color: var(--mds-bg-interactive-primary-hover);
389
+ background-color: var(--mds-bg-interactive-primary-hover);
390
+ color: var(--mds-text-inverse);
391
+ }
392
+ .mds-btn.btn-outline-primary:active {
393
+ border-color: var(--mds-bg-interactive-primary-active);
394
+ background-color: var(--mds-bg-interactive-primary-active);
395
+ color: var(--mds-text-inverse);
396
+ }
397
+ .mds-btn.btn.btn-outline-primary:disabled {
398
+ border-color: var(--mds-border-interactive-primary-disabled);
399
+ color: var(--mds-text-link-primary-disabled);
400
+ }
401
+
402
+ /**
403
+ * Outline Secondary variants
404
+ */
405
+ .mds-btn.btn.btn-outline-secondary {
406
+ border-color: var(--mds-border-interactive-secondary-default);
407
+ color: var(--mds-text-subtle);
408
+ }
409
+ .mds-btn.btn-outline-secondary:hover {
410
+ border-color: var(--mds-border-interactive-secondary-hover);
411
+ background-color: var(--mds-border-interactive-secondary-hover);
412
+ color: var(--mds-text-inverse);
413
+ }
414
+ .mds-btn.btn-outline-secondary:active {
415
+ border-color: var(--mds-border-interactive-secondary-active);
416
+ background-color: var(--mds-border-interactive-secondary-active);
417
+ color: var(--mds-text-inverse);
418
+ }
419
+ .mds-btn.btn.btn-outline-secondary:disabled {
420
+ border-color: var(--mds-border-interactive-secondary-disabled);
421
+ color: var(--mds-text-muted);
422
+ }
423
+
424
+ /**
425
+ * Outline Danger variants
426
+ */
427
+ .mds-btn.btn.btn-outline-danger {
428
+ border-color: var(--mds-border-interactive-danger-default);
429
+ color: var(--mds-text-danger);
430
+ }
431
+ .mds-btn.btn-outline-danger:hover {
432
+ border-color: var(--mds-bg-interactive-danger-hover);
433
+ background-color: var(--mds-bg-interactive-danger-hover);
434
+ color: var(--mds-text-inverse);
435
+ }
436
+ .mds-btn.btn-outline-danger:active {
437
+ border-color: var(--mds-bg-interactive-danger-active);
438
+ background-color: var(--mds-bg-interactive-danger-active);
439
+ color: var(--mds-text-inverse);
440
+ }
441
+ .mds-btn.btn.btn-outline-danger:disabled {
442
+ border-color: var(--mds-border-interactive-danger-disabled);
443
+ color: var(--mds-text-danger-disabled);
444
+ }
445
+
446
+ /**
447
+ * Size variants
448
+ */
449
+ .mds-btn.mds-btn--size-sm {
450
+ padding: var(--mds-spacing-xxs) var(--mds-spacing-xs);
451
+ border-radius: var(--mds-border-radius-xs);
452
+ font-weight: var(--mds-font-weight-medium);
453
+ font-size: var(--mds-font-size-paragraph-small);
454
+ }
455
+ .mds-btn.mds-btn--size-md {
456
+ padding: var(--mds-spacing-xs) var(--mds-spacing-sm);
457
+ font-size: var(--mds-font-size-paragraph-default);
458
+ }
459
+ .mds-btn.mds-btn--size-lg {
460
+ padding: var(--mds-spacing-sm) var(--mds-spacing-md);
461
+ font-size: var(--mds-font-size-paragraph-default);
462
+ }
463
+
464
+ /* Keep outer geometry aligned with filled variants when outline border is present. */
465
+ .mds-btn.mds-btn--size-sm:is(
466
+ .btn-outline-primary,
467
+ .btn-outline-secondary,
468
+ .btn-outline-danger
469
+ ) {
470
+ padding: calc(var(--mds-spacing-xxs) - var(--mds-stroke-weight-sm))
471
+ calc(var(--mds-spacing-xs) - var(--mds-stroke-weight-sm));
472
+ }
473
+
474
+ .mds-btn.mds-btn--size-md:is(
475
+ .btn-outline-primary,
476
+ .btn-outline-secondary,
477
+ .btn-outline-danger
478
+ ) {
479
+ padding: calc(var(--mds-spacing-xs) - var(--mds-stroke-weight-sm))
480
+ calc(var(--mds-spacing-sm) - var(--mds-stroke-weight-sm));
481
+ }
482
+
483
+ .mds-btn.mds-btn--size-lg:is(
484
+ .btn-outline-primary,
485
+ .btn-outline-secondary,
486
+ .btn-outline-danger
487
+ ) {
488
+ padding: calc(var(--mds-spacing-sm) - var(--mds-stroke-weight-sm))
489
+ calc(var(--mds-spacing-md) - var(--mds-stroke-weight-sm));
490
+ }
491
+
492
+ .mds-btn.mds-btn--icon-only:is(
493
+ .btn-outline-primary,
494
+ .btn-outline-secondary,
495
+ .btn-outline-danger
496
+ ) {
497
+ padding: calc(var(--mds-spacing-xs) - var(--mds-stroke-weight-sm));
498
+ }
499
+
500
+ .mds-btn.mds-btn--icon-only.mds-btn--size-sm:is(
501
+ .btn-outline-primary,
502
+ .btn-outline-secondary,
503
+ .btn-outline-danger
504
+ ) {
505
+ padding: calc(var(--mds-spacing-xxs) - var(--mds-stroke-weight-sm));
506
+ }
507
+
508
+ .mds-btn.mds-btn--icon-only.mds-btn--size-lg:is(
509
+ .btn-outline-primary,
510
+ .btn-outline-secondary,
511
+ .btn-outline-danger
512
+ ) {
513
+ padding: calc(var(--mds-spacing-xs) - var(--mds-stroke-weight-sm));
514
+ }
515
+
516
+ /* checkbox/index.css */
517
+ .mds-checkbox {
518
+ display: inline-grid;
519
+ grid-template-columns: auto auto;
520
+ align-items: center;
521
+ align-self: start;
522
+ min-height: var(--mds-icons-lg);
523
+ padding: var(--mds-spacing-none);
524
+ column-gap: var(--mds-spacing-xs);
525
+ row-gap: var(--mds-spacing-xxs);
526
+
527
+ font-family: var(--mds-font-family-base);
528
+ font-size: var(--mds-font-size-paragraph-default);
529
+ font-weight: var(--mds-font-weight-regular);
530
+ line-height: var(--mds-line-height-paragraph-xs);
531
+ letter-spacing: var(--mds-letter-spacing-default);
532
+ }
533
+
534
+ .mds-checkbox-input {
535
+ border-radius: var(--mds-border-radius-xs);
536
+ border: var(--mds-stroke-weight-sm) solid
537
+ var(--mds-border-interactive-secondary-default);
538
+ }
539
+
540
+ .mds-checkbox .mds-checkbox-input {
541
+ /* Bootstrap applies float/offset styles to .form-check-input for indented label layouts.
542
+ Match selector specificity and reset float/margins for the grid-based sibling layout. */
543
+ margin: 0;
544
+ float: none;
545
+ background-color: var(--mds-bg-surface-default);
546
+ }
547
+
548
+ .mds-checkbox-input:checked {
549
+ background-color: var(--mds-bg-interactive-primary-default);
550
+ border-color: transparent;
551
+ }
552
+
553
+ .mds-checkbox-input:indeterminate {
554
+ background-color: var(--mds-bg-interactive-primary-default);
555
+ border-color: transparent;
556
+ }
557
+
558
+ .mds-checkbox-input:disabled {
559
+ border-color: var(--mds-border-interactive-secondary-disabled);
560
+ }
561
+
562
+ .mds-checkbox-input:disabled:checked {
563
+ background-color: var(--mds-bg-interactive-primary-disabled);
564
+ border-color: transparent;
565
+ }
566
+
567
+ .mds-checkbox-input:disabled:indeterminate {
568
+ background-color: var(--mds-bg-interactive-primary-disabled);
569
+ border-color: transparent;
570
+ }
571
+
572
+ .mds-checkbox-input.is-invalid {
573
+ border-color: var(--mds-border-interactive-danger-default);
574
+ }
575
+
576
+ .mds-checkbox-input.is-invalid:checked {
577
+ background-color: var(--mds-bg-interactive-danger-default);
578
+ border-color: transparent;
579
+ }
580
+
581
+ .mds-checkbox-input.is-invalid:indeterminate {
582
+ background-color: var(--mds-bg-interactive-danger-default);
583
+ border-color: transparent;
584
+ }
585
+
586
+ .mds-checkbox-input.is-invalid:disabled {
587
+ background-color: var(--mds-bg-interactive-secondary-disabled);
588
+ border-color: var(--mds-border-interactive-secondary-disabled);
589
+ }
590
+
591
+ .mds-checkbox-input.is-invalid:disabled:checked {
592
+ background-color: var(--mds-bg-interactive-primary-disabled);
593
+ border-color: transparent;
594
+ }
595
+
596
+ .mds-checkbox-input.is-invalid:disabled:indeterminate {
597
+ background-color: var(--mds-bg-interactive-primary-disabled);
598
+ border-color: transparent;
599
+ }
600
+
601
+ .mds-checkbox-input:focus,
602
+ .mds-checkbox-input.is-invalid:focus {
603
+ /* Reset Bootstrap's :focus box-shadow; our ring is applied on :focus-visible only */
604
+ box-shadow: none;
605
+ outline: none;
606
+ }
607
+
608
+ /* Bootstrap also sets border-color on :focus. Restore the correct border for each state,
609
+ excluding checked/indeterminate which keep their transparent border. */
610
+ .mds-checkbox-input:focus:not(:checked):not(:indeterminate) {
611
+ border-color: var(--mds-border-interactive-secondary-default);
612
+ }
613
+
614
+ .mds-checkbox-input.is-invalid:focus:not(:checked):not(:indeterminate) {
615
+ border-color: var(--mds-border-interactive-danger-default);
616
+ }
617
+
618
+ .mds-checkbox-input:focus-visible {
619
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
620
+ outline-offset: var(--mds-spacing-offset);
621
+ box-shadow: none;
622
+ }
623
+
624
+ .mds-checkbox-input.is-invalid:focus-visible {
625
+ outline: var(--mds-stroke-weight-md) solid var(--mds-border-feedback-danger);
626
+ outline-offset: var(--mds-spacing-offset);
627
+ box-shadow: none;
628
+ }
629
+
630
+ .mds-checkbox .form-check-label,
631
+ .mds-checkbox .mds-checkbox-label {
632
+ display: flex;
633
+ align-items: center;
634
+ /* In the radio-style sibling layout, the checkbox should align to the label text line,
635
+ not to an expanded label hit area. Keep the label box tight and let htmlFor preserve click behavior. */
636
+ padding: 0;
637
+ color: var(--mds-text-default);
638
+ }
639
+
640
+ .mds-checkbox .mds-checkbox-label-text {
641
+ min-width: 0;
642
+ }
643
+
644
+ .mds-checkbox .mds-checkbox-input:disabled ~ .form-check-label,
645
+ .mds-checkbox .mds-checkbox-input:disabled ~ .mds-checkbox-label {
646
+ color: var(--mds-text-muted);
647
+ }
648
+
649
+ .mds-checkbox .mds-checkbox-input.is-invalid ~ .form-check-label,
650
+ .mds-checkbox .mds-checkbox-input.is-invalid ~ .mds-checkbox-label {
651
+ color: var(--mds-text-danger);
652
+ }
653
+
654
+ /* Disabled takes precedence over invalid — restore muted colour when both are active. */
655
+ .mds-checkbox .mds-checkbox-input.is-invalid:disabled ~ .form-check-label,
656
+ .mds-checkbox .mds-checkbox-input.is-invalid:disabled ~ .mds-checkbox-label {
657
+ color: var(--mds-text-muted);
658
+ }
659
+
660
+ .mds-checkbox-required {
661
+ color: var(--mds-text-danger);
662
+ margin-inline-start: var(--mds-spacing-xxs);
663
+ }
664
+
665
+ .mds-checkbox .invalid-feedback,
666
+ .mds-checkbox .mds-checkbox-feedback {
667
+ grid-column: 2;
668
+ font-size: var(--mds-font-size-paragraph-small);
669
+ font-weight: var(--mds-font-weight-medium);
670
+ line-height: var(--mds-line-height-paragraph-xs);
671
+ }
672
+
673
+ .mds-checkbox .invalid-feedback,
674
+ .mds-checkbox .mds-checkbox-feedback.mds-checkbox-supporting-text {
675
+ margin: 0;
676
+ }
677
+
678
+ .mds-checkbox .invalid-feedback {
679
+ color: var(--mds-text-danger);
680
+ }
681
+
682
+ /* In the default state supporting text is subtle. In the invalid state use danger to match
683
+ the label colour \u2014 matching Figma where the label container sets the colour for both. */
684
+ .mds-checkbox .mds-checkbox-feedback.mds-checkbox-supporting-text {
685
+ color: var(--mds-text-subtle);
686
+ }
687
+
688
+ .mds-checkbox
689
+ .mds-checkbox-input.is-invalid
690
+ ~ .mds-checkbox-feedback.mds-checkbox-supporting-text {
691
+ color: var(--mds-text-danger);
692
+ }
693
+
694
+ .mds-checkbox .mds-checkbox-input:disabled ~ .invalid-feedback,
695
+ .mds-checkbox .mds-checkbox-input:disabled ~ .mds-checkbox-feedback {
696
+ opacity: 0.5;
697
+ }
698
+
699
+ /* choicebox/index.css */
700
+ /* ==========================================================================
701
+ Choicebox
702
+ A full-surface interactive card for single-select option picking.
703
+ HTML structure:
704
+ .mds-choicebox-wrapper
705
+ input.mds-choicebox-input (visually hidden radio)
706
+ label.mds-choicebox
707
+ span.mds-choicebox-icon? (optional — aria-hidden)
708
+ span.mds-choicebox-labels
709
+ span.mds-choicebox-label
710
+ span.mds-choicebox-supporting-text?
711
+ span.mds-choicebox-indicator (decorative — aria-hidden)
712
+ State is driven entirely by CSS sibling selectors (.mds-choicebox-input
713
+ precedes .mds-choicebox in the DOM), avoiding any JS state management.
714
+ ========================================================================== */
715
+
716
+ /* --------------------------------------------------------------------------
717
+ Wrapper
718
+ -------------------------------------------------------------------------- */
719
+
720
+ .mds-choicebox-wrapper {
721
+ display: block;
722
+ /* Prevent flex containers from stretching this element */
723
+ align-self: start;
724
+ min-width: 320px; /* Ensure enough space for the indicator and some label text */
725
+ }
726
+
727
+ /* --------------------------------------------------------------------------
728
+ Hidden input
729
+ Visually hidden but focusable so keyboard and assistive tech can reach it.
730
+ pointer-events: none prevents it from receiving clicks — the <label for>
731
+ association handles delegation to the radio input instead.
732
+ -------------------------------------------------------------------------- */
733
+
734
+ .mds-choicebox-input {
735
+ position: absolute;
736
+ width: 1px;
737
+ height: 1px;
738
+ padding: 0;
739
+ margin: -1px;
740
+ overflow: hidden;
741
+ clip: rect(0, 0, 0, 0);
742
+ white-space: nowrap;
743
+ border: 0;
744
+ pointer-events: none;
745
+ }
746
+
747
+ /* --------------------------------------------------------------------------
748
+ Card (label element — full-surface clickable area)
749
+ -------------------------------------------------------------------------- */
750
+
751
+ .mds-choicebox {
752
+ display: flex;
753
+ flex-direction: row;
754
+ align-items: flex-start;
755
+ gap: var(--mds-spacing-xs);
756
+
757
+ padding: var(--mds-spacing-xs) var(--mds-spacing-sm);
758
+ border-radius: var(--mds-border-radius-md);
759
+ border: var(--mds-stroke-weight-sm) solid var(--mds-border-default);
760
+ background-color: var(--mds-bg-surface-subtle);
761
+
762
+ cursor: pointer;
763
+ /* Ensure the label fills its container when Choicebox is used in a grid or flex layout */
764
+ width: 100%;
765
+ box-sizing: border-box;
766
+ }
767
+
768
+ /* Hover — unselected */
769
+ .mds-choicebox-input:not(:disabled):not(:checked) + .mds-choicebox:hover {
770
+ background-color: var(--mds-bg-surface-default);
771
+ border-color: var(--mds-border-default);
772
+ }
773
+
774
+ /* Active/pressed — unselected */
775
+ .mds-choicebox-input:not(:disabled):not(:checked) + .mds-choicebox:active {
776
+ background-color: var(--mds-bg-surface-strong);
777
+ border-color: var(--mds-border-default);
778
+ }
779
+
780
+ /* Selected — default */
781
+ .mds-choicebox-input:checked + .mds-choicebox {
782
+ background-color: var(--mds-bg-interactive-primary-default-light);
783
+ border-color: var(--mds-border-feedback-primary);
784
+ }
785
+
786
+ /* Selected — hover */
787
+ .mds-choicebox-input:not(:disabled):checked + .mds-choicebox:hover {
788
+ background-color: var(--mds-bg-surface-subtle);
789
+ border-color: var(--mds-border-feedback-primary);
790
+ }
791
+
792
+ /* Selected — pressed */
793
+ .mds-choicebox-input:not(:disabled):checked + .mds-choicebox:active {
794
+ background-color: var(--mds-bg-surface-strong);
795
+ border-color: var(--mds-border-feedback-primary);
796
+ }
797
+
798
+ /* Disabled — show 'not-allowed' cursor over the whole card area.
799
+ cursor: not-allowed cannot be set on the label itself because
800
+ pointer-events: none (below) removes it from hit-testing, making the cursor
801
+ rule unreachable. :has() is safe for all Moodle-target browsers. */
802
+ .mds-choicebox-wrapper:has(.mds-choicebox-input:disabled) {
803
+ cursor: not-allowed;
804
+ }
805
+
806
+ /* Block clicks on the label so the disabled radio cannot be activated */
807
+ .mds-choicebox-input:disabled + .mds-choicebox {
808
+ pointer-events: none;
809
+ }
810
+
811
+ /* --------------------------------------------------------------------------
812
+ Focus — ring appears on the indicator per design spec
813
+ -------------------------------------------------------------------------- */
814
+
815
+ .mds-choicebox-input:focus-visible
816
+ + .mds-choicebox
817
+ .mds-choicebox-indicator::after {
818
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
819
+ outline-offset: var(--mds-spacing-offset);
820
+ box-shadow: none;
821
+ }
822
+
823
+ /* --------------------------------------------------------------------------
824
+ Icon slot
825
+ -------------------------------------------------------------------------- */
826
+
827
+ .mds-choicebox-icon {
828
+ display: flex;
829
+ align-items: center;
830
+ justify-content: center;
831
+ padding: var(--mds-spacing-xxs);
832
+ flex-shrink: 0;
833
+ /* Icon size matches --mds-icons-sm (16px) */
834
+ font-size: var(--mds-icons-sm);
835
+ color: var(--mds-text-default);
836
+ }
837
+
838
+ /* Selected icon */
839
+ .mds-choicebox-input:checked + .mds-choicebox .mds-choicebox-icon {
840
+ color: var(--mds-text-feedback-primary);
841
+ }
842
+
843
+ /* Disabled unselected icon */
844
+ .mds-choicebox-input:disabled:not(:checked)
845
+ + .mds-choicebox
846
+ .mds-choicebox-icon {
847
+ color: var(--mds-text-muted);
848
+ }
849
+
850
+ /* Disabled selected icon */
851
+ .mds-choicebox-input:disabled:checked + .mds-choicebox .mds-choicebox-icon {
852
+ color: var(--mds-text-link-primary-disabled);
853
+ }
854
+
855
+ /* --------------------------------------------------------------------------
856
+ Labels container
857
+ -------------------------------------------------------------------------- */
858
+
859
+ .mds-choicebox-labels {
860
+ display: flex;
861
+ flex-direction: column;
862
+ flex: 1 0 0;
863
+ gap: var(--mds-spacing-xxs);
864
+ padding: var(--mds-spacing-xxs) 0;
865
+ min-width: 0; /* Allow text to wrap within the flex item */
866
+ }
867
+
868
+ /* --------------------------------------------------------------------------
869
+ Label text
870
+ -------------------------------------------------------------------------- */
871
+
872
+ .mds-choicebox-label {
873
+ display: block;
874
+ font-family: var(--mds-font-family-base);
875
+ font-size: var(--mds-font-size-headings-6);
876
+ font-weight: var(--mds-font-weight-medium);
877
+ line-height: var(--mds-line-height-headings-6);
878
+ letter-spacing: var(--mds-letter-spacing);
879
+ color: var(--mds-text-default);
880
+ word-break: break-word;
881
+ }
882
+
883
+ /* Selected label */
884
+ .mds-choicebox-input:checked + .mds-choicebox .mds-choicebox-label {
885
+ color: var(--mds-text-feedback-primary);
886
+ }
887
+
888
+ /* Disabled unselected label */
889
+ .mds-choicebox-input:disabled:not(:checked)
890
+ + .mds-choicebox
891
+ .mds-choicebox-label {
892
+ color: var(--mds-text-muted);
893
+ }
894
+
895
+ /* Disabled selected label */
896
+ .mds-choicebox-input:disabled:checked + .mds-choicebox .mds-choicebox-label {
897
+ color: var(--mds-text-link-primary-disabled);
898
+ }
899
+
900
+ /* --------------------------------------------------------------------------
901
+ Supporting text
902
+ -------------------------------------------------------------------------- */
903
+
904
+ .mds-choicebox-supporting-text {
905
+ display: block;
906
+ font-family: var(--mds-font-family-base);
907
+ font-size: var(--mds-font-size-paragraph-small);
908
+ font-weight: var(--mds-font-weight-regular);
909
+ line-height: var(--mds-line-height-paragraph-small);
910
+ letter-spacing: var(--mds-letter-spacing);
911
+ color: var(--mds-text-subtle);
912
+ word-break: break-word;
913
+ }
914
+
915
+ /* Disabled unselected supporting text */
916
+ .mds-choicebox-input:disabled:not(:checked)
917
+ + .mds-choicebox
918
+ .mds-choicebox-supporting-text {
919
+ color: var(--mds-text-subtle);
920
+ }
921
+
922
+ /* Disabled selected supporting text */
923
+ .mds-choicebox-input:disabled:checked
924
+ + .mds-choicebox
925
+ .mds-choicebox-supporting-text {
926
+ color: var(--mds-text-link-primary-disabled);
927
+ }
928
+
929
+ /* --------------------------------------------------------------------------
930
+ Choice indicator
931
+ The visible circle and circle-check are rendered via ::after so no extra
932
+ DOM nodes are needed. The input's checked/disabled state drives the styling
933
+ via CSS sibling selectors.
934
+ -------------------------------------------------------------------------- */
935
+
936
+ .mds-choicebox-indicator {
937
+ display: flex;
938
+ align-items: center;
939
+ justify-content: center;
940
+ width: 16px;
941
+ height: 16px;
942
+ /* Vertically centre the indicator against the label line.
943
+ labels container has padding-block-start: spacing-xxs (0.25rem);
944
+ the label line-height is 1.2rem; indicator height is 1rem.
945
+ Offset = labels-padding-top + (label-line-height − indicator-height) / 2 */
946
+ margin-block-start: calc(
947
+ var(--mds-spacing-xxs) + (var(--mds-line-height-headings-6) - 1rem) / 2
948
+ );
949
+ flex-shrink: 0;
950
+ }
951
+
952
+ /* Unselected circle */
953
+ .mds-choicebox-indicator::after {
954
+ content: '';
955
+ display: block;
956
+ width: 16px;
957
+ height: 16px;
958
+ border-radius: var(--mds-border-radius-pill);
959
+ border: var(--mds-stroke-weight-sm) solid
960
+ var(--mds-border-interactive-secondary-default);
961
+ background-color: var(--mds-bg-surface-subtle);
962
+ box-sizing: border-box;
963
+ }
964
+
965
+ /* Unselected hover indicator background follows card background. */
966
+ .mds-choicebox-input:not(:disabled):not(:checked)
967
+ + .mds-choicebox:hover
968
+ .mds-choicebox-indicator::after {
969
+ background-color: var(--mds-bg-surface-default);
970
+ }
971
+
972
+ .mds-choicebox-input:not(:disabled):not(:checked)
973
+ + .mds-choicebox:active
974
+ .mds-choicebox-indicator::after {
975
+ background-color: var(--mds-bg-surface-strong);
976
+ }
977
+
978
+ /* Checked: filled circle with inline SVG checkmark via background-image.
979
+ Path matches the Figma design spec exactly (fill-based, 16×16 viewBox). */
980
+ .mds-choicebox-input:checked + .mds-choicebox .mds-choicebox-indicator::after {
981
+ border: none;
982
+ background-color: var(--mds-bg-interactive-primary-default);
983
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M6.05791 11.1853C6.47273 11.6049 7.1138 11.6049 7.52862 11.1853L12.3556 6.30245C12.7704 5.88283 12.7704 5.23433 12.3556 4.81471C11.9407 4.3951 11.2997 4.3951 10.8848 4.81471L6.81212 8.93461L5.11515 7.25613C4.70034 6.83651 4.05926 6.83651 3.64444 7.25613C3.22963 7.67575 3.22963 8.32425 3.64444 8.74387L6.05791 11.1853Z' fill='%23ffffff'/%3E%3C/svg%3E");
984
+ background-size: cover;
985
+ }
986
+
987
+ /* Disabled unselected indicator — muted border */
988
+ .mds-choicebox-input:disabled:not(:checked)
989
+ + .mds-choicebox
990
+ .mds-choicebox-indicator::after {
991
+ border-color: var(--mds-border-interactive-secondary-disabled);
992
+ }
993
+
994
+ /* Disabled selected indicator — dimmed fill */
995
+ .mds-choicebox-input:disabled:checked
996
+ + .mds-choicebox
997
+ .mds-choicebox-indicator::after {
998
+ background-color: var(--mds-bg-interactive-primary-disabled);
999
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M6.05791 11.1853C6.47273 11.6049 7.1138 11.6049 7.52862 11.1853L12.3556 6.30245C12.7704 5.88283 12.7704 5.23433 12.3556 4.81471C11.9407 4.3951 11.2997 4.3951 10.8848 4.81471L6.81212 8.93461L5.11515 7.25613C4.70034 6.83651 4.05926 6.83651 3.64444 7.25613C3.22963 7.67575 3.22963 8.32425 3.64444 8.74387L6.05791 11.1853Z' fill='%23ffffff'/%3E%3C/svg%3E");
1000
+ }
1001
+
1002
+ /* --------------------------------------------------------------------------
1003
+ Forced colors (Windows High Contrast)
1004
+ Card borders and indicator geometry use system colour keywords so they
1005
+ remain visible when forced-colors is active. Text colours are left to adapt
1006
+ automatically — they reference CSS custom properties that the browser will
1007
+ remap to system colours.
1008
+ Note: the SVG checkmark's hardcoded fill (%23ffffff) inside the checked
1009
+ indicator will not adapt until Must Fix 2 (mask-image refactor) is done.
1010
+ -------------------------------------------------------------------------- */
1011
+
1012
+ @media (forced-colors: active) {
1013
+ .mds-choicebox {
1014
+ border-color: ButtonText;
1015
+ }
1016
+
1017
+ .mds-choicebox-input:checked + .mds-choicebox {
1018
+ border-color: Highlight;
1019
+ }
1020
+
1021
+ .mds-choicebox-input:disabled + .mds-choicebox {
1022
+ border-color: GrayText;
1023
+ }
1024
+
1025
+ /* Preserve the indicator circle using forced-color-adjust: none so the
1026
+ border/fill remain under author control via system colour keywords. */
1027
+ .mds-choicebox-indicator::after {
1028
+ forced-color-adjust: none;
1029
+ border-color: ButtonText;
1030
+ background-color: Canvas;
1031
+ }
1032
+
1033
+ .mds-choicebox-input:checked
1034
+ + .mds-choicebox
1035
+ .mds-choicebox-indicator::after {
1036
+ forced-color-adjust: none;
1037
+ background-color: Highlight;
1038
+ /* SVG fill %23ffffff may be invisible on some Highlight backgrounds —
1039
+ fully addressed when Must Fix 2 (mask-image refactor) is completed. */
1040
+ }
1041
+
1042
+ .mds-choicebox-input:disabled:not(:checked)
1043
+ + .mds-choicebox
1044
+ .mds-choicebox-indicator::after {
1045
+ forced-color-adjust: none;
1046
+ border-color: GrayText;
1047
+ background-color: Canvas;
1048
+ }
1049
+
1050
+ .mds-choicebox-input:disabled:checked
1051
+ + .mds-choicebox
1052
+ .mds-choicebox-indicator::after {
1053
+ forced-color-adjust: none;
1054
+ background-color: GrayText;
1055
+ }
1056
+
1057
+ /* Keep the focus ring aligned with the system Highlight colour */
1058
+ .mds-choicebox-input:focus-visible
1059
+ + .mds-choicebox
1060
+ .mds-choicebox-indicator::after {
1061
+ outline-color: Highlight;
1062
+ }
1063
+ }
1064
+
1065
+ /* close-button/index.css */
1066
+ .mds-close-button.btn-close {
1067
+ inline-size: var(--mds-icons-md);
1068
+ block-size: var(--mds-icons-md);
1069
+ padding: 0;
1070
+ border-radius: var(--mds-border-radius-xs);
1071
+ background-image: url('./close-button.svg');
1072
+ background-size: var(--mds-icons-md) var(--mds-icons-md);
1073
+ opacity: 0.6;
1074
+ }
1075
+
1076
+ .mds-close-button.btn-close:hover {
1077
+ opacity: 0.75;
1078
+ }
1079
+
1080
+ .mds-close-button.btn-close:focus {
1081
+ box-shadow: none;
1082
+ }
1083
+
1084
+ .mds-close-button.btn-close:focus-visible {
1085
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1086
+ outline-offset: 0;
1087
+ box-shadow: none;
1088
+ opacity: 1;
1089
+ }
1090
+
1091
+ .mds-close-button.btn-close:disabled,
1092
+ .mds-close-button.btn-close.disabled {
1093
+ opacity: 0.25;
1094
+ }
1095
+
1096
+ .mds-close-button.btn-close.mds-close-button--sm {
1097
+ inline-size: var(--mds-icons-sm);
1098
+ block-size: var(--mds-icons-sm);
1099
+ background-size: var(--mds-icons-sm) var(--mds-icons-sm);
1100
+ }
1101
+
1102
+ .mds-close-button.btn-close.mds-close-button--md {
1103
+ inline-size: var(--mds-icons-md);
1104
+ block-size: var(--mds-icons-md);
1105
+ background-size: var(--mds-icons-md) var(--mds-icons-md);
1106
+ }
1107
+
1108
+ .mds-close-button.btn-close.mds-close-button--lg {
1109
+ inline-size: var(--mds-icons-lg);
1110
+ block-size: var(--mds-icons-lg);
1111
+ background-size: var(--mds-icons-lg) var(--mds-icons-lg);
1112
+ }
1113
+
1114
+ /* favourite-button/index.css */
1115
+ /* Favourite button (icon-only toggle) */
1116
+
1117
+ .mds-favourite-button {
1118
+ display: inline-flex;
1119
+ align-items: center;
1120
+ justify-content: center;
1121
+ padding: var(--mds-spacing-xs);
1122
+ border: none;
1123
+ border-radius: var(--mds-border-radius-pill);
1124
+ background-color: var(--mds-bg-surface-default);
1125
+ color: var(--mds-text-default);
1126
+ cursor: pointer;
1127
+ transition:
1128
+ background-color 0.15s ease-in-out,
1129
+ color 0.15s ease-in-out;
1130
+ }
1131
+
1132
+ .mds-favourite-button__icon {
1133
+ display: block;
1134
+ inline-size: var(--mds-icons-xs);
1135
+ block-size: var(--mds-icons-xs);
1136
+ background-color: currentColor;
1137
+ mask-image: url('./assets/star-outline.svg');
1138
+ mask-repeat: no-repeat;
1139
+ mask-position: center;
1140
+ mask-size: contain;
1141
+ -webkit-mask-image: url('./assets/star-outline.svg');
1142
+ -webkit-mask-repeat: no-repeat;
1143
+ -webkit-mask-position: center;
1144
+ -webkit-mask-size: contain;
1145
+ }
1146
+
1147
+ .mds-favourite-button--selected .mds-favourite-button__icon {
1148
+ mask-image: url('./assets/star-filled.svg');
1149
+ -webkit-mask-image: url('./assets/star-filled.svg');
1150
+ }
1151
+
1152
+ /* Unselected states */
1153
+ .mds-favourite-button:hover:not(:disabled) {
1154
+ background-color: var(--mds-bg-surface-subtle);
1155
+ color: var(--mds-text-subtle);
1156
+ }
1157
+
1158
+ .mds-favourite-button:active:not(:disabled) {
1159
+ background-color: var(--mds-bg-surface-strong);
1160
+ color: var(--mds-text-default);
1161
+ }
1162
+
1163
+ /* Selected states */
1164
+ .mds-favourite-button--selected {
1165
+ background-color: var(--mds-bg-surface-default);
1166
+ color: var(--mds-text-link-primary-default);
1167
+ }
1168
+
1169
+ .mds-favourite-button--selected:hover:not(:disabled) {
1170
+ background-color: var(--mds-bg-surface-subtle);
1171
+ color: var(--mds-text-link-primary-hover);
1172
+ }
1173
+
1174
+ .mds-favourite-button--selected:active:not(:disabled) {
1175
+ background-color: var(--mds-bg-surface-strong);
1176
+ color: var(--mds-text-link-primary-default);
1177
+ }
1178
+
1179
+ /* Disabled states */
1180
+ .mds-favourite-button:disabled {
1181
+ background-color: var(--mds-bg-surface-default);
1182
+ color: var(--mds-text-muted);
1183
+ cursor: not-allowed;
1184
+ }
1185
+
1186
+ .mds-favourite-button--selected:disabled {
1187
+ color: var(--mds-text-link-primary-disabled);
1188
+ }
1189
+
1190
+ /* Focus ring */
1191
+ .mds-favourite-button:focus {
1192
+ box-shadow: none;
1193
+ outline: none;
1194
+ }
1195
+
1196
+ .mds-favourite-button:focus-visible {
1197
+ box-shadow: none;
1198
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1199
+ outline-offset: 0;
1200
+ }
1201
+
1202
+ /* link/index.css */
1203
+ .mds-link {
1204
+ display: inline-flex;
1205
+ align-items: center;
1206
+ gap: var(--mds-spacing-xs);
1207
+
1208
+ color: var(--mds-text-link-primary-default);
1209
+ font-family: var(--mds-font-family-base);
1210
+ font-weight: var(--mds-font-weight-regular);
1211
+ font-size: var(--mds-font-size-paragraph-default);
1212
+ line-height: var(--mds-line-height-paragraph-xs);
1213
+ letter-spacing: var(--mds-letter-spacing-default);
1214
+ text-decoration: none;
1215
+
1216
+ cursor: pointer;
1217
+ }
1218
+
1219
+ .mds-link:hover:not(:focus-visible) {
1220
+ color: var(--mds-text-link-primary-hover);
1221
+ }
1222
+
1223
+ .mds-link:active {
1224
+ color: var(--mds-text-link-primary-hover);
1225
+ }
1226
+
1227
+ /* Underline on hover applies only to the label text, not the icon.
1228
+ Pressed state has no underline (Figma). Thickness and position use
1229
+ from-font so the underline follows the font's own metrics. */
1230
+ .mds-link:hover:not(:focus-visible) .mds-link__label {
1231
+ text-decoration: underline;
1232
+ text-decoration-thickness: from-font;
1233
+ text-underline-position: from-font;
1234
+ }
1235
+
1236
+ /* Slide icon toward the label on hover using transform so the link node width
1237
+ stays constant (gap remains --mds-spacing-xs; only the icon moves visually). */
1238
+ .mds-link:hover:not(:focus-visible) .mds-link__icon:first-child {
1239
+ transform: translateX(var(--mds-spacing-xxs));
1240
+ }
1241
+
1242
+ .mds-link:hover:not(:focus-visible) .mds-link__icon:last-child {
1243
+ transform: translateX(calc(-1 * var(--mds-spacing-xxs)));
1244
+ }
1245
+
1246
+ [dir='rtl'] .mds-link:hover:not(:focus-visible) .mds-link__icon:first-child {
1247
+ transform: translateX(calc(-1 * var(--mds-spacing-xxs)));
1248
+ }
1249
+
1250
+ [dir='rtl'] .mds-link:hover:not(:focus-visible) .mds-link__icon:last-child {
1251
+ transform: translateX(var(--mds-spacing-xxs));
1252
+ }
1253
+
1254
+ /* Reset icon position on press */
1255
+ .mds-link:active .mds-link__icon {
1256
+ transform: none;
1257
+ }
1258
+
1259
+ .mds-link:focus {
1260
+ outline: none;
1261
+ }
1262
+
1263
+ .mds-link:focus-visible {
1264
+ /* Bottom-only focus underline matching Figma: 2px border-bottom, 1px gap below content */
1265
+ outline: none;
1266
+ border-radius: 0;
1267
+ padding-bottom: var(--mds-spacing-offset);
1268
+ border-bottom: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1269
+ }
1270
+
1271
+ .mds-link.mds-link--secondary {
1272
+ color: var(--mds-text-subtle);
1273
+ }
1274
+
1275
+ .mds-link.mds-link--secondary:hover:not(:focus-visible),
1276
+ .mds-link.mds-link--secondary:active {
1277
+ color: var(--mds-text-default);
1278
+ }
1279
+
1280
+ .mds-link.mds-link--disabled,
1281
+ .mds-link.mds-link--disabled:hover,
1282
+ .mds-link.mds-link--disabled:active {
1283
+ color: var(--mds-text-link-primary-disabled);
1284
+ pointer-events: none;
1285
+ }
1286
+
1287
+ .mds-link.mds-link--disabled .mds-link__icon,
1288
+ .mds-link.mds-link--disabled:hover .mds-link__icon,
1289
+ .mds-link.mds-link--disabled:active .mds-link__icon {
1290
+ transform: none;
1291
+ }
1292
+
1293
+ .mds-link.mds-link--disabled .mds-link__label,
1294
+ .mds-link.mds-link--disabled:hover .mds-link__label,
1295
+ .mds-link.mds-link--disabled:active .mds-link__label {
1296
+ text-decoration: none;
1297
+ }
1298
+
1299
+ .mds-link.mds-link--secondary.mds-link--disabled,
1300
+ .mds-link.mds-link--secondary.mds-link--disabled:hover,
1301
+ .mds-link.mds-link--secondary.mds-link--disabled:active {
1302
+ color: var(--mds-text-muted);
1303
+ }
1304
+
1305
+ .mds-link__icon,
1306
+ .mds-link__icon i,
1307
+ .mds-link__icon svg {
1308
+ inline-size: var(--mds-icons-xs);
1309
+ block-size: var(--mds-icons-xs);
1310
+ font-size: var(--mds-icons-xs);
1311
+ line-height: 1;
1312
+ flex-shrink: 0;
1313
+ }
1314
+
1315
+ .mds-link__icon {
1316
+ display: inline-flex;
1317
+ align-items: center;
1318
+ justify-content: center;
1319
+ transition: transform 150ms ease;
1320
+ }
1321
+
1322
+ .mds-link__label {
1323
+ text-align: start;
1324
+ }
1325
+
1326
+ /* nav-pill/index.css */
1327
+ /* Nav pill — compact navigation destination indicator */
1328
+
1329
+ .mds-nav-pill,
1330
+ .mds-nav-pill:visited {
1331
+ /* Reset anchor/link defaults */
1332
+ display: inline-flex;
1333
+ align-items: center;
1334
+ gap: var(--mds-spacing-xxs);
1335
+ padding: var(--mds-spacing-xs) var(--mds-spacing-sm);
1336
+ border: none;
1337
+ background-color: transparent;
1338
+ cursor: pointer;
1339
+
1340
+ border-radius: var(--mds-border-radius-md);
1341
+
1342
+ /* Label colour: unselected default */
1343
+ color: var(--mds-text-subtle);
1344
+ text-decoration: none;
1345
+ }
1346
+
1347
+ .mds-nav-pill:hover,
1348
+ .mds-nav-pill:focus,
1349
+ .mds-nav-pill:active,
1350
+ .mds-nav-pill:visited {
1351
+ text-decoration: none;
1352
+ }
1353
+
1354
+ /* ---- Label ---- */
1355
+
1356
+ .mds-nav-pill__label {
1357
+ /* UI small — matches Figma: $font-size-sm / font-weight/medium / line-height/paragraph/xs */
1358
+ font-family: var(--mds-font-family-base);
1359
+ font-size: var(--mds-font-size-paragraph-small);
1360
+ font-weight: var(--mds-font-weight-medium);
1361
+ line-height: var(--mds-line-height-paragraph-xs);
1362
+ white-space: nowrap;
1363
+ }
1364
+
1365
+ /* ---- Active-indicator dot (selected state only) ---- */
1366
+
1367
+ .mds-nav-pill__indicator {
1368
+ display: block;
1369
+ width: 4px;
1370
+ height: 4px;
1371
+ border-radius: var(--mds-border-radius-pill);
1372
+ background-color: var(--mds-bg-interactive-primary-default);
1373
+ flex-shrink: 0;
1374
+ }
1375
+
1376
+ /* ---- Hover (unselected) ---- */
1377
+
1378
+ .mds-nav-pill:hover:not([aria-disabled='true']):not(.mds-nav-pill--selected) {
1379
+ background-color: var(--mds-bg-nav-pill-hover);
1380
+ }
1381
+
1382
+ /* ---- Pressed / active (unselected) ---- */
1383
+
1384
+ .mds-nav-pill:active:not([aria-disabled='true']):not(.mds-nav-pill--selected) {
1385
+ background-color: var(--mds-bg-nav-pill-pressed);
1386
+ }
1387
+
1388
+ /* ---- Focus-visible ring — keyboard navigation ---- */
1389
+
1390
+ .mds-nav-pill:focus {
1391
+ outline: none;
1392
+ box-shadow: none;
1393
+ }
1394
+
1395
+ .mds-nav-pill:focus-visible {
1396
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1397
+ outline-offset: var(--mds-spacing-offset);
1398
+ box-shadow: none;
1399
+ }
1400
+
1401
+ /* ---- Disabled ---- */
1402
+
1403
+ .mds-nav-pill[aria-disabled='true'] {
1404
+ color: var(--mds-text-muted);
1405
+ cursor: not-allowed;
1406
+ background-color: transparent;
1407
+ }
1408
+
1409
+ /* ---- Selected state ---- */
1410
+
1411
+ .mds-nav-pill--selected {
1412
+ color: var(--mds-text-default);
1413
+ background-color: var(--mds-bg-nav-pill-selected);
1414
+ }
1415
+
1416
+ .mds-nav-pill--selected:visited {
1417
+ color: var(--mds-text-default);
1418
+ }
1419
+
1420
+ .mds-nav-pill--selected:hover:not([aria-disabled='true']) {
1421
+ background-color: var(--mds-bg-nav-pill-selected);
1422
+ }
1423
+
1424
+ /* pagination/index.css */
1425
+ .mds-pagination {
1426
+ display: flex;
1427
+ gap: var(--mds-spacing-xxs);
1428
+ align-items: center;
1429
+ flex-wrap: wrap;
1430
+ font-family: inherit;
1431
+ direction: inherit;
1432
+ }
1433
+
1434
+ /* Shared base styles for buttons and page items */
1435
+ .mds-pagination__button,
1436
+ .mds-pagination__page {
1437
+ display: flex;
1438
+ align-items: center;
1439
+ justify-content: center;
1440
+ min-width: 34px;
1441
+ min-height: 34px;
1442
+ padding: var(--mds-spacing-xs);
1443
+ border: var(--mds-stroke-weight-sm) solid transparent;
1444
+ border-radius: var(--mds-border-radius-xs);
1445
+ background-color: transparent;
1446
+ color: var(--mds-text-default);
1447
+ font-size: inherit;
1448
+ font-family: inherit;
1449
+ cursor: pointer;
1450
+ -webkit-user-select: none;
1451
+ user-select: none;
1452
+ }
1453
+
1454
+ /* Keep hover affordance on prev/next controls only. Page-number buttons must
1455
+ switch state atomically to avoid visible handoff when ellipsis positions move. */
1456
+ .mds-pagination__button {
1457
+ transition: background-color 150ms ease;
1458
+ }
1459
+
1460
+ .mds-pagination__page {
1461
+ transition: none;
1462
+ }
1463
+
1464
+ .mds-pagination__button:hover:not(:disabled),
1465
+ .mds-pagination__page:hover:not(:disabled) {
1466
+ background-color: var(--mds-bg-interactive-secondary-hover);
1467
+ }
1468
+
1469
+ .mds-pagination__button:active:not(:disabled),
1470
+ .mds-pagination__page:active:not(:disabled) {
1471
+ background-color: var(--mds-bg-interactive-secondary-active);
1472
+ }
1473
+
1474
+ .mds-pagination__button:focus,
1475
+ .mds-pagination__page:focus {
1476
+ box-shadow: none;
1477
+ outline: none;
1478
+ }
1479
+
1480
+ .mds-pagination__button:focus-visible:not(:disabled),
1481
+ .mds-pagination__page:focus-visible:not(:disabled) {
1482
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1483
+ outline-offset: var(--mds-spacing-offset);
1484
+ box-shadow: none;
1485
+ }
1486
+
1487
+ .mds-pagination__button:disabled,
1488
+ .mds-pagination__page:disabled {
1489
+ color: var(--mds-text-muted);
1490
+ cursor: not-allowed;
1491
+ }
1492
+
1493
+ /* Icon sizing */
1494
+ .mds-pagination__button i {
1495
+ /* width/font-size use --mds-icons-sm (= scale-600 = 1rem = 16px, the icon md size). */
1496
+ width: var(--mds-icons-sm);
1497
+ font-size: var(--mds-icons-sm);
1498
+ line-height: var(--mds-line-height-paragraph-xs);
1499
+ }
1500
+
1501
+ /* Full variant */
1502
+ .mds-pagination--full .mds-pagination__pages {
1503
+ display: flex;
1504
+ gap: var(--mds-spacing-xxs);
1505
+ align-items: center;
1506
+ }
1507
+
1508
+ /* Page items */
1509
+ .mds-pagination__page {
1510
+ line-height: var(--mds-line-height-paragraph-xs);
1511
+ }
1512
+
1513
+ /* Current page state */
1514
+ .mds-pagination__page[data-current='true'] {
1515
+ background-color: var(--mds-bg-interactive-primary-default);
1516
+ color: var(--mds-text-inverse);
1517
+ }
1518
+
1519
+ .mds-pagination__page[data-current='true']:disabled {
1520
+ background-color: transparent;
1521
+ color: var(--mds-text-muted);
1522
+ }
1523
+
1524
+ .mds-pagination__page[data-current='true']:hover:not(:disabled) {
1525
+ background-color: var(--mds-bg-interactive-primary-hover);
1526
+ }
1527
+
1528
+ .mds-pagination__page[data-current='true']:active:not(:disabled) {
1529
+ background-color: var(--mds-bg-interactive-primary-active);
1530
+ }
1531
+
1532
+ .mds-pagination__page[data-current='true']:focus-visible {
1533
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1534
+ outline-offset: var(--mds-spacing-offset);
1535
+ box-shadow: none;
1536
+ }
1537
+
1538
+ /* Ellipsis */
1539
+ .mds-pagination__ellipsis {
1540
+ display: flex;
1541
+ align-items: center;
1542
+ justify-content: center;
1543
+ min-width: 34px;
1544
+ min-height: 34px;
1545
+ color: var(--mds-text-muted);
1546
+ user-select: none;
1547
+ pointer-events: none;
1548
+ }
1549
+
1550
+ /* Grouped variant - only shows prev/next buttons */
1551
+ .mds-pagination--grouped .mds-pagination__pages {
1552
+ display: none;
1553
+ }
1554
+
1555
+ /* Grouped button styling */
1556
+ .mds-pagination--grouped .mds-pagination__button {
1557
+ border-color: var(--mds-border-interactive-secondary-default);
1558
+ }
1559
+
1560
+ /* Flip prev/next chevrons in RTL so they point in the correct travel direction */
1561
+ [dir='rtl'] .mds-pagination__button i {
1562
+ transform: scaleX(-1);
1563
+ }
1564
+
1565
+ /* progress-bar/index.css */
1566
+ /* Wrapper layout */
1567
+ .mds-progress-bar {
1568
+ display: flex;
1569
+ flex-direction: column;
1570
+ gap: var(--mds-spacing-xs);
1571
+ /* 120px is intentionally has no token by design just as in Figma. */
1572
+ min-width: 120px;
1573
+ width: 100%;
1574
+ }
1575
+
1576
+ /* Inline variant: bar on the left, count on the right in one row. */
1577
+ .mds-progress-bar.mds-progress-bar--label-inline {
1578
+ flex-direction: row;
1579
+ align-items: center;
1580
+ }
1581
+
1582
+ /* Track (outer container) */
1583
+ .mds-progress-bar-track.progress {
1584
+ /* Re-state Bootstrap progress primitives here so host .progress overrides cannot drift MDS visuals. */
1585
+ /* 0.375rem = 6px from Figma; intentionally has no token by design for consumer flexibility. */
1586
+ display: flex;
1587
+ height: 0.375rem;
1588
+ overflow: hidden;
1589
+ border-radius: var(--mds-border-radius-pill);
1590
+ background-color: var(--mds-bg-feedback-secondary-default);
1591
+ box-shadow: none;
1592
+ }
1593
+
1594
+ /* In-progress and loading use the primary-subtle track. */
1595
+ .mds-progress-bar--in-progress .mds-progress-bar-track,
1596
+ .mds-progress-bar--loading .mds-progress-bar-track {
1597
+ background-color: var(--mds-bg-feedback-primary-subtle);
1598
+ }
1599
+
1600
+ /* Error track. */
1601
+ .mds-progress-bar--error .mds-progress-bar-track {
1602
+ background-color: var(--mds-bg-feedback-danger-subtle);
1603
+ }
1604
+
1605
+ /* Warning track. */
1606
+ .mds-progress-bar--warning .mds-progress-bar-track {
1607
+ background-color: var(--mds-bg-feedback-warning-subtle);
1608
+ }
1609
+
1610
+ /* Completed and empty keep the default secondary track. */
1611
+ .mds-progress-bar--completed .mds-progress-bar-track,
1612
+ .mds-progress-bar--empty .mds-progress-bar-track {
1613
+ background-color: var(--mds-bg-feedback-secondary-default);
1614
+ }
1615
+
1616
+ /* Inline variant: track should fill available row space. */
1617
+ .mds-progress-bar--label-inline .mds-progress-bar-track {
1618
+ flex: 1 1 0;
1619
+ min-inline-size: var(--mds-spacing-xxl);
1620
+ }
1621
+
1622
+ /* Fill (inner progress indicator) */
1623
+ .mds-progress-bar-fill.progress-bar {
1624
+ /* Keep core progress-bar behavior local so host --bs-progress-* token changes do not leak in. */
1625
+ display: flex;
1626
+ overflow: hidden;
1627
+ background-color: var(--mds-bg-interactive-primary-default);
1628
+ transition: width 0.3s ease;
1629
+ }
1630
+
1631
+ /* Explicit striped styles avoid dependence on host Bootstrap variables. */
1632
+ .mds-progress-bar-fill.progress-bar-striped {
1633
+ background-image: linear-gradient(
1634
+ 45deg,
1635
+ rgba(255, 255, 255, 0.15) 25%,
1636
+ transparent 25%,
1637
+ transparent 50%,
1638
+ rgba(255, 255, 255, 0.15) 50%,
1639
+ rgba(255, 255, 255, 0.15) 75%,
1640
+ transparent 75%,
1641
+ transparent
1642
+ );
1643
+ /* Figma loading pattern uses a larger tile than the 6px bar height. */
1644
+ background-size: var(--mds-spacing-md) var(--mds-spacing-md);
1645
+ background-repeat: repeat;
1646
+ background-position-x: 0;
1647
+ }
1648
+
1649
+ [dir='rtl'] .mds-progress-bar-fill.progress-bar-striped {
1650
+ background-image: linear-gradient(
1651
+ -45deg,
1652
+ rgba(255, 255, 255, 0.15) 25%,
1653
+ transparent 25%,
1654
+ transparent 50%,
1655
+ rgba(255, 255, 255, 0.15) 50%,
1656
+ rgba(255, 255, 255, 0.15) 75%,
1657
+ transparent 75%,
1658
+ transparent
1659
+ );
1660
+ }
1661
+
1662
+ .mds-progress-bar-fill.progress-bar-animated {
1663
+ animation: mds-progress-bar-stripes 0.8s linear infinite;
1664
+ }
1665
+
1666
+ [dir='rtl'] .mds-progress-bar-fill.progress-bar-animated {
1667
+ animation-name: mds-progress-bar-stripes-rtl;
1668
+ }
1669
+
1670
+ @keyframes mds-progress-bar-stripes {
1671
+ 0% {
1672
+ background-position-x: var(--mds-spacing-md);
1673
+ }
1674
+ }
1675
+
1676
+ @keyframes mds-progress-bar-stripes-rtl {
1677
+ 0% {
1678
+ background-position-x: calc(var(--mds-spacing-md) * -1);
1679
+ }
1680
+ }
1681
+
1682
+ /* Error fill. */
1683
+ .mds-progress-bar--error .mds-progress-bar-fill {
1684
+ background-color: var(--mds-bg-feedback-danger-default);
1685
+ }
1686
+
1687
+ /* Warning fill. */
1688
+ .mds-progress-bar--warning .mds-progress-bar-fill {
1689
+ background-color: var(--mds-bg-feedback-warning-default);
1690
+ }
1691
+
1692
+ /* Completed fill. */
1693
+ .mds-progress-bar--completed .mds-progress-bar-fill {
1694
+ background-color: var(--mds-bg-feedback-success-default);
1695
+ }
1696
+
1697
+ /* Empty fill blends with the track. */
1698
+ .mds-progress-bar--empty .mds-progress-bar-fill {
1699
+ background-color: var(--mds-bg-feedback-secondary-default);
1700
+ }
1701
+
1702
+ /* Label container */
1703
+ .mds-progress-bar-label {
1704
+ display: flex;
1705
+ justify-content: space-between;
1706
+ align-items: center;
1707
+ gap: var(--mds-spacing-xs);
1708
+ width: 100%;
1709
+
1710
+ /* UI text/small */
1711
+ font-family: var(--mds-font-family-base);
1712
+ font-size: var(--mds-font-size-paragraph-small);
1713
+ line-height: var(--mds-line-height-paragraph-small);
1714
+ letter-spacing: var(--mds-letter-spacing-default);
1715
+ color: var(--mds-text-subtle);
1716
+ }
1717
+
1718
+ /* Title text */
1719
+ .mds-progress-bar-title {
1720
+ font-weight: var(--mds-font-weight-medium);
1721
+ line-height: var(--mds-line-height-paragraph-small);
1722
+ word-break: break-word;
1723
+ }
1724
+
1725
+ /* In title-and-count layout, title and count share one row and must both be able to shrink. */
1726
+ .mds-progress-bar--label-title-and-count .mds-progress-bar-title {
1727
+ flex: 1 1 auto;
1728
+ min-inline-size: 0;
1729
+ }
1730
+
1731
+ /* Count / percentage text */
1732
+ .mds-progress-bar-count {
1733
+ font-weight: var(--mds-font-weight-regular);
1734
+
1735
+ /* Inline count sits outside .mds-progress-bar-label, so it needs explicit text styles. */
1736
+ font-family: var(--mds-font-family-base);
1737
+ font-size: var(--mds-font-size-paragraph-small);
1738
+ line-height: var(--mds-line-height-paragraph-small);
1739
+ letter-spacing: var(--mds-letter-spacing-default);
1740
+ color: var(--mds-text-subtle);
1741
+ white-space: normal;
1742
+ overflow-wrap: anywhere;
1743
+ min-inline-size: 0;
1744
+ }
1745
+
1746
+ /* In title-and-count layout, count is bounded to prevent hogging the row. */
1747
+ .mds-progress-bar--label-title-and-count .mds-progress-bar-count {
1748
+ flex: 0 1 50%;
1749
+ max-inline-size: 50%;
1750
+ text-align: end;
1751
+ }
1752
+
1753
+ /* In inline layout, count takes only what it needs, leaving space for the track. */
1754
+ .mds-progress-bar--label-inline .mds-progress-bar-count {
1755
+ flex: 0 1 auto;
1756
+ max-inline-size: 40%;
1757
+ text-align: start;
1758
+ }
1759
+
1760
+ /* radio/index.css */
1761
+ /* Parent element */
1762
+ .mds-form-check {
1763
+ /* Grid layout: column 1 = input, column 2 = label. The feedback is then placed
1764
+ explicitly into column 2 via grid-column so it aligns with the label without
1765
+ requiring any offset calculation. Grid formatting context also suppresses
1766
+ Bootstrap's float on the input child. */
1767
+ display: inline-grid;
1768
+ grid-template-columns: auto auto;
1769
+ align-items: start;
1770
+ /* Prevent flex containers from stretching this element across the cross axis,
1771
+ which would widen the grid and split the two auto columns equally, pushing
1772
+ the label away from the input. start preserves content-sized behaviour. */
1773
+ align-self: start;
1774
+ /* min-height matches the icon-lg token (24px) so the wrapper never collapses
1775
+ below the height of the radio indicator, regardless of label content. */
1776
+ min-height: var(--mds-icons-lg, 1.5rem);
1777
+ /* Reset Bootstrap's padding-inline-start — it exists to indent label text past a
1778
+ floated input, which our grid layout does not use. column-gap handles the spacing. */
1779
+ padding: var(--mds-spacing-none, 0);
1780
+ /* column-gap keeps input and label spaced; row-gap gives breathing room above feedback */
1781
+ column-gap: var(--mds-spacing-xs, 0.5rem);
1782
+ row-gap: var(--mds-spacing-xxs, 0.25rem);
1783
+
1784
+ /* UI text/UI default */
1785
+ font-family: var(--mds-font-family-base, Arial, sans-serif);
1786
+ font-size: var(--mds-font-size-body, 1rem);
1787
+ font-weight: var(--mds-font-weight-regular, 400);
1788
+ line-height: var(--mds-line-height-paragraph-xs, 0.75rem);
1789
+ letter-spacing: var(--mds-letter-spacing, 0);
1790
+ }
1791
+
1792
+ /* Input element styles */
1793
+ .mds-form-check-input {
1794
+ border-radius: var(--mds-border-radius-pill, 50rem);
1795
+ border: var(--mds-stroke-weight-sm, 1px) solid
1796
+ var(--mds-border-interactive-secondary-default, #6a737b);
1797
+ /* Use background-color, not the background shorthand — the shorthand resets
1798
+ background-image to none, which wipes out Bootstrap's --bs-form-check-bg-image
1799
+ (the inner dot SVG) on the checked state. */
1800
+ }
1801
+ /* Bootstrap's .form-check .form-check-input rule (specificity 0,2,0) sets float: left
1802
+ and a negative margin-inline-start that causes overlap. Match its specificity to
1803
+ ensure our flex-friendly reset wins regardless of load order. */
1804
+ .mds-form-check .mds-form-check-input {
1805
+ margin: 0;
1806
+ float: none;
1807
+ background-color: var(--mds-bg-surface-default, #fff);
1808
+ }
1809
+ .mds-form-check-input:checked {
1810
+ background-color: var(--mds-bg-interactive-primary-default);
1811
+ border-color: transparent;
1812
+ }
1813
+ .mds-form-check-input:disabled {
1814
+ border-color: var(--mds-border-interactive-secondary-disabled);
1815
+ }
1816
+ .mds-form-check-input:disabled:checked {
1817
+ background-color: var(--mds-bg-interactive-primary-disabled);
1818
+ border-color: transparent;
1819
+ }
1820
+ .mds-form-check-input.is-invalid {
1821
+ border-color: var(--mds-border-interactive-danger-default);
1822
+ }
1823
+ .mds-form-check-input.is-invalid:checked {
1824
+ background-color: var(--mds-bg-interactive-danger-default);
1825
+ border-color: transparent;
1826
+ }
1827
+ /* Explicit rule for invalid + disabled to avoid relying on specificity cascade order.
1828
+ Disabled takes visual precedence: use the disabled background/border, not the danger ones. */
1829
+ .mds-form-check-input.is-invalid:disabled {
1830
+ background-color: var(--mds-bg-interactive-secondary-disabled);
1831
+ border-color: var(--mds-border-interactive-secondary-disabled);
1832
+ }
1833
+ .mds-form-check-input.is-invalid:disabled:checked {
1834
+ background-color: var(--mds-bg-interactive-primary-disabled);
1835
+ border-color: transparent;
1836
+ }
1837
+ /* is-valid (Bootstrap) and neutral feedback states are intentionally not supported.
1838
+ Radio buttons surface a binary valid/invalid result; positive confirmation
1839
+ is conveyed by form-level success messaging rather than per-input valid styling. */
1840
+ /* Hover styles are intentionally omitted. The radio input relies on the OS/browser
1841
+ default appearance for hover; no design token exists for a radio hover state. */
1842
+
1843
+ /* Focus ring: Only outline + outline-offset allowed (no box-shadow or border).
1844
+ See .github/instructions/components.instructions.md for full rules and rationale. */
1845
+ .mds-form-check-input:focus,
1846
+ .mds-form-check-input.is-invalid:focus {
1847
+ /* Reset Bootstrap's :focus box-shadow; our ring is applied on :focus-visible only */
1848
+ box-shadow: none;
1849
+ outline: none;
1850
+ }
1851
+ .mds-form-check-input:focus-visible {
1852
+ outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
1853
+ outline-offset: var(--mds-spacing-offset);
1854
+ box-shadow: none;
1855
+ }
1856
+ .mds-form-check-input.is-invalid:focus-visible {
1857
+ outline: var(--mds-stroke-weight-md) solid var(--mds-border-feedback-danger);
1858
+ outline-offset: var(--mds-spacing-offset);
1859
+ box-shadow: none;
1860
+ }
1861
+
1862
+ /* Label element styles */
1863
+ .mds-form-check .form-check-label,
1864
+ .mds-form-check .mds-form-check-label {
1865
+ color: var(--mds-text-default, #1d2125);
1866
+ }
1867
+ /* Use sibling selector so the label reflects the input's disabled/invalid state */
1868
+ .mds-form-check .mds-form-check-input:disabled ~ .form-check-label,
1869
+ .mds-form-check .mds-form-check-input:disabled ~ .mds-form-check-label {
1870
+ color: var(--mds-text-muted);
1871
+ }
1872
+ .mds-form-check .mds-form-check-input.is-invalid ~ .form-check-label,
1873
+ .mds-form-check .mds-form-check-input.is-invalid ~ .mds-form-check-label {
1874
+ color: var(--mds-text-danger);
1875
+ }
1876
+
1877
+ /* Feedback element styles — UI text/UI small */
1878
+ .mds-form-check .invalid-feedback,
1879
+ .mds-form-check .mds-form-check-feedback {
1880
+ /* Place feedback in column 2 (the label column) so it aligns with the label
1881
+ regardless of input size or font-size differences. Row placement is automatic. */
1882
+ grid-column: 2;
1883
+ font-size: var(--mds-font-size-paragraph-small, 0.875rem);
1884
+ font-weight: var(--mds-font-weight-medium, 500);
1885
+ line-height: var(--mds-line-height-paragraph-xs, 0.75rem);
1886
+ color: var(--mds-text-danger);
1887
+ }
1888
+ /* Dim the feedback when the input is disabled to match the reduced visual weight of
1889
+ the label and input; keeps the feedback legible without implying it is actionable. */
1890
+ .mds-form-check:has(.mds-form-check-input:disabled) .invalid-feedback,
1891
+ .mds-form-check:has(.mds-form-check-input:disabled) .mds-form-check-feedback {
1892
+ opacity: 0.5;
1893
+ }