@databricks/design-system 2.0.5 → 2.0.7

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 (52) hide show
  1. package/AGENTS.md +239 -166
  2. package/CHANGELOG.md +47 -0
  3. package/design-guidance/docs/README.md +40 -0
  4. package/design-guidance/docs/review-contract.md +22 -0
  5. package/design-guidance/docs/tokens.md +35 -0
  6. package/design-guidance/docs/voice.md +40 -0
  7. package/design-guidance/skills/dbui-build-screen.md +24 -0
  8. package/design-guidance/skills/dbui-check-ecosystem.md +21 -0
  9. package/design-guidance/skills/dbui-check-guidelines.md +29 -0
  10. package/design-guidance/skills/dbui-check-principles.md +26 -0
  11. package/design-guidance/skills/dbui-check-voice.md +17 -0
  12. package/design-guidance/skills/dbui-pick-component.md +23 -0
  13. package/design-guidance/skills/dbui-pick-icon.md +23 -0
  14. package/design-guidance/skills/dbui-review.md +30 -0
  15. package/design-guidance/skills/dbui-validate.md +23 -0
  16. package/dist/{RHFControlledTypeaheadComboboxV2-DsH-nIEk.js → RHFControlledTypeaheadComboboxV2-nSSYj16I.js} +5 -5
  17. package/dist/{RHFControlledTypeaheadComboboxV2-DsH-nIEk.js.map → RHFControlledTypeaheadComboboxV2-nSSYj16I.js.map} +1 -1
  18. package/dist/{WizardStepContentWrapper-Bq1wm0V7.js → WizardStepContentWrapper-bbh_CoIf.js} +644 -679
  19. package/dist/WizardStepContentWrapper-bbh_CoIf.js.map +1 -0
  20. package/dist/dubois-colors.less +1 -1
  21. package/dist/icon-metadata.json +2069 -0
  22. package/dist/{index-BRAVZg-S.js → index-CUCuUviS.js} +350 -139
  23. package/dist/index-CUCuUviS.js.map +1 -0
  24. package/dist/index-dark.css +593 -191
  25. package/dist/index-dark.mitigated.css +623 -208
  26. package/dist/index.css +1536 -1049
  27. package/dist/index.js +4 -4
  28. package/dist/index.mitigated.css +1566 -1066
  29. package/dist/patterns.js +3 -3
  30. package/dist-types/design-system/Alert/Alert.d.ts +21 -4
  31. package/dist-types/design-system/Button/Button.d.ts +2 -1
  32. package/dist-types/design-system/Drawer/Drawer.d.ts +3 -1
  33. package/dist-types/design-system/Icon/AntdIcon.d.ts +3 -0
  34. package/dist-types/design-system/Icon/Icon.d.ts +20 -6
  35. package/dist-types/design-system/Icon/NativeIcon.d.ts +3 -0
  36. package/dist-types/design-system/Icon/__generated/icons/LakewatchIcon.d.ts +4 -0
  37. package/dist-types/design-system/Icon/__generated/icons/SlidesIcon.d.ts +4 -0
  38. package/dist-types/design-system/Icon/__generated/icons/index.d.ts +2 -0
  39. package/dist-types/design-system/Icon/index.d.ts +1 -0
  40. package/dist-types/design-system/Icon/utils.d.ts +1 -0
  41. package/dist-types/design-system/TypeaheadCombobox/TypeaheadComboboxControls.d.ts +0 -2
  42. package/dist-types/design-system/_shared_/Tabs/styles.d.ts +2 -3
  43. package/dist-types/design-system/index.d.ts +0 -1
  44. package/dist-types/design-system/utils/safex.d.ts +4 -2
  45. package/dist-types/theme/generalVariables.d.ts +0 -1
  46. package/dist-types/~patterns/Wizard/WizardModal.d.ts +1 -1
  47. package/package.json +8 -5
  48. package/setup.mjs +586 -0
  49. package/dist/WizardStepContentWrapper-Bq1wm0V7.js.map +0 -1
  50. package/dist/index-BRAVZg-S.js.map +0 -1
  51. package/dist-types/design-system/LegacyTooltip/LegacyTooltip.d.ts +0 -47
  52. package/dist-types/design-system/LegacyTooltip/index.d.ts +0 -1
package/dist/index.css CHANGED
@@ -16046,7 +16046,7 @@ span.du-bois-light-typography-ellipsis-single-line {
16046
16046
 
16047
16047
  --db-surface-selected: var(--db-color-table-background-selected-hover);
16048
16048
  --db-surface-selected-hover: initial; /* TODO */;
16049
- --db-surface-selected-active: initial; /* TODO */;
16049
+ --db-surface-selected-active: var(--db-color-table-background-selected-hover); /* no distinct active value */
16050
16050
 
16051
16051
  /* Surface alpha-blending modifiers */
16052
16052
  --db-surface-backdrop: initial; /* TODO */;
@@ -16072,7 +16072,7 @@ span.du-bois-light-typography-ellipsis-single-line {
16072
16072
  --db-accent-surface-strong: var(--db-color-action-primary-background-hover);
16073
16073
 
16074
16074
  --db-accent-surface-hover: var(--db-color-action-primary-background-hover);
16075
- --db-accent-surface-active: initial; /* TODO */
16075
+ --db-accent-surface-active: var(--db-color-action-primary-background-press); /* no distinct active value */
16076
16076
  --db-accent-surface-selected: var(--db-color-action-primary-background-press);
16077
16077
 
16078
16078
  /* In the current design, the text color for an accent surface does not change with hover, active, etc. */
@@ -16089,7 +16089,7 @@ span.du-bois-light-typography-ellipsis-single-line {
16089
16089
  --db-negative-surface-strong: var(--db-color-action-danger-primary-background-hover);
16090
16090
 
16091
16091
  --db-negative-surface-hover: var(--db-color-action-danger-primary-background-hover);
16092
- --db-negative-surface-active: initial; /* TODO */
16092
+ --db-negative-surface-active: var(--db-color-action-danger-primary-background-press); /* no distinct active value */
16093
16093
  --db-negative-surface-selected: var(--db-color-action-danger-primary-background-press);
16094
16094
 
16095
16095
  --db-negative-text: var(--db-color-text-validation-danger);
@@ -16130,6 +16130,11 @@ span.du-bois-light-typography-ellipsis-single-line {
16130
16130
  --db-positive-border: initial; /* TODO */
16131
16131
  --db-positive-border-hover: initial; /* TODO */
16132
16132
  --db-positive-border-active: initial; /* TODO */
16133
+
16134
+ /* Branded */
16135
+ --db-ai-gradient-start: #4299e0;
16136
+ --db-ai-gradient-mid: #ca42e0;
16137
+ --db-ai-gradient-end: #ff5f46;
16133
16138
  }
16134
16139
 
16135
16140
 
@@ -16210,6 +16215,59 @@ span.du-bois-light-typography-ellipsis-single-line {
16210
16215
  --db-shape-pill: 999px;
16211
16216
  }
16212
16217
 
16218
+ :where(:root), :where(.du-bois-light) {
16219
+ /* Shape */
16220
+ /* Mirrors the ShapeTokens host-override var used by Card; falls back to the theme radius. */
16221
+ --db-card-border-radius: var(--info-container-border-radius, 8px);
16222
+
16223
+ /* Color */
16224
+ --db-card-surface: var(--db-surface-base);
16225
+ --db-card-text: var(--db-text-base);
16226
+ --db-card-border: var(--db-border-base);
16227
+ --db-card-border-hover: var(--db-border-strong);
16228
+ }
16229
+
16230
+ :where(:root), :where(.du-bois-light) {
16231
+ /* Shape */
16232
+ /* Mirrors the ShapeTokens host-override var used by list items; falls back to the theme radius. */
16233
+ --db-list-item-border-radius: var(--list-item-border-radius, 4px);
16234
+
16235
+ /* Color */
16236
+ --db-list-surface: var(--db-surface-base);
16237
+ --db-list-text: var(--db-text-base);
16238
+ --db-list-header-text: var(--db-text-subtle);
16239
+ --db-list-separator: var(--db-border-subtle);
16240
+
16241
+ --db-list-item-surface-hover: var(--db-surface-hover);
16242
+ --db-list-item-surface-active: var(--db-surface-active);
16243
+ --db-list-item-surface-selected: var(--db-accent-surface-subtle);
16244
+ --db-list-item-surface-disabled: var(--db-surface-disabled);
16245
+
16246
+ --db-list-item-text-selected: var(--db-accent-text);
16247
+ --db-list-item-text-disabled: var(--db-text-disabled);
16248
+
16249
+ --db-list-item-danger-text: var(--db-negative-text);
16250
+ --db-list-item-danger-surface-hover: var(--db-negative-surface-subtle);
16251
+
16252
+ /* Density */
16253
+ --db-list-item-padding: var(--db-space-2);
16254
+ --db-list-item-height: var(--db-size-8);
16255
+ }
16256
+
16257
+ :where(:root), :where(.du-bois-light) {
16258
+ /* Shape */
16259
+ /* Mirrors the ShapeTokens host-override var; falls back to the v1 value (borderRadiusSm). */
16260
+ --db-popup-border-radius: var(--popup-border-radius, 4px);
16261
+
16262
+ /* Color */
16263
+ --db-popup-surface: var(--db-surface-base);
16264
+ --db-popup-text: var(--db-text-base);
16265
+ --db-popup-border: var(--db-border-base);
16266
+
16267
+ /* Elevation */
16268
+ --db-popup-elevation: var(--db-elevation-med);
16269
+ }
16270
+
16213
16271
  :where(:root), :where(.du-bois-light) {
16214
16272
  /* Shape */
16215
16273
  --db-selection-toggle-border-radius: 4px;
@@ -16221,15 +16279,15 @@ span.du-bois-light-typography-ellipsis-single-line {
16221
16279
  --db-selection-toggle-border: var(--db-border-strong);
16222
16280
 
16223
16281
  --db-selection-toggle-surface-hover: var(--db-surface-hover);
16224
- --db-selection-toggle-surface-press: var(--db-surface-selected);
16282
+ --db-selection-toggle-surface-active: var(--db-surface-selected-active);
16225
16283
  --db-selection-toggle-surface-checked: var(--db-accent-surface);
16226
16284
  --db-selection-toggle-surface-checked-hover: var(--db-accent-surface-hover);
16227
- --db-selection-toggle-surface-checked-press: var(--db-accent-surface-selected);
16285
+ --db-selection-toggle-surface-checked-active: var(--db-accent-surface-active);
16228
16286
  --db-selection-toggle-surface-disabled: var(--db-surface-disabled);
16229
16287
  --db-selection-toggle-surface-checked-disabled: var(--db-surface-disabled);
16230
16288
 
16231
16289
  --db-selection-toggle-border-hover: var(--db-accent-surface-hover);
16232
- --db-selection-toggle-border-press: var(--db-accent-surface-selected);
16290
+ --db-selection-toggle-border-active: var(--db-accent-surface-active);
16233
16291
  --db-selection-toggle-border-disabled: var(--db-border-base);
16234
16292
 
16235
16293
  --db-selection-toggle-mark-disabled: var(--db-text-disabled);
@@ -16247,338 +16305,492 @@ span.du-bois-light-typography-ellipsis-single-line {
16247
16305
 
16248
16306
  :where(:root), :where(.du-bois-light) {
16249
16307
  /* Shape */
16250
- --db-checkbox-border-radius: var(--db-selection-toggle-border-radius);
16251
- --db-checkbox-border-width: 1px;
16252
-
16253
- /* Color */
16254
- --db-checkbox-surface: var(--db-selection-toggle-surface);
16255
- --db-checkbox-surface-hover: var(--db-color-action-default-background-hover);
16256
- --db-checkbox-surface-press: var(--db-color-action-default-background-press);
16257
- --db-checkbox-surface-checked: var(--db-selection-toggle-surface-checked);
16258
- --db-checkbox-surface-checked-hover: var(--db-selection-toggle-surface-checked-hover);
16259
- --db-checkbox-surface-checked-press: var(--db-selection-toggle-surface-checked-press);
16260
- --db-checkbox-surface-disabled: var(--db-selection-toggle-surface-disabled);
16261
-
16262
- --db-checkbox-border: var(--db-color-action-default-border-default);
16263
- --db-checkbox-border-hover: var(--db-selection-toggle-border-hover);
16264
- --db-checkbox-border-press: var(--db-selection-toggle-border-press);
16265
- --db-checkbox-focus-ring: var(--db-focus-ring-color);
16266
- --db-checkbox-border-disabled: var(--db-color-action-disabled-border);
16267
-
16268
- --db-checkbox-mark: #ffffff;
16269
- --db-checkbox-mark-disabled: var(--db-selection-toggle-mark-disabled);
16270
- --db-checkbox-text: var(--db-selection-toggle-text);
16271
- --db-checkbox-text-disabled: var(--db-selection-toggle-text-disabled);
16272
-
16273
- --db-checkbox-shadow-checked: var(--db-selection-toggle-shadow-checked);
16308
+ --db-avatar-border-radius: var(--db-radius-sm);
16274
16309
 
16275
16310
  /* Density */
16276
- --db-checkbox-size: var(--db-selection-toggle-size);
16277
- --db-checkbox-label-spacing: var(--db-selection-toggle-label-spacing);
16278
- --db-checkbox-focus-ring-width: var(--db-focus-ring-width);
16279
- --db-checkbox-indeterminate-mark-width: var(--db-size-2); /* 8px */
16280
- --db-checkbox-indeterminate-mark-height: calc(var(--db-size-1) * 0.75); /* 3px */
16311
+ --db-avatar-group-gap: var(--db-space-1);
16312
+ --db-avatar-group-border-width: 1px;
16281
16313
 
16282
- /* Motion */
16283
- --db-checkbox-transition-duration: var(--db-selection-toggle-transition-duration);
16314
+ /* Color */
16315
+ /* No v2 token holds the secondary icon glyph yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
16316
+ --db-avatar-icon: var(--db-color-text-secondary);
16317
+ /* No v2 token holds the default avatar surface yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
16318
+ --db-avatar-surface: var(--db-color-tag-background-default);
16319
+ /* No v2 token holds the group divider border yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
16320
+ --db-avatar-group-border-color: var(--db-color-background-primary);
16284
16321
  }
16285
16322
 
16286
- /* Native-input <Checkbox> styles. Classic CSS only — no native nesting (the
16287
- * `&`-style nesting feature ships in Chrome/Edge 120, Safari 17.2, Firefox 117
16288
- * which leaves a non-trivial slice of our production traffic uncovered today;
16289
- * see PR #1954620 thread). `:has()` lookups are still used to read state from
16290
- * the native <input>; that feature has the support coverage we need.
16291
- *
16292
- * Structure (assumed by the React component):
16293
- *
16294
- * <label class="db-checkbox">
16295
- * <input type="checkbox" class="db-checkbox-input" />
16296
- * <span class="db-checkbox-indicator"> ← border + fill react to state
16297
- * <svg class="db-checkbox-checkmark" .../> ← visible when :checked
16298
- * <span class="db-checkbox-dash" /> ← visible when :indeterminate
16299
- * </span>
16300
- * <span class="db-checkbox-label">{children}</span>
16301
- * </label>
16302
- *
16303
- * State and hiding rules target `.db-checkbox-input` — the checkbox's OWN input —
16304
- * never a bare `input`: consumers may render an inline <input> (e.g. a number
16305
- * field) inside the label children, and a descendant `input` selector would hide
16306
- * / absolutely-position that field and mis-drive the `:has(…)` state rules. */
16307
-
16308
- .db-checkbox {
16309
- /* `display: flex` (block-level), NOT `inline-flex`: an inline-flex root puts
16310
- * the label inside an inline-line-box whose leading pushes block-level siblings
16311
- * (e.g. `<FormMessage>`) down by a few pixels — making validation messages sit
16312
- * lower under the native checkbox than under the AntD checkbox (which itself is
16313
- * wrapped in a block `<div>`, so its label's inline-line-box is consumed inside
16314
- * that div and never leaks into sibling spacing). `width: fit-content` keeps the
16315
- * label behaving like an inline-flex item inside a flex-row parent (the group,
16316
- * or any consumer's flex container). */
16317
- display: flex;
16318
- align-items: flex-start;
16319
- position: relative;
16320
- cursor: pointer;
16321
- user-select: none;
16322
- /* No `gap` here — spacing between indicator and label, plus the trailing
16323
- * whitespace that gives sibling checkboxes their visual separation, lives
16324
- * on `.db-checkbox-label` as padding-inline. Matches AntD's `.{prefix}-checkbox + span`
16325
- * rule and the prior Emotion-based NativeCheckbox implementation. */
16326
- min-height: var(--db-line-height-base, 20px);
16327
- width: fit-content;
16323
+ :where(:root), :where(.du-bois-light) {
16324
+ /* Color */
16325
+ /* No v2 token holds the initials/icon glyph on a colored avatar yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
16326
+ --db-avatar-text: #ffffff;
16328
16327
  }
16329
16328
 
16330
- /* Hover/press live on the root (label), not on the input: the input is
16331
- * visually hidden (opacity:0), so an `input:hover` rule wouldn't be visible
16332
- * anyway. Putting `:hover` on the root also makes the entire label area
16333
- * — not just the indicator square — drive the visual feedback. */
16334
- .db-checkbox:hover:has(.db-checkbox-input:not(:disabled, :checked, :indeterminate)) .db-checkbox-indicator {
16335
- background-color: var(--db-checkbox-surface-hover);
16336
- border-color: var(--db-checkbox-border-hover);
16337
- }
16338
16329
 
16339
- .db-checkbox:active:has(.db-checkbox-input:not(:disabled, :checked, :indeterminate)) .db-checkbox-indicator {
16340
- background-color: var(--db-checkbox-surface-press);
16341
- border-color: var(--db-checkbox-border-press);
16342
- }
16330
+ /* Component-tier tokens for the native <Button>, grouped by area (density, shape, typography, color).
16331
+ * Colors alias the action-* system tokens; sizes alias the system density scale.
16332
+ *
16333
+ * Follows the mortar/action.css pattern: one semantic set (surface / border / text, each with
16334
+ * -hover / -active / -disabled) whose defaults are the secondary variant. The variant classes that
16335
+ * re-set these tokens per variant live in NativeButton.css; this file declares only tokens. */
16343
16336
 
16344
- /* Selected fill checked and indeterminate share the same surface. */
16345
- .db-checkbox:has(.db-checkbox-input:is(:checked, :indeterminate)) .db-checkbox-indicator {
16346
- background-color: var(--db-checkbox-surface-checked);
16347
- border-color: var(--db-checkbox-surface-checked);
16348
- }
16337
+ /* Declared under the theme classes too (not just :root) so the var() chain re-resolves inside a
16338
+ * `.du-bois-dark` subtree (or a portal that re-applies the theme class) instead of freezing at the root values. */
16339
+ :where(:root), :where(.du-bois-light) {
16340
+ /* ── Density ──────────────────────────────────────────────────────────── */
16341
+ --db-btn-height: var(--db-size-8);
16342
+ --db-btn-height-small: var(--db-size-6);
16343
+ --db-btn-icon-size: var(--db-size-4);
16344
+ --db-btn-border-width: 1px;
16345
+ --db-btn-padding-vertical: var(--db-space-1);
16346
+ --db-btn-padding-horizontal: var(--db-space-3);
16347
+ --db-btn-padding-horizontal-small: var(--db-space-2);
16348
+ /* Icon/force-icon horizontal padding: half the standard horizontal padding. */
16349
+ --db-btn-padding-horizontal-icon: calc(var(--db-space-3) / 2); /* 6px */
16350
+ --db-btn-gap: var(--db-space-1);
16351
+ --db-btn-end-icon-gap: var(--db-space-2);
16352
+ --db-btn-end-icon-gap-small: var(--db-space-1);
16353
+ /* Width of a content-less button while loading, so the standalone spinner has room. */
16354
+ --db-btn-loading-empty-width: var(--db-size-10);
16349
16355
 
16350
- .db-checkbox:hover:has(.db-checkbox-input:is(:checked, :indeterminate):not(:disabled)) .db-checkbox-indicator {
16351
- background-color: var(--db-checkbox-surface-checked-hover);
16352
- border-color: var(--db-checkbox-surface-checked-hover);
16353
- }
16356
+ /* ── Shape ────────────────────────────────────────────────────────────── */
16357
+ --db-btn-border-radius: var(--db-shape-control);
16354
16358
 
16355
- .db-checkbox:active:has(.db-checkbox-input:is(:checked, :indeterminate):not(:disabled)) .db-checkbox-indicator {
16356
- background-color: var(--db-checkbox-surface-checked-press);
16357
- border-color: var(--db-checkbox-surface-checked-press);
16358
- }
16359
+ /* ── Typography ───────────────────────────────────────────────────────── */
16360
+ --db-btn-font-size: var(--db-font-size-base);
16361
+ --db-btn-icon-only-font-size: var(--db-font-size-lg); /* 15px */
16362
+ --db-btn-font-weight: var(--db-font-weight-normal);
16363
+ /* Line heights are hard-coded until the typography scale ships computed per-level line-height tokens. */
16364
+ --db-btn-line-height: 1.25rem; /* 20px */
16365
+ --db-btn-line-height-small: 1rem; /* 16px */
16359
16366
 
16360
- /* Mark visibility. */
16361
- .db-checkbox:has(.db-checkbox-input:checked) .db-checkbox-checkmark {
16362
- opacity: 1;
16363
- }
16367
+ /* ── Color: semantic set (defaults = secondary variant) ───────────────── */
16368
+ --db-btn-surface: var(--db-color-action-default-background-default);
16369
+ --db-btn-surface-hover: var(--db-color-action-default-background-hover);
16370
+ --db-btn-surface-active: var(--db-color-action-default-background-press);
16371
+ --db-btn-surface-disabled: transparent;
16372
+ --db-btn-border: var(--db-color-action-default-border-default);
16373
+ --db-btn-border-hover: var(--db-color-action-default-border-hover);
16374
+ --db-btn-border-active: var(--db-color-action-default-border-press);
16375
+ --db-btn-border-disabled: var(--db-color-action-disabled-border);
16376
+ --db-btn-text: var(--db-color-action-default-text-default);
16377
+ --db-btn-text-hover: var(--db-color-action-default-text-hover);
16378
+ --db-btn-text-active: var(--db-color-action-default-text-press);
16379
+ --db-btn-text-disabled: var(--db-color-action-disabled-text);
16364
16380
 
16365
- .db-checkbox:has(.db-checkbox-input:indeterminate) .db-checkbox-dash {
16366
- opacity: 1;
16381
+ /* ── Focus & icon ─────────────────────────────────────────────────────── */
16382
+ --db-btn-focus-ring: var(--db-color-action-default-border-focus);
16383
+ --db-btn-danger-focus-ring: var(--db-color-action-danger-primary-background-default);
16384
+ --db-btn-icon-glyph: var(--db-color-text-secondary);
16367
16385
  }
16368
16386
 
16369
- /* Focus-visible (keyboard only). */
16370
- .db-checkbox:has(.db-checkbox-input:focus-visible) .db-checkbox-indicator {
16371
- outline: var(--db-checkbox-focus-ring-width) solid var(--db-checkbox-focus-ring);
16372
- outline-offset: 4px;
16373
- }
16387
+ /* Native <Button> visual styles. Consumes the component-tier `--db-btn-*` tokens (see tokens.css);
16388
+ * the variant classes below re-set those tokens per variant and the rules that follow paint them.
16389
+ * Shipped via mortar.css. */
16374
16390
 
16375
- /* Disabled (covers unchecked, checked, indeterminate). Sourced from
16376
- * `input:disabled` so the standalone-disabled and group-disabled paths both
16377
- * land here without needing a separate root modifier class. */
16378
- .db-checkbox:has(.db-checkbox-input:disabled) {
16379
- cursor: not-allowed;
16391
+ /* ── 0. Variant token re-sets ──────────────────────────────────────────────
16392
+ * Each variant class re-sets the semantic tokens declared in tokens.css; combined classes win by
16393
+ * specificity, which beats the `:where(:root)` (specificity 0) defaults regardless of file order. */
16394
+ .db-btn-primary {
16395
+ --db-btn-surface: var(--db-color-action-primary-background-default);
16396
+ --db-btn-surface-hover: var(--db-color-action-primary-background-hover);
16397
+ --db-btn-surface-active: var(--db-color-action-primary-background-press);
16398
+ --db-btn-surface-disabled: var(--db-color-action-disabled-border);
16399
+ --db-btn-border: transparent;
16400
+ --db-btn-border-hover: transparent;
16401
+ --db-btn-border-active: transparent;
16402
+ --db-btn-border-disabled: transparent;
16403
+ --db-btn-text: var(--db-color-action-primary-text-default);
16404
+ --db-btn-text-hover: var(--db-color-action-primary-text-hover);
16405
+ --db-btn-text-active: var(--db-color-action-primary-text-press);
16406
+ --db-btn-text-disabled: var(--db-color-action-primary-text-default);
16380
16407
  }
16381
-
16382
- .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-label {
16383
- color: var(--db-checkbox-text-disabled);
16408
+ .db-btn-tertiary,
16409
+ .db-btn-link {
16410
+ --db-btn-surface: var(--db-color-action-tertiary-background-default);
16411
+ --db-btn-surface-disabled: var(--db-color-action-tertiary-background-default);
16412
+ --db-btn-border: var(--db-color-action-tertiary-background-default);
16413
+ --db-btn-border-hover: transparent;
16414
+ --db-btn-border-active: transparent;
16415
+ --db-btn-border-disabled: transparent;
16416
+ --db-btn-text: var(--db-color-action-tertiary-text-default);
16417
+ --db-btn-text-hover: var(--db-color-action-tertiary-text-hover);
16418
+ --db-btn-text-active: var(--db-color-action-tertiary-text-press);
16419
+ --db-btn-text-disabled: var(--db-color-action-disabled-text);
16384
16420
  }
16385
-
16386
- .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-indicator {
16387
- background-color: var(--db-checkbox-surface-disabled);
16388
- border-color: var(--db-checkbox-border-disabled);
16389
- box-shadow: none;
16421
+ .db-btn-tertiary {
16422
+ --db-btn-surface-hover: var(--db-color-action-tertiary-background-hover);
16423
+ --db-btn-surface-active: var(--db-color-action-tertiary-background-press);
16390
16424
  }
16391
-
16392
- .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-checkmark {
16393
- color: var(--db-checkbox-mark-disabled);
16425
+ /* Link is borderless and text-only, so its hover/press surface stays transparent. */
16426
+ .db-btn-link {
16427
+ --db-btn-surface-hover: transparent;
16428
+ --db-btn-surface-active: transparent;
16394
16429
  }
16395
-
16396
- .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-dash {
16397
- background-color: var(--db-checkbox-mark-disabled);
16430
+ /* Every danger button (icon included) disables to the filled treatment. */
16431
+ .db-btn-danger {
16432
+ --db-btn-surface-disabled: var(--db-color-action-disabled-border);
16433
+ --db-btn-border-disabled: transparent;
16434
+ --db-btn-text-disabled: var(--db-color-action-primary-text-default);
16398
16435
  }
16399
-
16400
- /* Visually-hidden native input (opacity:0) overlaying the indicator square,
16401
- * kept interactive and in the tab order so `:has()` lookups above can read its
16402
- * state. It stays pointer-clickable — matching AntD's `.ant-checkbox-input` —
16403
- * so it is the top-most hit-test target over the box: `getByRole('checkbox').click()`
16404
- * (Playwright, and any coordinate click that lands on the box) resolves to the
16405
- * real control instead of the decorative indicator overlay. Making the input
16406
- * `pointer-events: none` (the prior native-only behavior) is what regressed
16407
- * interaction tests vs. the AntD variant. `cursor: inherit` tracks the label's
16408
- * pointer / disabled `not-allowed` cursor so the box keeps AntD's cursor too. */
16409
- .db-checkbox .db-checkbox-input {
16410
- position: absolute;
16411
- inset: 0;
16412
- inline-size: var(--db-checkbox-size);
16413
- block-size: var(--db-checkbox-size);
16414
- margin: 0;
16415
- opacity: 0;
16416
- cursor: inherit;
16436
+ /* Danger, filled (primary): re-set only the surface; text/transparent border come from primary. */
16437
+ .db-btn-primary.db-btn-danger:not(.db-btn-icon-only) {
16438
+ --db-btn-surface: var(--db-color-action-danger-primary-background-default);
16439
+ --db-btn-surface-hover: var(--db-color-action-danger-primary-background-hover);
16440
+ --db-btn-surface-active: var(--db-color-action-danger-primary-background-press);
16417
16441
  }
16418
-
16419
- /* Indicator — square with the border/fill that responds to state above.
16420
- * Vertical math: aligns the indicator to the cap-height of the first text
16421
- * line so checkbox + label sit on the same visual baseline even when the
16422
- * label wraps. */
16423
- .db-checkbox .db-checkbox-indicator {
16424
- display: grid;
16425
- place-items: center;
16426
- flex-shrink: 0;
16427
- /* Decorative only — the overlaid input owns hit-testing. Without this, the
16428
- * indicator (and its inherited checkmark/dash children) intercept
16429
- * `getByRole('checkbox').click()`; clicks on any edge sliver the input does
16430
- * not cover fall through to the label, which toggles via label/input
16431
- * association. */
16432
- pointer-events: none;
16433
- inline-size: var(--db-checkbox-size);
16434
- block-size: var(--db-checkbox-size);
16435
- margin-top: calc((var(--db-line-height-base, 20px) - var(--db-checkbox-size)) / 2);
16436
- border: var(--db-checkbox-border-width) solid var(--db-checkbox-border);
16437
- border-radius: var(--db-checkbox-border-radius);
16438
- background-color: var(--db-checkbox-surface);
16442
+ /* Danger, secondary/outlined. */
16443
+ .db-btn-danger:not(.db-btn-primary):not(.db-btn-tertiary):not(.db-btn-link):not(.db-btn-icon-only) {
16444
+ --db-btn-surface: var(--db-color-action-danger-default-background-default);
16445
+ --db-btn-surface-hover: var(--db-color-action-danger-default-background-hover);
16446
+ --db-btn-surface-active: var(--db-color-action-danger-default-background-press);
16447
+ --db-btn-border: var(--db-color-action-danger-default-border-default);
16448
+ --db-btn-border-hover: var(--db-color-action-danger-default-border-hover);
16449
+ --db-btn-border-active: var(--db-color-action-danger-default-border-press);
16450
+ --db-btn-text: var(--db-color-action-danger-default-text-default);
16451
+ --db-btn-text-hover: var(--db-color-action-danger-default-text-hover);
16452
+ --db-btn-text-active: var(--db-color-action-danger-default-text-press);
16453
+ }
16454
+ /* Danger, tertiary/link: danger text everywhere, danger surface on hover/press; resting/disabled
16455
+ * surface stay the tertiary ghost (override the filled danger-disabled set above). */
16456
+ .db-btn-tertiary.db-btn-danger,
16457
+ .db-btn-link.db-btn-danger {
16458
+ --db-btn-surface-hover: var(--db-color-action-danger-default-background-hover);
16459
+ --db-btn-surface-active: var(--db-color-action-danger-default-background-press);
16460
+ --db-btn-surface-disabled: var(--db-color-action-tertiary-background-default);
16461
+ --db-btn-text: var(--db-color-action-danger-default-text-default);
16462
+ --db-btn-text-hover: var(--db-color-action-danger-default-text-hover);
16463
+ --db-btn-text-active: var(--db-color-action-danger-default-text-press);
16464
+ --db-btn-text-disabled: var(--db-color-action-disabled-text);
16465
+ }
16466
+ /* Icon-only renders as the ghost (tertiary) treatment regardless of variant; re-set the semantic set
16467
+ * so the icon-only rules paint from these component tokens. */
16468
+ .db-btn-icon-only {
16469
+ --db-btn-surface: var(--db-color-action-tertiary-background-default);
16470
+ --db-btn-surface-hover: var(--db-color-action-tertiary-background-hover);
16471
+ --db-btn-surface-active: var(--db-color-action-tertiary-background-press);
16472
+ --db-btn-text: var(--db-color-action-tertiary-text-default);
16473
+ --db-btn-text-hover: var(--db-color-action-tertiary-text-hover);
16474
+ --db-btn-text-active: var(--db-color-action-tertiary-text-press);
16475
+ }
16476
+ /* Danger icon-only keeps the ghost resting surface (inherited above) but uses danger text and a
16477
+ * danger hover/press surface. */
16478
+ .db-btn-icon-only.db-btn-danger {
16479
+ --db-btn-surface-hover: var(--db-color-action-danger-default-background-hover);
16480
+ --db-btn-surface-active: var(--db-color-action-danger-default-background-press);
16481
+ --db-btn-text: var(--db-color-action-danger-default-text-default);
16482
+ --db-btn-text-hover: var(--db-color-action-danger-default-text-hover);
16483
+ --db-btn-text-active: var(--db-color-action-danger-default-text-press);
16484
+ }
16485
+
16486
+ /* ── 1. Base — paints the default (secondary) variant ────────────────────── */
16487
+ .db-btn {
16488
+ appearance: none;
16439
16489
  box-sizing: border-box;
16440
- transition:
16441
- background-color var(--db-checkbox-transition-duration) ease,
16442
- border-color var(--db-checkbox-transition-duration) ease,
16443
- box-shadow var(--db-checkbox-transition-duration) ease;
16490
+ position: relative; /* anchors the loading spinner */
16491
+ display: inline-flex !important;
16492
+ align-items: center !important;
16493
+ justify-content: center !important;
16494
+ vertical-align: middle;
16495
+ white-space: nowrap;
16496
+ user-select: none;
16497
+ cursor: pointer;
16498
+ height: var(--db-btn-height) !important;
16499
+ font-family: inherit;
16500
+ font-size: var(--db-btn-font-size);
16501
+ font-weight: var(--db-btn-font-weight);
16502
+ line-height: var(--db-btn-line-height) !important;
16503
+ text-decoration: none !important;
16504
+ border: var(--db-btn-border-width) solid var(--db-btn-border);
16505
+ border-color: var(--db-btn-border) !important;
16506
+ border-radius: var(--db-btn-border-radius) !important;
16507
+ box-shadow: none !important;
16508
+ background-color: var(--db-btn-surface) !important;
16509
+ color: var(--db-btn-text) !important;
16444
16510
  }
16445
16511
 
16446
- .db-checkbox .db-checkbox-indicator > .db-checkbox-checkmark {
16447
- grid-area: 1 / 1;
16448
- inline-size: 100%;
16449
- block-size: 100%;
16450
- color: var(--db-checkbox-mark);
16451
- opacity: 0;
16452
- transition: opacity var(--db-checkbox-transition-duration) ease;
16512
+ /* ── 2. Padding ──────────────────────────────────────────────────────────── */
16513
+ .db-btn.db-btn:where(:not(.db-btn-icon-only, .db-btn-force-icon, .db-btn-link)) {
16514
+ padding: var(--db-btn-padding-vertical) var(--db-btn-padding-horizontal) !important;
16515
+ }
16516
+ .db-btn.db-btn-small:where(:not(.db-btn-icon-only, .db-btn-force-icon, .db-btn-link)) {
16517
+ padding: 0 var(--db-btn-padding-horizontal-small) !important;
16518
+ }
16519
+ .db-btn.db-btn-link {
16520
+ padding: 0 !important;
16521
+ }
16522
+ .db-btn.db-btn-force-icon {
16523
+ padding: 0 var(--db-btn-padding-horizontal-icon) !important;
16453
16524
  }
16454
16525
 
16455
- .db-checkbox .db-checkbox-indicator > .db-checkbox-checkmark path {
16456
- fill: none;
16457
- stroke: currentColor;
16458
- stroke-width: 2;
16459
- stroke-linecap: round;
16460
- stroke-linejoin: round;
16526
+ /* ── 3. Interactive states ───────────────────────────────────────────────── */
16527
+ .db-btn:hover:not(:disabled, .db-btn-loading) {
16528
+ background-color: var(--db-btn-surface-hover) !important;
16529
+ border-color: var(--db-btn-border-hover) !important;
16530
+ color: var(--db-btn-text-hover) !important;
16531
+ }
16532
+ .db-btn:active:not(:disabled, .db-btn-loading) {
16533
+ background-color: var(--db-btn-surface-active) !important;
16534
+ border-color: var(--db-btn-border-active) !important;
16535
+ color: var(--db-btn-text-active) !important;
16461
16536
  }
16462
16537
 
16463
- .db-checkbox .db-checkbox-indicator > .db-checkbox-dash {
16464
- grid-area: 1 / 1;
16465
- inline-size: var(--db-checkbox-indeterminate-mark-width);
16466
- block-size: var(--db-checkbox-indeterminate-mark-height);
16467
- /* CSS clamps this to half the 3px height, giving fully rounded end caps so the
16468
- * dash reads as a pill rather than a flat bar. */
16469
- border-radius: 4px;
16470
- background-color: var(--db-checkbox-mark);
16471
- opacity: 0;
16472
- transition: opacity var(--db-checkbox-transition-duration) ease;
16538
+ /* ── 4. Focus ────────────────────────────────────────────────────────────── */
16539
+ .db-btn:focus-visible {
16540
+ outline: 2px solid var(--db-btn-focus-ring);
16541
+ outline-offset: -2px;
16542
+ }
16543
+ .db-btn-primary:focus-visible {
16544
+ outline-offset: 1px;
16545
+ }
16546
+ .db-btn-use-focus-pseudo-class:focus {
16547
+ outline: 2px solid var(--db-btn-focus-ring);
16548
+ outline-offset: 1px;
16473
16549
  }
16474
16550
 
16475
- /* Padding-inline replicates AntD's `.{prefix}-checkbox + span { padding: 0 8px }`.
16476
- * `padding-left` provides the visible gap between indicator and label text.
16477
- * `padding-right` extends the checkbox's bounding box so adjacent checkboxes in a
16478
- * flex row sit ~16px apart (8px trailing + 8px group `gap`) — same visual rhythm
16479
- * as the AntD variant. */
16480
- .db-checkbox .db-checkbox-label {
16481
- color: var(--db-checkbox-text);
16482
- padding-inline: var(--db-checkbox-label-spacing);
16483
- word-break: break-word;
16551
+ /* ── 5. Small / block (full-width) / empty ────────────────────────────────── */
16552
+ .db-btn-small {
16553
+ height: var(--db-btn-height-small) !important;
16554
+ }
16555
+ .db-btn-block {
16556
+ width: 100%;
16557
+ }
16558
+ .db-btn-empty {
16559
+ background-color: transparent !important;
16560
+ border-color: transparent !important;
16561
+ }
16562
+ .db-btn-default.db-btn-empty {
16563
+ color: var(--db-btn-icon-glyph) !important;
16564
+ }
16565
+ .db-btn-empty.db-btn-loading {
16566
+ width: var(--db-btn-loading-empty-width);
16484
16567
  }
16485
16568
 
16486
- /* Form-flow integration: when a checkbox or group sits next to a `<Hint>` or
16487
- * `<FormMessage>` in a form layout, collapse the inherited sibling margin and
16488
- * align the helper text under the checkbox label. Mirrors the AntD-era rules
16489
- * carried in `getWrapperStyle` and `getNativeCheckboxWrapperStyles`.
16490
- *
16491
- * Each helper-text class is listed once per theme (`du-bois-light-*` /
16492
- * `du-bois-dark-*`). The selectors deliberately repeat the root/group class
16493
- * (`.db-checkbox.db-checkbox`) to push specificity to (0,3,0). That
16494
- * overrides `FormMessage`'s own `&&`-doubled `margin-top: 8px` declared with
16495
- * Emotion at runtime — without doubling we'd lose on the specificity tie and
16496
- * the validation message would keep an 8px gap below the checkbox. */
16569
+ /* ── 6. Tertiary & link ──────────────────────────────────────────────────── */
16570
+ .db-btn-link {
16571
+ height: auto !important;
16572
+ border: none !important;
16573
+ }
16574
+ /* Re-declare the text color for a non-icon-only tertiary/link at two-class weight so a plain
16575
+ * call-site `color` override loses; an icon-only glyph stays at base weight so its override wins. */
16576
+ .db-btn-tertiary:not(.db-btn-icon-only),
16577
+ .db-btn-link:not(.db-btn-icon-only) {
16578
+ color: var(--db-btn-text) !important;
16579
+ }
16580
+ /* Keep the resting border at two-class weight (parity with the shielded border-color). */
16581
+ .db-btn.db-btn-tertiary,
16582
+ .db-btn.db-btn-link {
16583
+ border-color: var(--db-btn-border) !important;
16584
+ }
16497
16585
 
16498
- /* Single checkbox followed by Hint and/or FormMessage. Indents the helper text
16499
- * by 24px so it aligns with the start of the label, and removes the default
16500
- * vertical margin Hint/FormMessage carry. */
16501
- .db-checkbox.db-checkbox + .du-bois-light-hint,
16502
- .db-checkbox.db-checkbox + .du-bois-dark-hint,
16503
- .db-checkbox.db-checkbox + .du-bois-light-form-message,
16504
- .db-checkbox.db-checkbox + .du-bois-dark-form-message,
16505
- .db-checkbox.db-checkbox + .du-bois-light-hint + .du-bois-light-form-message,
16506
- .db-checkbox.db-checkbox + .du-bois-dark-hint + .du-bois-dark-form-message {
16507
- padding-left: var(--db-space-6);
16508
- margin-top: 0;
16586
+ /* ── 7. Danger ───────────────────────────────────────────────────────────── */
16587
+ .db-btn-danger:focus-visible {
16588
+ outline-color: var(--db-btn-danger-focus-ring);
16509
16589
  }
16510
16590
 
16511
- /* Group spacing against sibling form text — 8px gap above the group when it
16512
- * follows a Hint, 8px gap above a FormMessage when it follows the group.
16513
- * Doubled class on the group side beats FormMessage's `&&` for the same reason. */
16514
- .du-bois-light-hint + .db-checkbox-group.db-checkbox-group,
16515
- .du-bois-dark-hint + .db-checkbox-group.db-checkbox-group,
16516
- .db-checkbox-group.db-checkbox-group + .du-bois-light-form-message,
16517
- .db-checkbox-group.db-checkbox-group + .du-bois-dark-form-message {
16518
- margin-top: var(--db-space-2);
16591
+ /* ── 8. Icon-only ────────────────────────────────────────────────────────── */
16592
+ .db-btn-icon-only {
16593
+ padding: 0;
16594
+ border: none !important;
16595
+ background-color: var(--db-btn-surface) !important;
16596
+ font-size: var(--db-btn-icon-only-font-size);
16597
+ }
16598
+ /* `:where()` keeps this at (0,1,0) so a call-site `css` width override wins, as it does for `height`. */
16599
+ .db-btn-icon-only:where(:not(.db-btn-small)) {
16600
+ width: var(--db-btn-height) !important;
16601
+ }
16602
+ .db-btn-icon-only:not(.db-btn-link, .db-btn-tertiary, [href], :hover, :active, :disabled) {
16603
+ color: var(--db-btn-icon-glyph) !important;
16604
+ }
16605
+ /* `:where()` on the exclusion holds this at (0,2,0): enough to tint an un-overridden icon-only link
16606
+ * over the base color, while still letting a consumer's own icon-color override win on source order. */
16607
+ .db-btn-icon-only[href]:where(:not(.db-btn-tertiary, .db-btn-link, .db-btn-danger, :disabled)) {
16608
+ color: var(--db-btn-text) !important;
16609
+ }
16610
+ .db-btn-icon-only:where(.db-btn-small) {
16611
+ width: var(--db-btn-height-small);
16612
+ }
16613
+ .db-btn-icon-only.db-btn-small {
16614
+ line-height: var(--db-btn-line-height-small) !important;
16615
+ }
16616
+ .db-btn-icon-only:hover:not(:disabled, .db-btn-loading) {
16617
+ background-color: var(--db-btn-surface-hover) !important;
16618
+ color: var(--db-btn-text-hover) !important;
16619
+ }
16620
+ .db-btn-icon-only:active:not(:disabled, .db-btn-loading) {
16621
+ background-color: var(--db-btn-surface-active) !important;
16622
+ color: var(--db-btn-text-active) !important;
16623
+ }
16624
+ .db-btn-icon-only:enabled:not(.db-btn-primary, .db-btn-tertiary, .db-btn-link, .db-btn-danger, :hover, :active)
16625
+ > .anticon, .db-btn-icon-only:enabled:not(.db-btn-primary, .db-btn-tertiary, .db-btn-link, .db-btn-danger, :hover, :active)
16626
+ > .db-icon {
16627
+ color: var(--db-btn-icon-glyph);
16628
+ }
16629
+ .db-btn-icon-only.db-btn-danger:not(:hover, :active) > .anticon, .db-btn-icon-only.db-btn-danger:not(:hover, :active) > .db-icon {
16630
+ color: var(--db-btn-text);
16631
+ }
16632
+ .db-btn-icon-only.db-btn-danger:hover:not(:disabled, .db-btn-loading) {
16633
+ background-color: var(--db-btn-surface-hover) !important;
16634
+ color: var(--db-btn-text-hover) !important;
16635
+ }
16636
+ .db-btn-icon-only.db-btn-danger:active:not(:disabled, .db-btn-loading) {
16637
+ background-color: var(--db-btn-surface-active) !important;
16638
+ color: var(--db-btn-text-active) !important;
16519
16639
  }
16520
16640
 
16521
- /* CheckboxGroup layout. Vertical delegates to the `.db-stack` primitive; the
16522
- * horizontal default stays bespoke because it must NOT wrap (a long row of
16523
- * checkboxes overflows horizontally rather than reflowing onto multiple lines),
16524
- * which is the one thing `.db-cluster` won't do. Both wire their gap to the
16525
- * checkbox spacing token so a retheme moves with the rest of the component. */
16526
- .db-checkbox-group {
16527
- display: flex;
16641
+ /* ── 9. Disabled base rules paint the per-variant `*-disabled` tokens (see tokens.css) ──────── */
16642
+ .db-btn:disabled {
16643
+ cursor: not-allowed;
16644
+ background-color: var(--db-btn-surface-disabled) !important;
16645
+ color: var(--db-btn-text-disabled) !important;
16646
+ }
16647
+ /* Empty disabled buttons keep a transparent border (set elsewhere), so exclude them.
16648
+ * `:where()` on the exclusion holds this at (0,2,0): enough to beat the base border, while still
16649
+ * letting a consumer's own `[disabled]` border override win on source order. */
16650
+ .db-btn:disabled:where(:not(.db-btn-empty)) {
16651
+ border-color: var(--db-btn-border-disabled) !important;
16652
+ }
16653
+ .db-btn-icon-only:disabled {
16654
+ border: none;
16655
+ }
16656
+ .db-btn-icon-only:disabled:hover {
16657
+ background-color: transparent !important;
16658
+ }
16659
+ .db-btn:disabled > .anticon,
16660
+ .db-btn:disabled > .db-icon {
16661
+ color: inherit !important;
16662
+ }
16663
+
16664
+ /* ── 10. Icon & content layout ───────────────────────────────────────────── */
16665
+ /* The leading icon renders unwrapped as the button's direct `.anticon` child (see NativeButton.tsx),
16666
+ * so `> .anticon` targets only a real icon glyph — a non-glyph `icon` (e.g. a Spinner) keeps its own
16667
+ * sizing — and out-specifies AntD's runtime-injected global `.anticon` rule. */
16668
+ .db-btn > .anticon,
16669
+ .db-btn > .db-icon,
16670
+ .db-btn-end-icon,
16671
+ .db-btn-end-icon > .anticon,
16672
+ .db-btn-end-icon > .db-icon {
16673
+ display: inline-flex;
16674
+ align-items: center;
16675
+ font-size: var(--db-btn-icon-size) !important;
16676
+ line-height: 0;
16677
+ }
16678
+ .db-btn-small > .anticon .anticon,
16679
+ .db-btn-small > .anticon .db-icon,
16680
+ .db-btn-small > .db-icon .anticon,
16681
+ .db-btn-small > .db-icon .db-icon {
16682
+ line-height: 0;
16683
+ }
16684
+ .db-btn > .db-btn-content > .anticon,
16685
+ .db-btn > .db-btn-content > .db-icon {
16686
+ display: inline-flex;
16687
+ align-items: center;
16688
+ line-height: 0;
16689
+ }
16690
+ .db-btn-content {
16691
+ display: inline-flex;
16692
+ align-items: center;
16693
+ }
16694
+ .db-btn > .anticon + .db-btn-content,
16695
+ .db-btn > .db-icon + .db-btn-content {
16696
+ margin-inline-start: var(--db-btn-gap) !important;
16697
+ }
16698
+ .db-btn-end-icon {
16699
+ margin-inline-start: var(--db-btn-end-icon-gap) !important;
16700
+ }
16701
+ .db-btn-small .db-btn-end-icon {
16702
+ margin-inline-start: var(--db-btn-end-icon-gap-small) !important;
16703
+ }
16704
+ .db-btn-default:not(.db-btn-danger, [href], :hover, :active, :disabled) > .anticon, .db-btn-default:not(.db-btn-danger, [href], :hover, :active, :disabled) > .db-icon {
16705
+ color: var(--db-btn-icon-glyph);
16706
+ }
16707
+ .db-btn-default[href]:not(.db-btn-danger, :hover, :active, :disabled) .db-btn-end-icon {
16708
+ color: var(--db-btn-icon-glyph);
16709
+ }
16710
+ .db-btn-default:not([href], .db-btn-danger, :hover, :active, :disabled)
16711
+ > .db-btn-content
16712
+ > .db-btn-end-icon
16713
+ > .anticon, .db-btn-default:not([href], .db-btn-danger, :hover, :active, :disabled)
16714
+ > .db-btn-content
16715
+ > .db-btn-end-icon
16716
+ > .db-icon {
16717
+ color: var(--db-btn-icon-glyph);
16718
+ }
16719
+
16720
+ /* ── 11. Loading ─────────────────────────────────────────────────────────── */
16721
+ .db-btn-loading {
16722
+ cursor: default;
16723
+ }
16724
+ .db-btn-force-icon.db-btn-loading:disabled {
16725
+ border-color: transparent !important;
16726
+ }
16727
+ .db-btn-spinner {
16728
+ color: inherit !important;
16729
+ pointer-events: none;
16730
+ }
16731
+ .db-btn-loading-with-icon .db-btn-content {
16732
+ padding-inline-start: calc(var(--db-btn-icon-size) + var(--db-btn-gap));
16733
+ }
16734
+ .db-btn-loading .db-btn-spinner {
16735
+ position: absolute;
16736
+ left: 50%;
16737
+ top: 50%;
16738
+ transform: translate(-50%, -50%);
16528
16739
  }
16529
-
16530
- /* Horizontal (default): single non-wrapping row. `:not(.db-stack)` keeps this
16531
- * off the vertical element so it never fights the stack primitive's gap. */
16532
- .db-checkbox-group:not(.db-stack) {
16533
- flex-wrap: nowrap;
16534
- column-gap: var(--db-checkbox-label-spacing);
16535
- row-gap: 0;
16740
+ .db-btn-loading.db-btn-icon-only .db-btn-spinner {
16741
+ position: static;
16742
+ transform: none;
16536
16743
  }
16537
16744
 
16538
- /* Vertical: .db-stack supplies flex-direction + gap; we only retarget the knob. */
16539
- .db-checkbox-group.db-stack {
16540
- --db-stack-gap: var(--db-checkbox-label-spacing);
16745
+ /* ── 12. dangerouslySetForceIconStyles ───────────────────────────────────── */
16746
+ .db-btn-force-icon {
16747
+ width: var(--db-btn-height) !important;
16748
+ color: var(--db-btn-icon-glyph) !important;
16749
+ }
16750
+ .db-btn-force-icon.db-btn-small {
16751
+ width: var(--db-btn-height-small) !important;
16541
16752
  }
16542
16753
 
16754
+
16543
16755
  :where(:root), :where(.du-bois-light) {
16544
16756
  /* Shape */
16545
- --db-radio-border-radius: var(--db-radius-full);
16546
- --db-radio-border-width: 1px;
16757
+ --db-checkbox-border-radius: var(--db-selection-toggle-border-radius);
16758
+ --db-checkbox-border-width: 1px;
16547
16759
 
16548
16760
  /* Color */
16549
- --db-radio-surface: var(--db-selection-toggle-surface);
16550
- --db-radio-surface-hover: var(--db-color-action-default-background-hover);
16551
- --db-radio-surface-press: var(--db-color-action-default-background-press);
16552
- --db-radio-surface-checked: var(--db-selection-toggle-surface-checked);
16553
- --db-radio-surface-checked-hover: var(--db-selection-toggle-surface-checked-hover);
16554
- --db-radio-surface-checked-press: var(--db-color-action-default-background-press);
16555
- --db-radio-surface-disabled: var(--db-selection-toggle-surface-disabled);
16761
+ --db-checkbox-surface: var(--db-selection-toggle-surface);
16762
+ --db-checkbox-surface-hover: var(--db-color-action-default-background-hover);
16763
+ --db-checkbox-surface-active: var(--db-color-action-default-background-press);
16764
+ --db-checkbox-surface-checked: var(--db-selection-toggle-surface-checked);
16765
+ --db-checkbox-surface-checked-hover: var(--db-selection-toggle-surface-checked-hover);
16766
+ --db-checkbox-surface-checked-active: var(--db-selection-toggle-surface-checked-active);
16767
+ --db-checkbox-surface-disabled: var(--db-selection-toggle-surface-disabled);
16556
16768
 
16557
- --db-radio-border: var(--db-color-action-default-border-default);
16558
- --db-radio-border-hover: var(--db-selection-toggle-border-hover);
16559
- --db-radio-border-press: var(--db-selection-toggle-border-press);
16560
- --db-radio-border-focus: var(--db-focus-ring-color);
16561
- --db-radio-border-disabled: var(--db-color-action-disabled-border);
16562
- --db-radio-border-checked-hover: var(--db-selection-toggle-border-press);
16563
- --db-radio-border-checked-press: var(--db-color-action-default-border-press);
16564
- --db-radio-border-unchecked-focus: var(--db-color-action-primary-background-default);
16565
- --db-radio-outline-checked-focus: var(--db-color-action-primary-background-default);
16769
+ --db-checkbox-border: var(--db-color-action-default-border-default);
16770
+ --db-checkbox-border-hover: var(--db-selection-toggle-border-hover);
16771
+ --db-checkbox-border-active: var(--db-selection-toggle-border-active);
16772
+ --db-checkbox-focus-ring: var(--db-focus-ring-color);
16773
+ --db-checkbox-border-disabled: var(--db-color-action-disabled-border);
16566
16774
 
16567
- --db-radio-mark: #ffffff;
16568
- --db-radio-mark-disabled: var(--db-selection-toggle-mark-disabled);
16569
- --db-radio-text: var(--db-selection-toggle-text);
16570
- --db-radio-text-disabled: var(--db-selection-toggle-text-disabled);
16775
+ --db-checkbox-mark: #ffffff;
16776
+ --db-checkbox-mark-disabled: var(--db-selection-toggle-mark-disabled);
16777
+ --db-checkbox-text: var(--db-selection-toggle-text);
16778
+ --db-checkbox-text-disabled: var(--db-selection-toggle-text-disabled);
16779
+
16780
+ --db-checkbox-shadow-checked: var(--db-selection-toggle-shadow-checked);
16571
16781
 
16572
16782
  /* Density */
16573
- --db-radio-size: var(--db-selection-toggle-size);
16574
- --db-radio-label-spacing: var(--db-selection-toggle-label-spacing);
16575
- --db-radio-mark-size: var(--db-size-1);
16783
+ --db-checkbox-size: var(--db-selection-toggle-size);
16784
+ --db-checkbox-label-spacing: var(--db-selection-toggle-label-spacing);
16785
+ --db-checkbox-focus-ring-width: var(--db-focus-ring-width);
16786
+ --db-checkbox-indeterminate-mark-width: var(--db-size-2); /* 8px */
16787
+ --db-checkbox-indeterminate-mark-height: calc(var(--db-size-1) * 0.75); /* 3px */
16576
16788
 
16577
16789
  /* Motion */
16578
- --db-radio-transition-duration: var(--db-selection-toggle-transition-duration);
16790
+ --db-checkbox-transition-duration: var(--db-selection-toggle-transition-duration);
16579
16791
  }
16580
16792
 
16581
- /* Native-input <Radio> styles. Classic CSS only — no native nesting (the
16793
+ /* Native-input <Checkbox> styles. Classic CSS only — no native nesting (the
16582
16794
  * `&`-style nesting feature ships in Chrome/Edge 120, Safari 17.2, Firefox 117
16583
16795
  * which leaves a non-trivial slice of our production traffic uncovered today;
16584
16796
  * see PR #1954620 thread). `:has()` lookups are still used to read state from
@@ -16586,981 +16798,1285 @@ span.du-bois-light-typography-ellipsis-single-line {
16586
16798
  *
16587
16799
  * Structure (assumed by the React component):
16588
16800
  *
16589
- * <label class="db-radio">
16590
- * <input type="radio" />
16591
- * <span class="db-radio-indicator"> circle border + fill react to state
16592
- * <span class="db-radio-dot" /> ← visible when :checked
16801
+ * <label class="db-checkbox">
16802
+ * <input type="checkbox" class="db-checkbox-input" />
16803
+ * <span class="db-checkbox-indicator"> ← border + fill react to state
16804
+ * <svg class="db-checkbox-checkmark" .../> ← visible when :checked
16805
+ * <span class="db-checkbox-dash" /> ← visible when :indeterminate
16593
16806
  * </span>
16594
- * <span class="db-radio-label">{children}</span>
16807
+ * <span class="db-checkbox-label">{children}</span>
16595
16808
  * </label>
16596
16809
  *
16597
- * The root only ever has one <input> descendant, so a bare `input` selector is
16598
- * safe and clearer than a class. Mirrors NativeCheckbox.css; the differences are
16599
- * the circular indicator (border-radius: full) and the filled dot instead of a
16600
- * checkmark/dash, and there is no indeterminate state.
16601
- *
16602
- * TODO(DUBOIS-452): the indicator/dot state styles below are identical to
16603
- * NativeCheckbox.css — consolidate them into a shared `.db-selection-toggle` class. */
16810
+ * State and hiding rules target `.db-checkbox-input` the checkbox's OWN input
16811
+ * never a bare `input`: consumers may render an inline <input> (e.g. a number
16812
+ * field) inside the label children, and a descendant `input` selector would hide
16813
+ * / absolutely-position that field and mis-drive the `:has(…)` state rules. */
16604
16814
 
16605
- .db-radio {
16606
- /* `display: flex` (block-level), NOT `inline-flex`: see NativeCheckbox.css for
16607
- * the line-box / sibling-spacing rationale. */
16815
+ .db-checkbox {
16816
+ /* `display: flex` (block-level), NOT `inline-flex`: an inline-flex root puts
16817
+ * the label inside an inline-line-box whose leading pushes block-level siblings
16818
+ * (e.g. `<FormMessage>`) down by a few pixels — making validation messages sit
16819
+ * lower under the native checkbox than under the AntD checkbox (which itself is
16820
+ * wrapped in a block `<div>`, so its label's inline-line-box is consumed inside
16821
+ * that div and never leaks into sibling spacing). `width: fit-content` keeps the
16822
+ * label behaving like an inline-flex item inside a flex-row parent (the group,
16823
+ * or any consumer's flex container). */
16608
16824
  display: flex;
16609
16825
  align-items: flex-start;
16610
16826
  position: relative;
16611
16827
  cursor: pointer;
16612
16828
  user-select: none;
16829
+ /* No `gap` here — spacing between indicator and label, plus the trailing
16830
+ * whitespace that gives sibling checkboxes their visual separation, lives
16831
+ * on `.db-checkbox-label` as padding-inline. Matches AntD's `.{prefix}-checkbox + span`
16832
+ * rule and the prior Emotion-based NativeCheckbox implementation. */
16613
16833
  min-height: var(--db-line-height-base, 20px);
16614
16834
  width: fit-content;
16615
16835
  }
16616
16836
 
16617
16837
  /* Hover/press live on the root (label), not on the input: the input is
16618
16838
  * visually hidden (opacity:0), so an `input:hover` rule wouldn't be visible
16619
- * anyway, and the whole label area should drive the feedback. */
16620
- .db-radio:hover:has(input:not(:disabled, :checked)) .db-radio-indicator {
16621
- background-color: var(--db-radio-surface-hover);
16622
- border-color: var(--db-radio-border-hover);
16839
+ * anyway. Putting `:hover` on the root also makes the entire label area
16840
+ * — not just the indicator square — drive the visual feedback. */
16841
+ .db-checkbox:hover:has(.db-checkbox-input:not(:disabled, :checked, :indeterminate)) .db-checkbox-indicator {
16842
+ background-color: var(--db-checkbox-surface-hover);
16843
+ border-color: var(--db-checkbox-border-hover);
16623
16844
  }
16624
16845
 
16625
- .db-radio:active:has(input:not(:disabled, :checked)) .db-radio-indicator {
16626
- background-color: var(--db-radio-surface-press);
16627
- border-color: var(--db-radio-border-press);
16846
+ .db-checkbox:active:has(.db-checkbox-input:not(:disabled, :checked, :indeterminate)) .db-checkbox-indicator {
16847
+ background-color: var(--db-checkbox-surface-active);
16848
+ border-color: var(--db-checkbox-border-active);
16628
16849
  }
16629
16850
 
16630
- /* Selected fill. */
16631
- .db-radio:has(input:checked) .db-radio-indicator {
16632
- background-color: var(--db-radio-surface-checked);
16633
- border-color: var(--db-radio-surface-checked);
16851
+ /* Selected fill — checked and indeterminate share the same surface. */
16852
+ .db-checkbox:has(.db-checkbox-input:is(:checked, :indeterminate)) .db-checkbox-indicator {
16853
+ background-color: var(--db-checkbox-surface-checked);
16854
+ border-color: var(--db-checkbox-surface-checked);
16634
16855
  }
16635
16856
 
16636
- /* AntD getRadioInputStyles checked hover: background = actionPrimaryBackgroundHover,
16637
- * borderColor = actionPrimaryBackgroundPress (a darker ring than the fill). */
16638
- .db-radio:hover:has(input:checked:not(:disabled)) .db-radio-indicator {
16639
- background-color: var(--db-radio-surface-checked-hover);
16640
- border-color: var(--db-radio-border-checked-hover);
16857
+ .db-checkbox:hover:has(.db-checkbox-input:is(:checked, :indeterminate):not(:disabled)) .db-checkbox-indicator {
16858
+ background-color: var(--db-checkbox-surface-checked-hover);
16859
+ border-color: var(--db-checkbox-surface-checked-hover);
16641
16860
  }
16642
16861
 
16643
- /* AntD getRadioInputStyles checked active: background = actionDefaultBackgroundPress,
16644
- * borderColor = actionDefaultBorderPress (two-tone, not flat). */
16645
- .db-radio:active:has(input:checked:not(:disabled)) .db-radio-indicator {
16646
- background-color: var(--db-radio-surface-checked-press);
16647
- border-color: var(--db-radio-border-checked-press);
16862
+ .db-checkbox:active:has(.db-checkbox-input:is(:checked, :indeterminate):not(:disabled)) .db-checkbox-indicator {
16863
+ background-color: var(--db-checkbox-surface-checked-active);
16864
+ border-color: var(--db-checkbox-surface-checked-active);
16648
16865
  }
16649
16866
 
16650
- /* Dot visibility. */
16651
- .db-radio:has(input:checked) .db-radio-dot {
16867
+ /* Mark visibility. */
16868
+ .db-checkbox:has(.db-checkbox-input:checked) .db-checkbox-checkmark {
16652
16869
  opacity: 1;
16653
16870
  }
16654
16871
 
16655
- /* Focus-visible (keyboard only). AntD getRadioInputStyles splits focus by checked
16656
- * state, so we do too.
16657
- *
16658
- * UNCHECKED focus (AntD `&:not(disabled) > input:focus + inner`): only the border
16659
- * color changes to actionPrimaryBackgroundDefault — NO outline ring. */
16660
- .db-radio:has(input:focus-visible:not(:checked)) .db-radio-indicator {
16661
- border-color: var(--db-radio-border-unchecked-focus);
16872
+ .db-checkbox:has(.db-checkbox-input:indeterminate) .db-checkbox-dash {
16873
+ opacity: 1;
16662
16874
  }
16663
16875
 
16664
- /* CHECKED focus-visible (AntD `&.checked &:not(disabled) > input:focus-visible + inner`):
16665
- * background = actionPrimaryBackgroundDefault, borderColor = actionDefaultBorderFocus,
16666
- * outline = 1px solid actionPrimaryBackgroundDefault, outlineOffset = 1. */
16667
- .db-radio:has(input:focus-visible:checked) .db-radio-indicator {
16668
- background-color: var(--db-radio-surface-checked);
16669
- border-color: var(--db-radio-border-focus);
16670
- outline: 1px solid var(--db-radio-outline-checked-focus);
16671
- outline-offset: 1px;
16876
+ /* Focus-visible (keyboard only). */
16877
+ .db-checkbox:has(.db-checkbox-input:focus-visible) .db-checkbox-indicator {
16878
+ outline: var(--db-checkbox-focus-ring-width) solid var(--db-checkbox-focus-ring);
16879
+ outline-offset: 4px;
16672
16880
  }
16673
16881
 
16674
- /* Disabled (covers checked and unchecked). Sourced from `input:disabled` so the
16675
- * standalone-disabled and group-disabled paths both land here. */
16676
- .db-radio:has(input:disabled) {
16882
+ /* Disabled (covers unchecked, checked, indeterminate). Sourced from
16883
+ * `input:disabled` so the standalone-disabled and group-disabled paths both
16884
+ * land here without needing a separate root modifier class. */
16885
+ .db-checkbox:has(.db-checkbox-input:disabled) {
16677
16886
  cursor: not-allowed;
16678
16887
  }
16679
16888
 
16680
- .db-radio:has(input:disabled) .db-radio-label {
16681
- color: var(--db-radio-text-disabled);
16889
+ .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-label {
16890
+ color: var(--db-checkbox-text-disabled);
16682
16891
  }
16683
16892
 
16684
- .db-radio:has(input:disabled) .db-radio-indicator {
16685
- background-color: var(--db-radio-surface-disabled);
16686
- border-color: var(--db-radio-border-disabled);
16893
+ .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-indicator {
16894
+ background-color: var(--db-checkbox-surface-disabled);
16895
+ border-color: var(--db-checkbox-border-disabled);
16687
16896
  box-shadow: none;
16688
16897
  }
16689
16898
 
16690
- .db-radio:has(input:disabled) .db-radio-dot {
16691
- background-color: var(--db-radio-mark-disabled);
16899
+ .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-checkmark {
16900
+ color: var(--db-checkbox-mark-disabled);
16692
16901
  }
16693
16902
 
16694
- /* Visually-hidden native input (opacity:0) overlaying the indicator circle,
16695
- * kept interactive and in the tab order so the `:has()` lookups above can read
16696
- * its state. It stays pointer-clickable — matching AntD's `.ant-radio-input` —
16697
- * so it is the top-most hit-test target over the circle: `getByRole('radio').click()`
16698
- * (Playwright, and any coordinate click that lands on the circle) resolves to the
16903
+ .db-checkbox:has(.db-checkbox-input:disabled) .db-checkbox-dash {
16904
+ background-color: var(--db-checkbox-mark-disabled);
16905
+ }
16906
+
16907
+ /* Visually-hidden native input (opacity:0) overlaying the indicator square,
16908
+ * kept interactive and in the tab order so `:has()` lookups above can read its
16909
+ * state. It stays pointer-clickable — matching AntD's `.ant-checkbox-input` —
16910
+ * so it is the top-most hit-test target over the box: `getByRole('checkbox').click()`
16911
+ * (Playwright, and any coordinate click that lands on the box) resolves to the
16699
16912
  * real control instead of the decorative indicator overlay. Making the input
16700
16913
  * `pointer-events: none` (the prior native-only behavior) is what regressed
16701
16914
  * interaction tests vs. the AntD variant. `cursor: inherit` tracks the label's
16702
- * pointer / disabled `not-allowed` cursor so the circle keeps AntD's cursor too. */
16703
- .db-radio input {
16915
+ * pointer / disabled `not-allowed` cursor so the box keeps AntD's cursor too. */
16916
+ .db-checkbox .db-checkbox-input {
16704
16917
  position: absolute;
16705
16918
  inset: 0;
16706
- inline-size: var(--db-radio-size);
16707
- block-size: var(--db-radio-size);
16919
+ inline-size: var(--db-checkbox-size);
16920
+ block-size: var(--db-checkbox-size);
16708
16921
  margin: 0;
16709
16922
  opacity: 0;
16710
16923
  cursor: inherit;
16711
16924
  }
16712
16925
 
16713
- /* Indicator — circle with the border/fill that responds to state above.
16714
- * Vertical math aligns the indicator to the cap-height of the first text line
16715
- * so radio + label sit on the same visual baseline even when the label wraps. */
16716
- .db-radio .db-radio-indicator {
16926
+ /* Indicator — square with the border/fill that responds to state above.
16927
+ * Vertical math: aligns the indicator to the cap-height of the first text
16928
+ * line so checkbox + label sit on the same visual baseline even when the
16929
+ * label wraps. */
16930
+ .db-checkbox .db-checkbox-indicator {
16717
16931
  display: grid;
16718
16932
  place-items: center;
16719
16933
  flex-shrink: 0;
16720
16934
  /* Decorative only — the overlaid input owns hit-testing. Without this, the
16721
- * indicator (and its inherited dot child) intercept `getByRole('radio').click()`;
16722
- * clicks on any edge sliver the input does not cover fall through to the label,
16723
- * which toggles via label/input association. */
16935
+ * indicator (and its inherited checkmark/dash children) intercept
16936
+ * `getByRole('checkbox').click()`; clicks on any edge sliver the input does
16937
+ * not cover fall through to the label, which toggles via label/input
16938
+ * association. */
16724
16939
  pointer-events: none;
16725
- inline-size: var(--db-radio-size);
16726
- block-size: var(--db-radio-size);
16727
- margin-top: calc((var(--db-line-height-base, 20px) - var(--db-radio-size)) / 2);
16728
- border: var(--db-radio-border-width) solid var(--db-radio-border);
16729
- border-radius: var(--db-radio-border-radius);
16730
- background-color: var(--db-radio-surface);
16940
+ inline-size: var(--db-checkbox-size);
16941
+ block-size: var(--db-checkbox-size);
16942
+ margin-top: calc((var(--db-line-height-base, 20px) - var(--db-checkbox-size)) / 2);
16943
+ border: var(--db-checkbox-border-width) solid var(--db-checkbox-border);
16944
+ border-radius: var(--db-checkbox-border-radius);
16945
+ background-color: var(--db-checkbox-surface);
16731
16946
  box-sizing: border-box;
16732
16947
  transition:
16733
- background-color var(--db-radio-transition-duration) ease,
16734
- border-color var(--db-radio-transition-duration) ease,
16735
- box-shadow var(--db-radio-transition-duration) ease;
16948
+ background-color var(--db-checkbox-transition-duration) ease,
16949
+ border-color var(--db-checkbox-transition-duration) ease,
16950
+ box-shadow var(--db-checkbox-transition-duration) ease;
16736
16951
  }
16737
16952
 
16738
- .db-radio .db-radio-indicator > .db-radio-dot {
16953
+ .db-checkbox .db-checkbox-indicator > .db-checkbox-checkmark {
16739
16954
  grid-area: 1 / 1;
16740
- inline-size: var(--db-radio-mark-size);
16741
- block-size: var(--db-radio-mark-size);
16742
- border-radius: var(--db-radius-full);
16743
- background-color: var(--db-radio-mark);
16744
- opacity: 0;
16745
- transition: opacity var(--db-radio-transition-duration) ease;
16746
- }
16747
-
16748
- /* Honour the user's reduced-motion preference: drop the state-change
16749
- * transitions on the indicator and dot so checking/hovering is instant. */
16750
- @media (prefers-reduced-motion: reduce) {
16751
- .db-radio .db-radio-indicator,
16752
- .db-radio .db-radio-indicator > .db-radio-dot {
16753
- transition: none;
16754
- }
16955
+ inline-size: 100%;
16956
+ block-size: 100%;
16957
+ color: var(--db-checkbox-mark);
16958
+ opacity: 0;
16959
+ transition: opacity var(--db-checkbox-transition-duration) ease;
16755
16960
  }
16756
16961
 
16757
- /* Padding-inline replicates the AntD `.{prefix} + span` label gap and the
16758
- * trailing whitespace that separates adjacent radios in a row. */
16759
- .db-radio .db-radio-label {
16760
- color: var(--db-radio-text);
16761
- padding-inline: var(--db-radio-label-spacing);
16762
- word-break: break-word;
16962
+ .db-checkbox .db-checkbox-indicator > .db-checkbox-checkmark path {
16963
+ fill: none;
16964
+ stroke: currentColor;
16965
+ stroke-width: 2;
16966
+ stroke-linecap: round;
16967
+ stroke-linejoin: round;
16763
16968
  }
16764
16969
 
16765
- /* Form-flow integration: align helper text under the radio label. The `paddingLeft`
16766
- * indent comes from AntD's getCommonRadioGroupStyles (`& > label + hint`) and applies
16767
- * to BOTH layouts. The doubled root class pushes specificity to (0,3,0) to beat
16768
- * FormMessage's runtime `&&` margin. */
16769
- .db-radio.db-radio + .du-bois-light-hint,
16770
- .db-radio.db-radio + .du-bois-dark-hint,
16771
- .db-radio.db-radio + .du-bois-light-form-message,
16772
- .db-radio.db-radio + .du-bois-dark-form-message,
16773
- .db-radio.db-radio + .du-bois-light-hint + .du-bois-light-form-message,
16774
- .db-radio.db-radio + .du-bois-dark-hint + .du-bois-dark-form-message {
16775
- padding-inline-start: var(--db-space-6);
16776
- margin-top: 0;
16970
+ .db-checkbox .db-checkbox-indicator > .db-checkbox-dash {
16971
+ grid-area: 1 / 1;
16972
+ inline-size: var(--db-checkbox-indeterminate-mark-width);
16973
+ block-size: var(--db-checkbox-indeterminate-mark-height);
16974
+ /* CSS clamps this to half the 3px height, giving fully rounded end caps so the
16975
+ * dash reads as a pill rather than a flat bar. */
16976
+ border-radius: 4px;
16977
+ background-color: var(--db-checkbox-mark);
16978
+ opacity: 0;
16979
+ transition: opacity var(--db-checkbox-transition-duration) ease;
16777
16980
  }
16778
16981
 
16779
- /* Vertical hint rhythm (AntD getVerticalRadioGroupStyles, vertical-only): a hint
16780
- * after a radio gets `marginTop: -sm` (cancels the preceding radio's
16781
- * `paddingBottom: sm` so the hint tucks directly under its label) and
16782
- * `marginBottom: sm` (gap before the next radio). The negative top margin relies on
16783
- * the .db-stack gap being 0 (set below). Scoped to .db-stack so it never perturbs
16784
- * the horizontal grid, where hints are positioned by grid-row instead. */
16785
- .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-light-hint,
16786
- .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-dark-hint,
16787
- .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-light-form-message,
16788
- .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-dark-form-message,
16789
- .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-light-hint + .du-bois-light-form-message,
16790
- .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-dark-hint + .du-bois-dark-form-message {
16791
- margin-top: calc(-1 * var(--db-space-2));
16792
- margin-bottom: var(--db-space-2);
16982
+ /* Padding-inline replicates AntD's `.{prefix}-checkbox + span { padding: 0 8px }`.
16983
+ * `padding-left` provides the visible gap between indicator and label text.
16984
+ * `padding-right` extends the checkbox's bounding box so adjacent checkboxes in a
16985
+ * flex row sit ~16px apart (8px trailing + 8px group `gap`) same visual rhythm
16986
+ * as the AntD variant. */
16987
+ .db-checkbox .db-checkbox-label {
16988
+ color: var(--db-checkbox-text);
16989
+ padding-inline: var(--db-checkbox-label-spacing);
16990
+ word-break: break-word;
16793
16991
  }
16794
16992
 
16795
- /* AntD `& > label:last-of-type + hint { marginTop: 0 }`: the last radio has no
16796
- * `paddingBottom` to cancel, so its trailing hint keeps its natural top position. */
16797
- .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-light-hint,
16798
- .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-dark-hint,
16799
- .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-light-form-message,
16800
- .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-dark-form-message,
16801
- .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-light-hint + .du-bois-light-form-message,
16802
- .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-dark-hint + .du-bois-dark-form-message {
16993
+ /* Form-flow integration: when a checkbox or group sits next to a `<Hint>` or
16994
+ * `<FormMessage>` in a form layout, collapse the inherited sibling margin and
16995
+ * align the helper text under the checkbox label. Mirrors the AntD-era rules
16996
+ * carried in `getWrapperStyle` and `getNativeCheckboxWrapperStyles`.
16997
+ *
16998
+ * Each helper-text class is listed once per theme (`du-bois-light-*` /
16999
+ * `du-bois-dark-*`). The selectors deliberately repeat the root/group class
17000
+ * (`.db-checkbox.db-checkbox`) to push specificity to (0,3,0). That
17001
+ * overrides `FormMessage`'s own `&&`-doubled `margin-top: 8px` declared with
17002
+ * Emotion at runtime — without doubling we'd lose on the specificity tie and
17003
+ * the validation message would keep an 8px gap below the checkbox. */
17004
+
17005
+ /* Single checkbox followed by Hint and/or FormMessage. Indents the helper text
17006
+ * by 24px so it aligns with the start of the label, and removes the default
17007
+ * vertical margin Hint/FormMessage carry. */
17008
+ .db-checkbox.db-checkbox + .du-bois-light-hint,
17009
+ .db-checkbox.db-checkbox + .du-bois-dark-hint,
17010
+ .db-checkbox.db-checkbox + .du-bois-light-form-message,
17011
+ .db-checkbox.db-checkbox + .du-bois-dark-form-message,
17012
+ .db-checkbox.db-checkbox + .du-bois-light-hint + .du-bois-light-form-message,
17013
+ .db-checkbox.db-checkbox + .du-bois-dark-hint + .du-bois-dark-form-message {
17014
+ padding-left: var(--db-space-6);
16803
17015
  margin-top: 0;
16804
17016
  }
16805
17017
 
16806
- .du-bois-light-hint + .db-radio-group.db-radio-group,
16807
- .du-bois-dark-hint + .db-radio-group.db-radio-group,
16808
- .db-radio-group.db-radio-group + .du-bois-light-form-message,
16809
- .db-radio-group.db-radio-group + .du-bois-dark-form-message {
17018
+ /* Group spacing against sibling form text — 8px gap above the group when it
17019
+ * follows a Label or Hint, 8px gap above a FormMessage when it follows the group.
17020
+ * Doubled class on the group side beats FormMessage's `&&` for the same reason. */
17021
+ .du-bois-light-label + .db-checkbox-group.db-checkbox-group,
17022
+ .du-bois-dark-label + .db-checkbox-group.db-checkbox-group,
17023
+ .du-bois-light-hint + .db-checkbox-group.db-checkbox-group,
17024
+ .du-bois-dark-hint + .db-checkbox-group.db-checkbox-group,
17025
+ .db-checkbox-group.db-checkbox-group + .du-bois-light-form-message,
17026
+ .db-checkbox-group.db-checkbox-group + .du-bois-dark-form-message {
16810
17027
  margin-top: var(--db-space-2);
16811
17028
  }
16812
17029
 
16813
- /* RadioGroup layout. Vertical (the default) delegates flex-direction to the
16814
- * .db-stack primitive; horizontal is a two-row grid (mirroring AntD). */
16815
- .db-radio-group {
17030
+ /* CheckboxGroup layout. Vertical delegates to the `.db-stack` primitive; the
17031
+ * horizontal default stays bespoke because it must NOT wrap (a long row of
17032
+ * checkboxes overflows horizontally rather than reflowing onto multiple lines),
17033
+ * which is the one thing `.db-cluster` won't do. Both wire their gap to the
17034
+ * checkbox spacing token so a retheme moves with the rest of the component. */
17035
+ .db-checkbox-group {
16816
17036
  display: flex;
16817
17037
  }
16818
17038
 
16819
- /* Vertical: .db-stack supplies flex-direction: column. AntD's vertical helper
16820
- * (getVerticalRadioGroupStyles) produces its item rhythm purely from
16821
- * `& > label { paddingBottom: sm }` plus the negative-margin hint trick above — it
16822
- * uses NO flex gap. The negative `marginTop` on hints only works if the stack adds
16823
- * no gap of its own, so we zero out the gap and reproduce the rhythm via
16824
- * padding-bottom on each radio. */
16825
- .db-radio-group.db-stack {
16826
- --db-stack-gap: 0;
17039
+ /* Horizontal (default): single non-wrapping row. `:not(.db-stack)` keeps this
17040
+ * off the vertical element so it never fights the stack primitive's gap. */
17041
+ .db-checkbox-group:not(.db-stack) {
17042
+ flex-wrap: nowrap;
17043
+ column-gap: var(--db-checkbox-label-spacing);
17044
+ row-gap: 0;
16827
17045
  }
16828
17046
 
16829
- /* AntD `& > label { paddingBottom: sm }`. */
16830
- .db-radio-group.db-stack > .db-radio {
16831
- padding-bottom: var(--db-space-2);
17047
+ /* Vertical: .db-stack supplies flex-direction + gap; we only retarget the knob. */
17048
+ .db-checkbox-group.db-stack {
17049
+ --db-stack-gap: var(--db-checkbox-label-spacing);
16832
17050
  }
16833
17051
 
16834
- /* AntD `& > label:last-of-type { paddingBottom: 0 }`. */
16835
- .db-radio-group.db-stack > .db-radio:last-of-type {
16836
- padding-bottom: 0;
16837
- }
16838
17052
 
16839
- /* AntD getVerticalRadioGroupStyles `.${prefix}-radio-tile + .${prefix}-radio-tile {
16840
- * marginTop: md }`: RadioTiles are not matched by the `.db-radio` padding-bottom
16841
- * rhythm above (the tile's own class is du-bois-{light,dark}-radio-tile), so space
16842
- * consecutive tiles explicitly. The .db-stack gap is 0, so this margin is the spacing. */
16843
- .db-radio-group.db-stack > .du-bois-light-radio-tile + .du-bois-light-radio-tile,
16844
- .db-radio-group.db-stack > .du-bois-light-radio-tile + .du-bois-dark-radio-tile,
16845
- .db-radio-group.db-stack > .du-bois-dark-radio-tile + .du-bois-light-radio-tile,
16846
- .db-radio-group.db-stack > .du-bois-dark-radio-tile + .du-bois-dark-radio-tile {
16847
- margin-top: var(--db-space-4);
16848
- }
16849
17053
 
16850
- /* Horizontal: AntD `getHorizontalRadioGroupStyles` is a TWO-ROW grid (NOT a flex
16851
- * row) so each radio sits in the [label] row and its following hint drops into the
16852
- * [hint] row of the same auto-column. `&&` in AntD doubles specificity; the doubled
16853
- * group class here matches that.
16854
- * AntD: `display: grid; gridTemplateRows: [label] auto [hint] auto;
16855
- * gridAutoColumns: max-content; gridColumnGap: md`. */
16856
- .db-radio-group--horizontal.db-radio-group--horizontal {
16857
- display: grid;
16858
- grid-template-rows: [label] auto [hint] auto;
16859
- grid-auto-columns: max-content;
16860
- column-gap: var(--db-space-4);
16861
- }
17054
+ :where(:root), :where(.du-bois-light) {
17055
+ /* Density */
17056
+ --db-drawer-padding-horizontal: var(--db-space-6);
17057
+ --db-drawer-padding-horizontal-small: var(--db-space-4);
17058
+ --db-drawer-content-padding-top: var(--db-space-4);
17059
+ --db-drawer-content-padding-top-small: var(--db-space-2);
17060
+ --db-drawer-footer-padding-top: var(--db-space-4);
17061
+ --db-drawer-footer-padding-bottom: var(--db-space-6);
17062
+ --db-drawer-footer-padding-bottom-small: var(--db-space-4);
17063
+ --db-drawer-title-margin-bottom: var(--db-space-2);
17064
+ --db-drawer-close-button-margin: var(--db-space-1);
16862
17065
 
16863
- /* Equal column widths: AntD `gridAutoColumns: minmax(0, 1fr)`. */
16864
- .db-radio-group--horizontal.db-radio-group--equal-width {
16865
- grid-auto-columns: minmax(0, 1fr);
17066
+ /* Color */
17067
+ --db-drawer-surface: var(--db-surface-base);
17068
+ --db-drawer-text: var(--db-text-base);
17069
+ --db-drawer-border: var(--db-border-base);
17070
+ /* No v2 token holds the drawer scrim yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17071
+ --db-drawer-overlay: var(--db-color-overlay-overlay);
16866
17072
  }
16867
17073
 
16868
- /* AntD `& > label, & > .${prefix}-radio-tile { gridRow: label / label; marginRight: 0 }`:
16869
- * every radio (and every RadioTile button, whose own class is
16870
- * du-bois-{light,dark}-radio-tile and is NOT matched by `> .db-radio`) lives in the
16871
- * label row. grid-auto-flow defaults to row, so successive items advance the implicit
16872
- * column. */
16873
- .db-radio-group--horizontal > .db-radio,
16874
- .db-radio-group--horizontal > .du-bois-light-radio-tile,
16875
- .db-radio-group--horizontal > .du-bois-dark-radio-tile {
16876
- grid-row: label / label;
16877
- margin-inline-end: 0;
17074
+ :where(:root), :where(.du-bois-light) {
17075
+ /* Elevation */
17076
+ --db-drawer-shadow: var(--db-elevation-high);
16878
17077
  }
16879
17078
 
16880
- /* AntD `& > label + hint { display: inline-block; gridRow: hint / hint }`: a hint
16881
- * following a radio drops into the hint row of the column its radio occupies. The
16882
- * vertical negative-margin rules are scoped to .db-stack, so nothing to reset here. */
16883
- .db-radio-group--horizontal > .du-bois-light-hint,
16884
- .db-radio-group--horizontal > .du-bois-dark-hint {
16885
- display: inline-block;
16886
- grid-row: hint / hint;
16887
- }
16888
17079
 
16889
- /* AntD `:has(> hint) { marginTop: sm }`: when the horizontal group contains a hint,
16890
- * the group itself gets a top margin (room above the two-row grid). */
16891
- .db-radio-group--horizontal:has(> .du-bois-light-hint),
16892
- .db-radio-group--horizontal:has(> .du-bois-dark-hint) {
16893
- margin-top: var(--db-space-2);
16894
- }
17080
+ :where(:root), :where(.du-bois-light) {
17081
+ /* Color */
17082
+ --db-empty-text: var(--db-text-subtle);
17083
+ --db-empty-image-color: var(--db-text-disabled);
16895
17084
 
16896
- /* AntD `&& + form-message { marginTop: sm }` (horizontal): a form-message following
16897
- * the whole group is separated by sm. */
16898
- .db-radio-group--horizontal + .du-bois-light-form-message,
16899
- .db-radio-group--horizontal + .du-bois-dark-form-message {
16900
- margin-top: var(--db-space-2);
17085
+ /* Density */
17086
+ --db-empty-image-size: calc(var(--db-size-8) * 2);
17087
+ --db-empty-image-spacing: var(--db-space-4);
17088
+ --db-empty-description-spacing: var(--db-space-4);
17089
+ --db-empty-max-width: 600px;
16901
17090
  }
16902
17091
 
16903
- :where(:root), :where(.du-bois-light) {
16904
- /* Shape */
16905
- --db-switch-border-radius: var(--db-radius-full);
16906
- --db-switch-border-width: 1px;
16907
-
16908
- /* Color */
16909
- --db-switch-surface: var(--db-selection-toggle-surface);
16910
- --db-switch-surface-hover: var(--db-color-action-default-background-hover);
16911
- --db-switch-surface-press: var(--db-color-action-default-background-press);
16912
- --db-switch-surface-checked: var(--db-selection-toggle-surface-checked);
16913
- --db-switch-surface-checked-hover: var(--db-selection-toggle-surface-checked-hover);
16914
- --db-switch-surface-checked-press: var(--db-selection-toggle-surface-checked-press);
16915
- --db-switch-surface-disabled: var(--db-selection-toggle-surface-disabled);
16916
- --db-switch-surface-checked-disabled: var(--db-color-action-disabled-text);
16917
17092
 
16918
- --db-switch-border: var(--db-color-action-default-border-default);
16919
- --db-switch-border-hover: var(--db-selection-toggle-border-hover);
16920
- --db-switch-border-press: var(--db-selection-toggle-border-press);
16921
- --db-switch-border-disabled: var(--db-color-action-disabled-border);
16922
- --db-switch-border-checked: var(--db-selection-toggle-surface-checked);
16923
- --db-switch-border-checked-hover: var(--db-selection-toggle-surface-checked-hover);
16924
- --db-switch-border-checked-press: var(--db-selection-toggle-surface-checked-press);
16925
- --db-switch-border-focus: var(--db-selection-toggle-surface-checked);
16926
17093
 
16927
- --db-switch-thumb: var(--db-selection-toggle-surface);
16928
17094
 
16929
- --db-switch-text: var(--db-selection-toggle-text);
16930
- --db-switch-text-disabled: var(--db-selection-toggle-text-disabled);
16931
17095
 
16932
- /* Density */
16933
- --db-switch-track-width: calc(var(--db-size-4) * 1.75); /* 28px */
16934
- --db-switch-track-height: var(--db-size-4); /* 16px */
16935
- --db-switch-thumb-size: var(--db-switch-track-height);
16936
- --db-switch-label-spacing: var(--db-selection-toggle-label-spacing);
16937
17096
 
16938
- /* Motion */
16939
- --db-switch-transition-duration: var(--db-selection-toggle-transition-duration);
16940
- }
16941
17097
 
16942
- .db-switch {
16943
- /* flex, not inline-flex: an inline-flex root's line-box leading pushes block siblings
16944
- * (e.g. a following FormMessage) down a few px, breaking the form-flow spacing. */
16945
- display: flex;
16946
- align-items: center;
16947
- cursor: pointer;
16948
- user-select: none;
16949
- width: fit-content;
16950
- margin-inline-end: var(--db-switch-label-spacing);
16951
- }
16952
17098
 
16953
- .db-switch:has(input:disabled) {
16954
- cursor: not-allowed;
17099
+ /* Component tokens for the native Icon. */
17100
+ :where(:root),
17101
+ :where(.du-bois-light) {
17102
+ --db-icon-size: var(--db-font-size-xl);
17103
+ --db-icon-color-danger: var(--db-color-text-validation-danger);
17104
+ --db-icon-color-warning: var(--db-color-text-validation-warning);
17105
+ --db-icon-color-success: var(--db-color-text-validation-success);
17106
+ --db-icon-ai-gradient-start: var(--db-ai-gradient-start);
17107
+ --db-icon-ai-gradient-mid: var(--db-ai-gradient-mid);
17108
+ --db-icon-ai-gradient-end: var(--db-ai-gradient-end);
16955
17109
  }
16956
17110
 
16957
- /* The track. `appearance: none` strips the native checkbox rendering so the input itself can be styled. */
16958
- .db-switch input {
16959
- appearance: none;
16960
- position: relative;
16961
- flex-shrink: 0;
16962
- margin: 0;
16963
- cursor: pointer;
16964
- inline-size: var(--db-switch-track-width);
16965
- block-size: var(--db-switch-track-height);
16966
- border: var(--db-switch-border-width) solid var(--db-switch-border);
16967
- border-radius: var(--db-switch-border-radius);
16968
- background-color: var(--db-switch-surface);
16969
- box-sizing: border-box;
16970
- transition:
16971
- background-color var(--db-switch-transition-duration) ease,
16972
- border-color var(--db-switch-transition-duration) ease;
17111
+ /* Native Icon styles. Ships through mortar.css and reads only component-tier --db-icon-* tokens.
17112
+ * Icon size is driven solely by the `--db-icon-size` design token: to resize an icon, set that
17113
+ * token in scope (default 16px in tokens.css) rather than overriding `font-size`. */
17114
+ .db-icon {
17115
+ display: inline-block;
17116
+ color: inherit;
17117
+ font-style: normal;
17118
+ font-size: var(--db-icon-size);
17119
+ line-height: 0;
17120
+ text-align: center;
17121
+ text-transform: none;
17122
+ vertical-align: -0.125em;
17123
+ text-rendering: optimizelegibility;
17124
+ -webkit-font-smoothing: antialiased;
17125
+ -moz-osx-font-smoothing: grayscale;
16973
17126
  }
16974
17127
 
16975
- /* The thumb. A pseudo-element keeps the input the switch's only element, so nothing can overlay it
16976
- * and intercept clicks. Offset left by the border width to sit flush with the track's outer edge. */
16977
- .db-switch input::before {
16978
- content: '';
16979
- position: absolute;
16980
- top: 50%;
16981
- left: calc(-1 * var(--db-switch-border-width));
16982
- inline-size: var(--db-switch-thumb-size);
16983
- block-size: var(--db-switch-thumb-size);
16984
- border: var(--db-switch-border-width) solid;
16985
- border-color: inherit;
16986
- border-radius: var(--db-switch-border-radius);
16987
- background-color: var(--db-switch-thumb);
16988
- box-sizing: border-box;
16989
- transform: translateY(-50%);
16990
- transition: transform var(--db-switch-transition-duration) ease;
17128
+ .db-icon svg {
17129
+ display: inline-block;
17130
+ line-height: 1;
16991
17131
  }
16992
17132
 
16993
- /* `<label for>` propagates `:hover` to the input, so scope hover/press to the control, not `input:hover`. */
16994
- .db-switch:hover input:not(:disabled):not(:checked) {
16995
- background-color: var(--db-switch-surface-hover);
16996
- border-color: var(--db-switch-border-hover);
17133
+ /* TODO: remove once clickable-icon callsites are gone. Kept for backwards compatibility with the
17134
+ * AntD icon, which made icons clickable via onClick; some callsites still rely on it. Actual icons
17135
+ * should never carry a tabindex — a clickable icon belongs inside a button. */
17136
+ .db-icon[tabindex] {
17137
+ cursor: pointer;
16997
17138
  }
16998
17139
 
16999
- .db-switch:active input:not(:disabled):not(:checked) {
17000
- background-color: var(--db-switch-surface-press);
17001
- border-color: var(--db-switch-border-press);
17140
+ /* Semantic colors set `color` only; the SVG glyph paints its `fill`/`stroke` from `currentColor`,
17141
+ * so setting the text color recolors the icon. */
17142
+ .db-icon-danger {
17143
+ color: var(--db-icon-color-danger);
17002
17144
  }
17003
17145
 
17004
- .db-switch input:checked {
17005
- background-color: var(--db-switch-surface-checked);
17006
- border-color: var(--db-switch-border-checked);
17146
+ .db-icon-warning {
17147
+ color: var(--db-icon-color-warning);
17007
17148
  }
17008
17149
 
17009
- .db-switch:hover input:checked:not(:disabled) {
17010
- background-color: var(--db-switch-surface-checked-hover);
17011
- border-color: var(--db-switch-border-checked-hover);
17150
+ .db-icon-success {
17151
+ color: var(--db-icon-color-success);
17012
17152
  }
17013
17153
 
17014
- .db-switch:active input:checked:not(:disabled) {
17015
- background-color: var(--db-switch-surface-checked-press);
17016
- border-color: var(--db-switch-border-checked-press);
17154
+ .db-icon-ai svg path,
17155
+ .db-icon-ai svg rect,
17156
+ .db-icon-ai svg circle,
17157
+ .db-icon-ai svg ellipse,
17158
+ .db-icon-ai svg line,
17159
+ .db-icon-ai svg polygon,
17160
+ .db-icon-ai svg polyline,
17161
+ .db-icon-ai svg text,
17162
+ .db-icon-ai svg tspan,
17163
+ .db-icon-ai svg textPath,
17164
+ .db-icon-ai svg use {
17165
+ fill: var(--db-icon-gradient);
17017
17166
  }
17018
17167
 
17019
- .db-switch input:checked::before {
17020
- transform: translateY(-50%) translateX(calc(var(--db-switch-track-width) - var(--db-switch-thumb-size)));
17168
+ .db-icon-ai-gradient-start {
17169
+ stop-color: var(--db-icon-ai-gradient-start);
17021
17170
  }
17022
17171
 
17023
- /* On focus, recolor the border to the checked-fill color on top of the outline ring. Matching AntD,
17024
- * the ring sits flush when off and gains a 1px offset when checked.
17025
- * TODO: width/offset are hardcoded 1px to preserve the AntD look; adopt the system 2px focus ring
17026
- * (var(--db-focus-ring-width)) in a separate change. */
17027
- .db-switch input:focus-visible {
17028
- border-color: var(--db-switch-border-focus);
17029
- outline: 1px solid var(--db-focus-ring-color);
17030
- outline-offset: 0;
17172
+ .db-icon-ai-gradient-mid {
17173
+ stop-color: var(--db-icon-ai-gradient-mid);
17031
17174
  }
17032
17175
 
17033
- .db-switch input:checked:focus-visible {
17034
- outline-offset: 1px;
17176
+ .db-icon-ai-gradient-end {
17177
+ stop-color: var(--db-icon-ai-gradient-end);
17035
17178
  }
17036
17179
 
17037
- .db-switch input:disabled {
17038
- cursor: not-allowed;
17039
- background-color: var(--db-switch-surface-disabled);
17040
- border-color: var(--db-switch-border-disabled);
17180
+ .db-icon-gradient-definitions {
17181
+ position: absolute;
17182
+ width: 1px;
17183
+ height: 1px;
17184
+ padding: 0;
17185
+ margin: -1px;
17186
+ overflow: hidden;
17187
+ clip: rect(0, 0, 0, 0);
17188
+ white-space: nowrap;
17189
+ border: 0;
17041
17190
  }
17042
17191
 
17043
- /* Disabled + on: more specific than the plain disabled rule, so it wins regardless of source order. */
17044
- .db-switch input:checked:disabled {
17045
- background-color: var(--db-switch-surface-checked-disabled);
17046
- border-color: var(--db-switch-surface-checked-disabled);
17192
+ .db-icon-spin {
17193
+ display: inline-block;
17194
+ animation: db-icon-loading-circle 1s infinite linear;
17195
+ }
17196
+
17197
+ @keyframes db-icon-loading-circle {
17198
+ 100% {
17199
+ transform: rotate(360deg);
17200
+ }
17047
17201
  }
17048
17202
 
17049
17203
  @media (prefers-reduced-motion: reduce) {
17050
- .db-switch input,
17051
- .db-switch input::before {
17052
- transition: none;
17204
+ .db-icon-spin {
17205
+ animation: none;
17053
17206
  }
17054
17207
  }
17055
17208
 
17056
- .db-switch-wrapper {
17057
- display: flex;
17058
- align-items: center;
17209
+
17210
+ :where(:root), :where(.du-bois-light) {
17211
+ /* Shape */
17212
+ --db-listbox-border-radius: var(--db-list-item-border-radius);
17213
+ --db-listbox-focus-ring-width: var(--db-focus-ring-width);
17214
+
17215
+ /* Color */
17216
+ --db-listbox-surface: var(--db-list-surface);
17217
+ --db-listbox-empty-text: var(--db-text-subtle);
17218
+ --db-listbox-focus-ring-color: var(--db-focus-ring-color);
17219
+
17220
+ /* Density */
17221
+ --db-listbox-spacing: var(--db-space-2);
17222
+ --db-listbox-empty-spacing-horizontal: var(--db-space-3);
17223
+ --db-listbox-empty-spacing-vertical: calc(var(--db-space-2) * 0.75);
17059
17224
  }
17060
17225
 
17061
- .db-switch-wrapper .db-switch-state-message {
17062
- margin-inline-start: auto;
17063
- margin-inline-end: var(--db-switch-label-spacing);
17226
+
17227
+ :where(:root), :where(.du-bois-light) {
17228
+ /* Shape */
17229
+ /* Must not scale with density; no non-scaling system token holds it. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17230
+ --db-navigation-menu-indicator-thickness: 4px;
17231
+ /* No system token holds this control-specific inset. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17232
+ --db-navigation-menu-focus-ring-offset: -1px;
17233
+
17234
+ /* Color */
17235
+ --db-navigation-menu-indicator: var(--db-accent-surface);
17236
+ --db-navigation-menu-focus-ring: var(--db-focus-ring-color);
17237
+ --db-navigation-menu-text: var(--db-text-subtle);
17238
+ --db-navigation-menu-text-selected: var(--db-text-base);
17239
+ /* No v2 semantic for default-action link text yet; bridges on the v1 --db-color-* token. */
17240
+ --db-navigation-menu-text-hover: var(--db-color-action-default-text-hover);
17241
+ --db-navigation-menu-text-active: var(--db-color-action-default-text-press);
17242
+
17243
+ /* Density */
17244
+ --db-navigation-menu-item-height: var(--db-size-8);
17245
+ --db-navigation-menu-item-min-width: var(--db-size-6);
17246
+ --db-navigation-menu-link-padding-top: var(--db-space-1);
17247
+ --db-navigation-menu-link-padding-bottom: var(--db-space-2);
17248
+ --db-navigation-menu-focus-ring-width: var(--db-focus-ring-width);
17064
17249
  }
17065
17250
 
17066
- /* `:first-child` scopes this to the label-first layout; the plain layout renders the label last. */
17067
- .db-switch-wrapper > .du-bois-light-label:first-child,
17068
- .db-switch-wrapper > .du-bois-dark-label:first-child {
17069
- margin-inline-end: var(--db-switch-label-spacing);
17251
+ :where(:root), :where(.du-bois-light) {
17252
+ /* Shape */
17253
+ --db-notification-border-radius: var(--db-popup-border-radius);
17254
+
17255
+ /* Density */
17256
+ --db-notification-padding: var(--db-space-3);
17257
+ --db-notification-column-gap: var(--db-space-1);
17258
+ --db-notification-description-gap: var(--db-space-1);
17259
+ --db-notification-close-offset: calc(var(--db-space-1) * 1.5);
17260
+ --db-notification-close-size: var(--db-size-5);
17261
+ --db-notification-viewport-padding: var(--db-space-3);
17262
+ --db-notification-viewport-gap: var(--db-space-3);
17263
+ --db-notification-viewport-margin: var(--db-space-6);
17264
+ --db-notification-viewport-width: 440px;
17265
+
17266
+ /* Color */
17267
+ --db-notification-surface: var(--db-popup-surface);
17268
+ --db-notification-border: var(--db-popup-border);
17269
+ --db-notification-text: var(--db-popup-text);
17270
+ --db-notification-icon: var(--db-text-subtle);
17271
+ --db-notification-icon-success: var(--db-positive-text);
17272
+ --db-notification-icon-warning: var(--db-caution-text);
17273
+ --db-notification-icon-error: var(--db-negative-text);
17070
17274
  }
17071
17275
 
17072
- .db-switch-state-message {
17073
- color: var(--db-switch-text);
17276
+ :where(:root), :where(.du-bois-light) {
17277
+ /* Elevation */
17278
+ --db-notification-shadow: var(--db-elevation-med);
17074
17279
  }
17075
17280
 
17076
- /* Target the <Label>'s class, not a bare `label` — that would also match the .db-switch root <label>. */
17077
- .db-switch-wrapper:has(input:disabled) .du-bois-light-label,
17078
- .db-switch-wrapper:has(input:disabled) .du-bois-dark-label {
17079
- color: var(--db-switch-text-disabled);
17281
+ :where(:root), :where(.du-bois-light) {
17282
+ /* Color */
17283
+ /* No v2 token holds tertiary action-text states yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17284
+ --db-overflow-trigger-text: var(--db-color-action-tertiary-text-default);
17285
+ --db-overflow-trigger-text-hover: var(--db-color-action-tertiary-text-hover);
17286
+ --db-overflow-trigger-text-press: var(--db-color-action-tertiary-text-press);
17287
+
17288
+ /* Density */
17289
+ --db-overflow-spacing: var(--db-space-2);
17290
+ --db-overflow-menu-spacing: var(--db-space-1);
17080
17291
  }
17081
17292
 
17082
- /* Indent a following hint/message past the track. The class is doubled to out-specify FormMessage's
17083
- * own margin rule. Each rule's key (rightmost) selector is a single class, not a multi-item :is(),
17084
- * so Chromium can hash-reject it instead of attempting the match against every element on every
17085
- * recalc (the leading/middle :is() groups aren't the key selector, so they're left as-is). */
17086
- :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-light-hint, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-dark-hint, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-dark-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
17087
- + :is(.du-bois-light-hint, .du-bois-dark-hint)
17088
- + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
17089
- + :is(.du-bois-light-hint, .du-bois-dark-hint)
17090
- + .du-bois-dark-form-message {
17091
- padding-inline-start: calc(var(--db-space-2) + var(--db-switch-track-width));
17293
+
17294
+
17295
+ /* Popover is a neutral-card member of the popup family: surface, text, border, and radius come
17296
+ * from --db-popup-*. Link, focus, and spacing are popover-specific and reference system tokens
17297
+ * directly. The popup elevation scale is light-only, so the dark shadow is re-declared below. */
17298
+
17299
+ :where(:root), :where(.du-bois-light) {
17300
+ /* Shape */
17301
+ --db-popover-border-radius: var(--db-popup-border-radius);
17302
+
17303
+ /* Color */
17304
+ --db-popover-surface: var(--db-popup-surface);
17305
+ --db-popover-text: var(--db-popup-text);
17306
+ --db-popover-border: var(--db-popup-border);
17307
+ /* No v2 token holds the tertiary link text yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17308
+ --db-popover-link-text: var(--db-color-action-tertiary-text-default);
17309
+ --db-popover-link-text-hover: var(--db-color-action-tertiary-text-hover);
17310
+ --db-popover-focus-ring: var(--db-focus-ring-color);
17311
+
17312
+ /* Density */
17313
+ --db-popover-spacing: var(--db-space-2);
17314
+ --db-popover-focus-ring-width: var(--db-focus-ring-width);
17315
+
17316
+ /* Elevation */
17317
+ --db-popover-shadow: var(--db-popup-elevation);
17092
17318
  }
17093
17319
 
17094
- :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-dark-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
17095
- + :is(.du-bois-light-hint, .du-bois-dark-hint)
17096
- + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
17097
- + :is(.du-bois-light-hint, .du-bois-dark-hint)
17098
- + .du-bois-dark-form-message {
17099
- margin-top: 0;
17320
+ :where(:root), :where(.du-bois-light) {
17321
+ /* Shape */
17322
+ --db-preview-card-border-radius: var(--db-card-border-radius);
17323
+ --db-preview-card-image-border-radius: var(--info-container-border-radius, 4px);
17324
+ --db-preview-card-border-width: 1px;
17325
+
17326
+ /* Density */
17327
+ --db-preview-card-padding: var(--db-space-4);
17328
+ --db-preview-card-padding-large: var(--db-space-6);
17329
+ --db-preview-card-gap: var(--db-space-2);
17330
+ --db-preview-card-gap-large: var(--db-space-4);
17331
+ --db-preview-card-action-spacing: var(--db-space-4);
17332
+
17333
+ /* Color */
17334
+ --db-preview-card-text: var(--db-text-subtle);
17335
+ --db-preview-card-title-text: var(--db-card-text);
17336
+ --db-preview-card-border: var(--db-card-border);
17337
+ /* No v2 token holds the active/focus border yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17338
+ --db-preview-card-border-hover: var(--db-color-action-default-border-hover);
17339
+ /* No v2 token holds the focus outline ring yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17340
+ --db-preview-card-border-focus: var(--db-color-action-default-border-focus);
17341
+ /* No v2 token holds the active surface tint yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17342
+ --db-preview-card-surface-active: var(--db-color-action-tertiary-background-press);
17343
+ --db-preview-card-surface-disabled: var(--db-surface-disabled);
17344
+ /* No v2 token holds the disabled border yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17345
+ --db-preview-card-border-disabled: var(--db-color-action-disabled-border);
17346
+ --db-preview-card-text-disabled: var(--db-text-disabled);
17347
+
17348
+ /* Typography */
17349
+ --db-preview-card-title-font-weight: var(--db-font-weight-semibold);
17100
17350
  }
17101
17351
 
17102
- /* Component-tier tokens for the native <Button>, grouped by area (density, shape, typography, color).
17103
- * Colors alias the action-* system tokens; sizes alias the system density scale.
17104
- *
17105
- * Follows the mortar/action.css pattern: one semantic set (surface / border / text, each with
17106
- * -hover / -press / -disabled) whose defaults are the secondary variant. The variant classes that
17107
- * re-set these tokens per variant live in NativeButton.css; this file declares only tokens. */
17352
+ :where(:root), :where(.du-bois-light) {
17353
+ /* Elevation */
17354
+ --db-preview-card-shadow: var(--db-elevation-low);
17355
+ /* No v2 elevation token holds the hover/focus shadow yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17356
+ --db-preview-card-shadow-hover: 0px 3px 6px 0px rgba(0, 0, 0, 0.05);
17357
+ }
17108
17358
 
17109
17359
  /* Declared under the theme classes too (not just :root) so the var() chain re-resolves inside a
17110
- * `.du-bois-dark` subtree (or a portal that re-applies the theme class) instead of freezing at the root values. */
17360
+ * `.du-bois-dark` subtree instead of freezing at the light values. */
17111
17361
  :where(:root), :where(.du-bois-light) {
17112
- /* ── Density ──────────────────────────────────────────────────────────── */
17113
- --db-btn-height: var(--db-size-8);
17114
- --db-btn-height-small: var(--db-size-6);
17115
- --db-btn-icon-size: var(--db-size-4);
17116
- --db-btn-border-width: 1px;
17117
- --db-btn-padding-vertical: var(--db-space-1);
17118
- --db-btn-padding-horizontal: var(--db-space-3);
17119
- --db-btn-padding-horizontal-small: var(--db-space-2);
17120
- /* Icon/force-icon horizontal padding: half the standard horizontal padding. */
17121
- --db-btn-padding-horizontal-icon: calc(var(--db-space-3) / 2); /* 6px */
17122
- --db-btn-gap: var(--db-space-1);
17123
- --db-btn-end-icon-gap: var(--db-space-2);
17124
- --db-btn-end-icon-gap-small: var(--db-space-1);
17125
- /* Width of a content-less button while loading, so the standalone spinner has room. */
17126
- --db-btn-loading-empty-width: var(--db-size-10);
17362
+ /* Shape */
17363
+ --db-progress-border-radius: var(--db-shape-pill);
17127
17364
 
17128
- /* ── Shape ────────────────────────────────────────────────────────────── */
17129
- --db-btn-border-radius: var(--db-shape-control);
17365
+ /* Color */
17366
+ --db-progress-track: var(--db-color-progress-track);
17367
+ --db-progress-fill: var(--db-color-progress-fill);
17130
17368
 
17131
- /* ── Typography ───────────────────────────────────────────────────────── */
17132
- --db-btn-font-size: var(--db-font-size-base);
17133
- --db-btn-icon-only-font-size: var(--db-font-size-lg); /* 15px */
17134
- --db-btn-font-weight: var(--db-font-weight-normal);
17135
- /* Line heights are hard-coded until the typography scale ships computed per-level line-height tokens. */
17136
- --db-btn-line-height: 1.25rem; /* 20px */
17137
- --db-btn-line-height-small: 1rem; /* 16px */
17369
+ /* Density */
17370
+ --db-progress-size: var(--db-size-2);
17138
17371
 
17139
- /* ── Color: semantic set (defaults = secondary variant) ───────────────── */
17140
- --db-btn-surface: var(--db-color-action-default-background-default);
17141
- --db-btn-surface-hover: var(--db-color-action-default-background-hover);
17142
- --db-btn-surface-press: var(--db-color-action-default-background-press);
17143
- --db-btn-surface-disabled: transparent;
17144
- --db-btn-border: var(--db-color-action-default-border-default);
17145
- --db-btn-border-hover: var(--db-color-action-default-border-hover);
17146
- --db-btn-border-press: var(--db-color-action-default-border-press);
17147
- --db-btn-border-disabled: var(--db-color-action-disabled-border);
17148
- --db-btn-text: var(--db-color-action-default-text-default);
17149
- --db-btn-text-hover: var(--db-color-action-default-text-hover);
17150
- --db-btn-text-press: var(--db-color-action-default-text-press);
17151
- --db-btn-text-disabled: var(--db-color-action-disabled-text);
17372
+ /* Motion */
17373
+ /* No v2 motion token holds this duration. TODO(FEINF-6505): adopt a v2 motion token when one is added. */
17374
+ --db-progress-transition-duration: 300ms;
17375
+ }
17376
+
17377
+ :where(:root), :where(.du-bois-light) {
17378
+ /* Shape */
17379
+ --db-radio-border-radius: var(--db-radius-full);
17380
+ --db-radio-border-width: 1px;
17381
+
17382
+ /* Color */
17383
+ --db-radio-surface: var(--db-selection-toggle-surface);
17384
+ --db-radio-surface-hover: var(--db-color-action-default-background-hover);
17385
+ --db-radio-surface-active: var(--db-color-action-default-background-press);
17386
+ --db-radio-surface-checked: var(--db-selection-toggle-surface-checked);
17387
+ --db-radio-surface-checked-hover: var(--db-selection-toggle-surface-checked-hover);
17388
+ --db-radio-surface-checked-active: var(--db-color-action-default-background-press);
17389
+ --db-radio-surface-disabled: var(--db-selection-toggle-surface-disabled);
17390
+
17391
+ --db-radio-border: var(--db-color-action-default-border-default);
17392
+ --db-radio-border-hover: var(--db-selection-toggle-border-hover);
17393
+ --db-radio-border-active: var(--db-selection-toggle-border-active);
17394
+ --db-radio-border-focus: var(--db-focus-ring-color);
17395
+ --db-radio-border-disabled: var(--db-color-action-disabled-border);
17396
+ --db-radio-border-checked-hover: var(--db-selection-toggle-border-active);
17397
+ --db-radio-border-checked-active: var(--db-color-action-default-border-press);
17398
+ --db-radio-border-unchecked-focus: var(--db-color-action-primary-background-default);
17399
+ --db-radio-outline-checked-focus: var(--db-color-action-primary-background-default);
17400
+
17401
+ --db-radio-mark: #ffffff;
17402
+ --db-radio-mark-disabled: var(--db-selection-toggle-mark-disabled);
17403
+ --db-radio-text: var(--db-selection-toggle-text);
17404
+ --db-radio-text-disabled: var(--db-selection-toggle-text-disabled);
17405
+
17406
+ /* Density */
17407
+ --db-radio-size: var(--db-selection-toggle-size);
17408
+ --db-radio-label-spacing: var(--db-selection-toggle-label-spacing);
17409
+ --db-radio-mark-size: var(--db-size-1);
17410
+
17411
+ /* Motion */
17412
+ --db-radio-transition-duration: var(--db-selection-toggle-transition-duration);
17413
+ }
17414
+
17415
+ /* Native-input <Radio> styles. Classic CSS only — no native nesting (the
17416
+ * `&`-style nesting feature ships in Chrome/Edge 120, Safari 17.2, Firefox 117
17417
+ * which leaves a non-trivial slice of our production traffic uncovered today;
17418
+ * see PR #1954620 thread). `:has()` lookups are still used to read state from
17419
+ * the native <input>; that feature has the support coverage we need.
17420
+ *
17421
+ * Structure (assumed by the React component):
17422
+ *
17423
+ * <label class="db-radio">
17424
+ * <input type="radio" />
17425
+ * <span class="db-radio-indicator"> ← circle border + fill react to state
17426
+ * <span class="db-radio-dot" /> ← visible when :checked
17427
+ * </span>
17428
+ * <span class="db-radio-label">{children}</span>
17429
+ * </label>
17430
+ *
17431
+ * The root only ever has one <input> descendant, so a bare `input` selector is
17432
+ * safe and clearer than a class. Mirrors NativeCheckbox.css; the differences are
17433
+ * the circular indicator (border-radius: full) and the filled dot instead of a
17434
+ * checkmark/dash, and there is no indeterminate state.
17435
+ *
17436
+ * TODO(DUBOIS-452): the indicator/dot state styles below are identical to
17437
+ * NativeCheckbox.css — consolidate them into a shared `.db-selection-toggle` class. */
17152
17438
 
17153
- /* ── Focus & icon ─────────────────────────────────────────────────────── */
17154
- --db-btn-focus-ring: var(--db-color-action-default-border-focus);
17155
- --db-btn-danger-focus-ring: var(--db-color-action-danger-primary-background-default);
17156
- --db-btn-icon-glyph: var(--db-color-text-secondary);
17439
+ .db-radio {
17440
+ /* `display: flex` (block-level), NOT `inline-flex`: see NativeCheckbox.css for
17441
+ * the line-box / sibling-spacing rationale. */
17442
+ display: flex;
17443
+ align-items: flex-start;
17444
+ position: relative;
17445
+ cursor: pointer;
17446
+ user-select: none;
17447
+ min-height: var(--db-line-height-base, 20px);
17448
+ width: fit-content;
17157
17449
  }
17158
17450
 
17159
- /* Native <Button> visual styles. Consumes the component-tier `--db-btn-*` tokens (see tokens.css);
17160
- * the variant classes below re-set those tokens per variant and the rules that follow paint them.
17161
- * Shipped via mortar.css. */
17162
-
17163
- /* ── 0. Variant token re-sets ──────────────────────────────────────────────
17164
- * Each variant class re-sets the semantic tokens declared in tokens.css; combined classes win by
17165
- * specificity, which beats the `:where(:root)` (specificity 0) defaults regardless of file order. */
17166
- .db-btn-primary {
17167
- --db-btn-surface: var(--db-color-action-primary-background-default);
17168
- --db-btn-surface-hover: var(--db-color-action-primary-background-hover);
17169
- --db-btn-surface-press: var(--db-color-action-primary-background-press);
17170
- --db-btn-surface-disabled: var(--db-color-action-disabled-border);
17171
- --db-btn-border: transparent;
17172
- --db-btn-border-hover: transparent;
17173
- --db-btn-border-press: transparent;
17174
- --db-btn-border-disabled: transparent;
17175
- --db-btn-text: var(--db-color-action-primary-text-default);
17176
- --db-btn-text-hover: var(--db-color-action-primary-text-hover);
17177
- --db-btn-text-press: var(--db-color-action-primary-text-press);
17178
- --db-btn-text-disabled: var(--db-color-action-primary-text-default);
17179
- }
17180
- .db-btn-tertiary,
17181
- .db-btn-link {
17182
- --db-btn-surface: var(--db-color-action-tertiary-background-default);
17183
- --db-btn-surface-disabled: var(--db-color-action-tertiary-background-default);
17184
- --db-btn-border: var(--db-color-action-tertiary-background-default);
17185
- --db-btn-border-hover: transparent;
17186
- --db-btn-border-press: transparent;
17187
- --db-btn-border-disabled: transparent;
17188
- --db-btn-text: var(--db-color-action-tertiary-text-default);
17189
- --db-btn-text-hover: var(--db-color-action-tertiary-text-hover);
17190
- --db-btn-text-press: var(--db-color-action-tertiary-text-press);
17191
- --db-btn-text-disabled: var(--db-color-action-disabled-text);
17192
- }
17193
- .db-btn-tertiary {
17194
- --db-btn-surface-hover: var(--db-color-action-tertiary-background-hover);
17195
- --db-btn-surface-press: var(--db-color-action-tertiary-background-press);
17196
- }
17197
- /* Link is borderless and text-only, so its hover/press surface stays transparent. */
17198
- .db-btn-link {
17199
- --db-btn-surface-hover: transparent;
17200
- --db-btn-surface-press: transparent;
17451
+ /* Hover/press live on the root (label), not on the input: the input is
17452
+ * visually hidden (opacity:0), so an `input:hover` rule wouldn't be visible
17453
+ * anyway, and the whole label area should drive the feedback. */
17454
+ .db-radio:hover:has(input:not(:disabled, :checked)) .db-radio-indicator {
17455
+ background-color: var(--db-radio-surface-hover);
17456
+ border-color: var(--db-radio-border-hover);
17201
17457
  }
17202
- /* Every danger button (icon included) disables to the filled treatment. */
17203
- .db-btn-danger {
17204
- --db-btn-surface-disabled: var(--db-color-action-disabled-border);
17205
- --db-btn-border-disabled: transparent;
17206
- --db-btn-text-disabled: var(--db-color-action-primary-text-default);
17458
+
17459
+ .db-radio:active:has(input:not(:disabled, :checked)) .db-radio-indicator {
17460
+ background-color: var(--db-radio-surface-active);
17461
+ border-color: var(--db-radio-border-active);
17207
17462
  }
17208
- /* Danger, filled (primary): re-set only the surface; text/transparent border come from primary. */
17209
- .db-btn-primary.db-btn-danger:not(.db-btn-icon-only) {
17210
- --db-btn-surface: var(--db-color-action-danger-primary-background-default);
17211
- --db-btn-surface-hover: var(--db-color-action-danger-primary-background-hover);
17212
- --db-btn-surface-press: var(--db-color-action-danger-primary-background-press);
17463
+
17464
+ /* Selected fill. */
17465
+ .db-radio:has(input:checked) .db-radio-indicator {
17466
+ background-color: var(--db-radio-surface-checked);
17467
+ border-color: var(--db-radio-surface-checked);
17213
17468
  }
17214
- /* Danger, secondary/outlined. */
17215
- .db-btn-danger:not(.db-btn-primary):not(.db-btn-tertiary):not(.db-btn-link):not(.db-btn-icon-only) {
17216
- --db-btn-surface: var(--db-color-action-danger-default-background-default);
17217
- --db-btn-surface-hover: var(--db-color-action-danger-default-background-hover);
17218
- --db-btn-surface-press: var(--db-color-action-danger-default-background-press);
17219
- --db-btn-border: var(--db-color-action-danger-default-border-default);
17220
- --db-btn-border-hover: var(--db-color-action-danger-default-border-hover);
17221
- --db-btn-border-press: var(--db-color-action-danger-default-border-press);
17222
- --db-btn-text: var(--db-color-action-danger-default-text-default);
17223
- --db-btn-text-hover: var(--db-color-action-danger-default-text-hover);
17224
- --db-btn-text-press: var(--db-color-action-danger-default-text-press);
17469
+
17470
+ /* AntD getRadioInputStyles checked hover: background = actionPrimaryBackgroundHover,
17471
+ * borderColor = actionPrimaryBackgroundPress (a darker ring than the fill). */
17472
+ .db-radio:hover:has(input:checked:not(:disabled)) .db-radio-indicator {
17473
+ background-color: var(--db-radio-surface-checked-hover);
17474
+ border-color: var(--db-radio-border-checked-hover);
17225
17475
  }
17226
- /* Danger, tertiary/link: danger text everywhere, danger surface on hover/press; resting/disabled
17227
- * surface stay the tertiary ghost (override the filled danger-disabled set above). */
17228
- .db-btn-tertiary.db-btn-danger,
17229
- .db-btn-link.db-btn-danger {
17230
- --db-btn-surface-hover: var(--db-color-action-danger-default-background-hover);
17231
- --db-btn-surface-press: var(--db-color-action-danger-default-background-press);
17232
- --db-btn-surface-disabled: var(--db-color-action-tertiary-background-default);
17233
- --db-btn-text: var(--db-color-action-danger-default-text-default);
17234
- --db-btn-text-hover: var(--db-color-action-danger-default-text-hover);
17235
- --db-btn-text-press: var(--db-color-action-danger-default-text-press);
17236
- --db-btn-text-disabled: var(--db-color-action-disabled-text);
17476
+
17477
+ /* AntD getRadioInputStyles checked active: background = actionDefaultBackgroundPress,
17478
+ * borderColor = actionDefaultBorderPress (two-tone, not flat). */
17479
+ .db-radio:active:has(input:checked:not(:disabled)) .db-radio-indicator {
17480
+ background-color: var(--db-radio-surface-checked-active);
17481
+ border-color: var(--db-radio-border-checked-active);
17237
17482
  }
17238
- /* Icon-only renders as the ghost (tertiary) treatment regardless of variant; re-set the semantic set
17239
- * so the icon-only rules paint from these component tokens. */
17240
- .db-btn-icon-only {
17241
- --db-btn-surface: var(--db-color-action-tertiary-background-default);
17242
- --db-btn-surface-hover: var(--db-color-action-tertiary-background-hover);
17243
- --db-btn-surface-press: var(--db-color-action-tertiary-background-press);
17244
- --db-btn-text: var(--db-color-action-tertiary-text-default);
17245
- --db-btn-text-hover: var(--db-color-action-tertiary-text-hover);
17246
- --db-btn-text-press: var(--db-color-action-tertiary-text-press);
17483
+
17484
+ /* Dot visibility. */
17485
+ .db-radio:has(input:checked) .db-radio-dot {
17486
+ opacity: 1;
17247
17487
  }
17248
- /* Danger icon-only keeps the ghost resting surface (inherited above) but uses danger text and a
17249
- * danger hover/press surface. */
17250
- .db-btn-icon-only.db-btn-danger {
17251
- --db-btn-surface-hover: var(--db-color-action-danger-default-background-hover);
17252
- --db-btn-surface-press: var(--db-color-action-danger-default-background-press);
17253
- --db-btn-text: var(--db-color-action-danger-default-text-default);
17254
- --db-btn-text-hover: var(--db-color-action-danger-default-text-hover);
17255
- --db-btn-text-press: var(--db-color-action-danger-default-text-press);
17488
+
17489
+ /* Focus-visible (keyboard only). AntD getRadioInputStyles splits focus by checked
17490
+ * state, so we do too.
17491
+ *
17492
+ * UNCHECKED focus (AntD `&:not(disabled) > input:focus + inner`): only the border
17493
+ * color changes to actionPrimaryBackgroundDefault — NO outline ring. */
17494
+ .db-radio:has(input:focus-visible:not(:checked)) .db-radio-indicator {
17495
+ border-color: var(--db-radio-border-unchecked-focus);
17256
17496
  }
17257
17497
 
17258
- /* ── 1. Base paints the default (secondary) variant ────────────────────── */
17259
- .db-btn {
17260
- appearance: none;
17261
- box-sizing: border-box;
17262
- position: relative; /* anchors the loading spinner */
17263
- display: inline-flex !important;
17264
- align-items: center !important;
17265
- justify-content: center !important;
17266
- vertical-align: middle;
17267
- white-space: nowrap;
17268
- user-select: none;
17269
- cursor: pointer;
17270
- height: var(--db-btn-height) !important;
17271
- font-family: inherit;
17272
- font-size: var(--db-btn-font-size);
17273
- font-weight: var(--db-btn-font-weight);
17274
- line-height: var(--db-btn-line-height) !important;
17275
- text-decoration: none !important;
17276
- border: var(--db-btn-border-width) solid var(--db-btn-border);
17277
- border-color: var(--db-btn-border) !important;
17278
- border-radius: var(--db-btn-border-radius) !important;
17279
- box-shadow: none !important;
17280
- background-color: var(--db-btn-surface) !important;
17281
- color: var(--db-btn-text) !important;
17498
+ /* CHECKED focus-visible (AntD `&.checked &:not(disabled) > input:focus-visible + inner`):
17499
+ * background = actionPrimaryBackgroundDefault, borderColor = actionDefaultBorderFocus,
17500
+ * outline = 1px solid actionPrimaryBackgroundDefault, outlineOffset = 1. */
17501
+ .db-radio:has(input:focus-visible:checked) .db-radio-indicator {
17502
+ background-color: var(--db-radio-surface-checked);
17503
+ border-color: var(--db-radio-border-focus);
17504
+ outline: 1px solid var(--db-radio-outline-checked-focus);
17505
+ outline-offset: 1px;
17282
17506
  }
17283
17507
 
17284
- /* ── 2. Padding ──────────────────────────────────────────────────────────── */
17285
- .db-btn.db-btn:where(:not(.db-btn-icon-only, .db-btn-force-icon, .db-btn-link)) {
17286
- padding: var(--db-btn-padding-vertical) var(--db-btn-padding-horizontal) !important;
17508
+ /* Disabled (covers checked and unchecked). Sourced from `input:disabled` so the
17509
+ * standalone-disabled and group-disabled paths both land here. */
17510
+ .db-radio:has(input:disabled) {
17511
+ cursor: not-allowed;
17287
17512
  }
17288
- .db-btn.db-btn-small:where(:not(.db-btn-icon-only, .db-btn-force-icon, .db-btn-link)) {
17289
- padding: 0 var(--db-btn-padding-horizontal-small) !important;
17513
+
17514
+ .db-radio:has(input:disabled) .db-radio-label {
17515
+ color: var(--db-radio-text-disabled);
17290
17516
  }
17291
- .db-btn.db-btn-link {
17292
- padding: 0 !important;
17517
+
17518
+ .db-radio:has(input:disabled) .db-radio-indicator {
17519
+ background-color: var(--db-radio-surface-disabled);
17520
+ border-color: var(--db-radio-border-disabled);
17521
+ box-shadow: none;
17293
17522
  }
17294
- .db-btn.db-btn-force-icon {
17295
- padding: 0 var(--db-btn-padding-horizontal-icon) !important;
17523
+
17524
+ .db-radio:has(input:disabled) .db-radio-dot {
17525
+ background-color: var(--db-radio-mark-disabled);
17296
17526
  }
17297
17527
 
17298
- /* ── 3. Interactive states ───────────────────────────────────────────────── */
17299
- .db-btn:hover:not(:disabled, .db-btn-loading) {
17300
- background-color: var(--db-btn-surface-hover) !important;
17301
- border-color: var(--db-btn-border-hover) !important;
17302
- color: var(--db-btn-text-hover) !important;
17528
+ /* Visually-hidden native input (opacity:0) overlaying the indicator circle,
17529
+ * kept interactive and in the tab order so the `:has()` lookups above can read
17530
+ * its state. It stays pointer-clickable — matching AntD's `.ant-radio-input`
17531
+ * so it is the top-most hit-test target over the circle: `getByRole('radio').click()`
17532
+ * (Playwright, and any coordinate click that lands on the circle) resolves to the
17533
+ * real control instead of the decorative indicator overlay. Making the input
17534
+ * `pointer-events: none` (the prior native-only behavior) is what regressed
17535
+ * interaction tests vs. the AntD variant. `cursor: inherit` tracks the label's
17536
+ * pointer / disabled `not-allowed` cursor so the circle keeps AntD's cursor too. */
17537
+ .db-radio input {
17538
+ position: absolute;
17539
+ inset: 0;
17540
+ inline-size: var(--db-radio-size);
17541
+ block-size: var(--db-radio-size);
17542
+ margin: 0;
17543
+ opacity: 0;
17544
+ cursor: inherit;
17303
17545
  }
17304
- .db-btn:active:not(:disabled, .db-btn-loading) {
17305
- background-color: var(--db-btn-surface-press) !important;
17306
- border-color: var(--db-btn-border-press) !important;
17307
- color: var(--db-btn-text-press) !important;
17546
+
17547
+ /* Indicator — circle with the border/fill that responds to state above.
17548
+ * Vertical math aligns the indicator to the cap-height of the first text line
17549
+ * so radio + label sit on the same visual baseline even when the label wraps. */
17550
+ .db-radio .db-radio-indicator {
17551
+ display: grid;
17552
+ place-items: center;
17553
+ flex-shrink: 0;
17554
+ /* Decorative only — the overlaid input owns hit-testing. Without this, the
17555
+ * indicator (and its inherited dot child) intercept `getByRole('radio').click()`;
17556
+ * clicks on any edge sliver the input does not cover fall through to the label,
17557
+ * which toggles via label/input association. */
17558
+ pointer-events: none;
17559
+ inline-size: var(--db-radio-size);
17560
+ block-size: var(--db-radio-size);
17561
+ margin-top: calc((var(--db-line-height-base, 20px) - var(--db-radio-size)) / 2);
17562
+ border: var(--db-radio-border-width) solid var(--db-radio-border);
17563
+ border-radius: var(--db-radio-border-radius);
17564
+ background-color: var(--db-radio-surface);
17565
+ box-sizing: border-box;
17566
+ transition:
17567
+ background-color var(--db-radio-transition-duration) ease,
17568
+ border-color var(--db-radio-transition-duration) ease,
17569
+ box-shadow var(--db-radio-transition-duration) ease;
17308
17570
  }
17309
17571
 
17310
- /* ── 4. Focus ────────────────────────────────────────────────────────────── */
17311
- .db-btn:focus-visible {
17312
- outline: 2px solid var(--db-btn-focus-ring);
17313
- outline-offset: -2px;
17572
+ .db-radio .db-radio-indicator > .db-radio-dot {
17573
+ grid-area: 1 / 1;
17574
+ inline-size: var(--db-radio-mark-size);
17575
+ block-size: var(--db-radio-mark-size);
17576
+ border-radius: var(--db-radius-full);
17577
+ background-color: var(--db-radio-mark);
17578
+ opacity: 0;
17579
+ transition: opacity var(--db-radio-transition-duration) ease;
17314
17580
  }
17315
- .db-btn-primary:focus-visible {
17316
- outline-offset: 1px;
17581
+
17582
+ /* Honour the user's reduced-motion preference: drop the state-change
17583
+ * transitions on the indicator and dot so checking/hovering is instant. */
17584
+ @media (prefers-reduced-motion: reduce) {
17585
+ .db-radio .db-radio-indicator,
17586
+ .db-radio .db-radio-indicator > .db-radio-dot {
17587
+ transition: none;
17588
+ }
17317
17589
  }
17318
- .db-btn-use-focus-pseudo-class:focus {
17319
- outline: 2px solid var(--db-btn-focus-ring);
17320
- outline-offset: 1px;
17590
+
17591
+ /* Padding-inline replicates the AntD `.{prefix} + span` label gap and the
17592
+ * trailing whitespace that separates adjacent radios in a row. */
17593
+ .db-radio .db-radio-label {
17594
+ color: var(--db-radio-text);
17595
+ padding-inline: var(--db-radio-label-spacing);
17596
+ word-break: break-word;
17321
17597
  }
17322
17598
 
17323
- /* ── 5. Small / full-width / empty ────────────────────────────────────────── */
17324
- .db-btn-small {
17325
- height: var(--db-btn-height-small) !important;
17599
+ /* Form-flow integration: align helper text under the radio label. The `paddingLeft`
17600
+ * indent comes from AntD's getCommonRadioGroupStyles (`& > label + hint`) and applies
17601
+ * to BOTH layouts. The doubled root class pushes specificity to (0,3,0) to beat
17602
+ * FormMessage's runtime `&&` margin. */
17603
+ .db-radio.db-radio + .du-bois-light-hint,
17604
+ .db-radio.db-radio + .du-bois-dark-hint,
17605
+ .db-radio.db-radio + .du-bois-light-form-message,
17606
+ .db-radio.db-radio + .du-bois-dark-form-message,
17607
+ .db-radio.db-radio + .du-bois-light-hint + .du-bois-light-form-message,
17608
+ .db-radio.db-radio + .du-bois-dark-hint + .du-bois-dark-form-message {
17609
+ padding-inline-start: var(--db-space-6);
17610
+ margin-top: 0;
17326
17611
  }
17327
- .db-btn-full-width {
17328
- width: 100%;
17612
+
17613
+ /* Vertical hint rhythm (AntD getVerticalRadioGroupStyles, vertical-only): a hint
17614
+ * after a radio gets `marginTop: -sm` (cancels the preceding radio's
17615
+ * `paddingBottom: sm` so the hint tucks directly under its label) and
17616
+ * `marginBottom: sm` (gap before the next radio). The negative top margin relies on
17617
+ * the .db-stack gap being 0 (set below). Scoped to .db-stack so it never perturbs
17618
+ * the horizontal grid, where hints are positioned by grid-row instead. */
17619
+ .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-light-hint,
17620
+ .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-dark-hint,
17621
+ .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-light-form-message,
17622
+ .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-dark-form-message,
17623
+ .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-light-hint + .du-bois-light-form-message,
17624
+ .db-radio-group.db-stack > .db-radio.db-radio + .du-bois-dark-hint + .du-bois-dark-form-message {
17625
+ margin-top: calc(-1 * var(--db-space-2));
17626
+ margin-bottom: var(--db-space-2);
17329
17627
  }
17330
- .db-btn-empty {
17331
- background-color: transparent !important;
17332
- border-color: transparent !important;
17628
+
17629
+ /* AntD `& > label:last-of-type + hint { marginTop: 0 }`: the last radio has no
17630
+ * `paddingBottom` to cancel, so its trailing hint keeps its natural top position. */
17631
+ .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-light-hint,
17632
+ .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-dark-hint,
17633
+ .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-light-form-message,
17634
+ .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-dark-form-message,
17635
+ .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-light-hint + .du-bois-light-form-message,
17636
+ .db-radio-group.db-stack > .db-radio.db-radio:last-of-type + .du-bois-dark-hint + .du-bois-dark-form-message {
17637
+ margin-top: 0;
17333
17638
  }
17334
- .db-btn-default.db-btn-empty {
17335
- color: var(--db-btn-icon-glyph) !important;
17639
+
17640
+ .du-bois-light-label + .db-radio-group.db-radio-group,
17641
+ .du-bois-dark-label + .db-radio-group.db-radio-group,
17642
+ .du-bois-light-hint + .db-radio-group.db-radio-group,
17643
+ .du-bois-dark-hint + .db-radio-group.db-radio-group,
17644
+ .db-radio-group.db-radio-group + .du-bois-light-form-message,
17645
+ .db-radio-group.db-radio-group + .du-bois-dark-form-message {
17646
+ margin-top: var(--db-space-2);
17336
17647
  }
17337
- .db-btn-empty.db-btn-loading {
17338
- width: var(--db-btn-loading-empty-width);
17648
+
17649
+ /* RadioGroup layout. Vertical (the default) delegates flex-direction to the
17650
+ * .db-stack primitive; horizontal is a two-row grid (mirroring AntD). */
17651
+ .db-radio-group {
17652
+ display: flex;
17339
17653
  }
17340
17654
 
17341
- /* ── 6. Tertiary & link ──────────────────────────────────────────────────── */
17342
- .db-btn-link {
17343
- height: auto !important;
17344
- border: none !important;
17655
+ /* Vertical: .db-stack supplies flex-direction: column. AntD's vertical helper
17656
+ * (getVerticalRadioGroupStyles) produces its item rhythm purely from
17657
+ * `& > label { paddingBottom: sm }` plus the negative-margin hint trick above — it
17658
+ * uses NO flex gap. The negative `marginTop` on hints only works if the stack adds
17659
+ * no gap of its own, so we zero out the gap and reproduce the rhythm via
17660
+ * padding-bottom on each radio. */
17661
+ .db-radio-group.db-stack {
17662
+ --db-stack-gap: 0;
17345
17663
  }
17346
- /* Re-declare the text color for a non-icon-only tertiary/link at two-class weight so a plain
17347
- * call-site `color` override loses; an icon-only glyph stays at base weight so its override wins. */
17348
- .db-btn-tertiary:not(.db-btn-icon-only),
17349
- .db-btn-link:not(.db-btn-icon-only) {
17350
- color: var(--db-btn-text) !important;
17664
+
17665
+ /* AntD `& > label { paddingBottom: sm }`. */
17666
+ .db-radio-group.db-stack > .db-radio {
17667
+ padding-bottom: var(--db-space-2);
17351
17668
  }
17352
- /* Keep the resting border at two-class weight (parity with the shielded border-color). */
17353
- .db-btn.db-btn-tertiary,
17354
- .db-btn.db-btn-link {
17355
- border-color: var(--db-btn-border) !important;
17669
+
17670
+ /* AntD `& > label:last-of-type { paddingBottom: 0 }`. */
17671
+ .db-radio-group.db-stack > .db-radio:last-of-type {
17672
+ padding-bottom: 0;
17356
17673
  }
17357
17674
 
17358
- /* ── 7. Danger ───────────────────────────────────────────────────────────── */
17359
- .db-btn-danger:focus-visible {
17360
- outline-color: var(--db-btn-danger-focus-ring);
17675
+ /* AntD getVerticalRadioGroupStyles `.${prefix}-radio-tile + .${prefix}-radio-tile {
17676
+ * marginTop: md }`: RadioTiles are not matched by the `.db-radio` padding-bottom
17677
+ * rhythm above (the tile's own class is du-bois-{light,dark}-radio-tile), so space
17678
+ * consecutive tiles explicitly. The .db-stack gap is 0, so this margin is the spacing. */
17679
+ .db-radio-group.db-stack > .du-bois-light-radio-tile + .du-bois-light-radio-tile,
17680
+ .db-radio-group.db-stack > .du-bois-light-radio-tile + .du-bois-dark-radio-tile,
17681
+ .db-radio-group.db-stack > .du-bois-dark-radio-tile + .du-bois-light-radio-tile,
17682
+ .db-radio-group.db-stack > .du-bois-dark-radio-tile + .du-bois-dark-radio-tile {
17683
+ margin-top: var(--db-space-4);
17361
17684
  }
17362
17685
 
17363
- /* ── 8. Icon-only ────────────────────────────────────────────────────────── */
17364
- .db-btn-icon-only {
17365
- padding: 0;
17366
- border: none !important;
17367
- background-color: var(--db-btn-surface) !important;
17368
- font-size: var(--db-btn-icon-only-font-size);
17686
+ /* Horizontal: AntD `getHorizontalRadioGroupStyles` is a TWO-ROW grid (NOT a flex
17687
+ * row) so each radio sits in the [label] row and its following hint drops into the
17688
+ * [hint] row of the same auto-column. `&&` in AntD doubles specificity; the doubled
17689
+ * group class here matches that.
17690
+ * AntD: `display: grid; gridTemplateRows: [label] auto [hint] auto;
17691
+ * gridAutoColumns: max-content; gridColumnGap: md`. */
17692
+ .db-radio-group--horizontal.db-radio-group--horizontal {
17693
+ display: grid;
17694
+ grid-template-rows: [label] auto [hint] auto;
17695
+ grid-auto-columns: max-content;
17696
+ column-gap: var(--db-space-4);
17369
17697
  }
17370
- /* `:where()` keeps this at (0,1,0) so a call-site `css` width override wins, as it does for `height`. */
17371
- .db-btn-icon-only:where(:not(.db-btn-small)) {
17372
- width: var(--db-btn-height) !important;
17698
+
17699
+ /* Equal column widths: AntD `gridAutoColumns: minmax(0, 1fr)`. */
17700
+ .db-radio-group--horizontal.db-radio-group--equal-width {
17701
+ grid-auto-columns: minmax(0, 1fr);
17373
17702
  }
17374
- .db-btn-icon-only:not(.db-btn-link, .db-btn-tertiary, [href], :hover, :active, :disabled) {
17375
- color: var(--db-btn-icon-glyph) !important;
17703
+
17704
+ /* AntD `& > label, & > .${prefix}-radio-tile { gridRow: label / label; marginRight: 0 }`:
17705
+ * every radio (and every RadioTile button, whose own class is
17706
+ * du-bois-{light,dark}-radio-tile and is NOT matched by `> .db-radio`) lives in the
17707
+ * label row. grid-auto-flow defaults to row, so successive items advance the implicit
17708
+ * column. */
17709
+ .db-radio-group--horizontal > .db-radio,
17710
+ .db-radio-group--horizontal > .du-bois-light-radio-tile,
17711
+ .db-radio-group--horizontal > .du-bois-dark-radio-tile {
17712
+ grid-row: label / label;
17713
+ margin-inline-end: 0;
17376
17714
  }
17377
- /* `:where()` on the exclusion holds this at (0,2,0): enough to tint an un-overridden icon-only link
17378
- * over the base color, while still letting a consumer's own icon-color override win on source order. */
17379
- .db-btn-icon-only[href]:where(:not(.db-btn-tertiary, .db-btn-link, .db-btn-danger, :disabled)) {
17380
- color: var(--db-btn-text) !important;
17715
+
17716
+ /* AntD `& > label + hint { display: inline-block; gridRow: hint / hint }`: a hint
17717
+ * following a radio drops into the hint row of the column its radio occupies. The
17718
+ * vertical negative-margin rules are scoped to .db-stack, so nothing to reset here. */
17719
+ .db-radio-group--horizontal > .du-bois-light-hint,
17720
+ .db-radio-group--horizontal > .du-bois-dark-hint {
17721
+ display: inline-block;
17722
+ grid-row: hint / hint;
17381
17723
  }
17382
- .db-btn-icon-only:where(.db-btn-small) {
17383
- width: var(--db-btn-height-small);
17724
+
17725
+ /* AntD `:has(> hint) { marginTop: sm }`: when the horizontal group contains a hint,
17726
+ * the group itself gets a top margin (room above the two-row grid). */
17727
+ .db-radio-group--horizontal:has(> .du-bois-light-hint),
17728
+ .db-radio-group--horizontal:has(> .du-bois-dark-hint) {
17729
+ margin-top: var(--db-space-2);
17384
17730
  }
17385
- .db-btn-icon-only.db-btn-small {
17386
- line-height: var(--db-btn-line-height-small) !important;
17731
+
17732
+ /* AntD `&& + form-message { marginTop: sm }` (horizontal): a form-message following
17733
+ * the whole group is separated by sm. */
17734
+ .db-radio-group--horizontal + .du-bois-light-form-message,
17735
+ .db-radio-group--horizontal + .du-bois-dark-form-message {
17736
+ margin-top: var(--db-space-2);
17387
17737
  }
17388
- .db-btn-icon-only:hover:not(:disabled, .db-btn-loading) {
17389
- background-color: var(--db-btn-surface-hover) !important;
17390
- color: var(--db-btn-text-hover) !important;
17738
+
17739
+
17740
+
17741
+
17742
+
17743
+ :where(:root), :where(.du-bois-light) {
17744
+ /* Shape */
17745
+ --db-slider-border-radius: var(--db-shape-pill);
17746
+
17747
+ /* Color */
17748
+ /* No v2 token holds the track + disabled-range surface yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17749
+ --db-slider-track-surface: #e8ecf0;
17750
+ --db-slider-range-surface-disabled: #e8ecf0;
17751
+ /* No v2 token holds the range fill yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17752
+ --db-slider-range-surface: #2272b4;
17753
+
17754
+ --db-slider-thumb-surface: var(--db-accent-surface);
17755
+ --db-slider-thumb-surface-hover: var(--db-accent-surface-hover);
17756
+ --db-slider-thumb-surface-press: var(--db-accent-surface-active);
17757
+ /* No v2 token holds the disabled thumb yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17758
+ --db-slider-thumb-surface-disabled: #d1d9e1;
17759
+
17760
+ /* Density */
17761
+ --db-slider-size: var(--db-size-5); /* 20px */
17762
+ --db-slider-thumb-size: var(--db-size-5); /* 20px */
17763
+ --db-slider-track-thickness: calc(var(--db-size-1) * 0.75); /* 3px */
17391
17764
  }
17392
- .db-btn-icon-only:active:not(:disabled, .db-btn-loading) {
17393
- background-color: var(--db-btn-surface-press) !important;
17394
- color: var(--db-btn-text-press) !important;
17765
+
17766
+ :where(:root), :where(.du-bois-light) {
17767
+ /* Shape */
17768
+ /* TODO(FEINF-6505): adopt --db-shape-container-* once a 4px container radius exists; kept on --db-radius-sm to preserve 4px. */
17769
+ --db-snackbar-border-radius: var(--db-radius-sm);
17770
+
17771
+ /* Density */
17772
+ --db-snackbar-spacing-vertical: var(--db-space-3);
17773
+ --db-snackbar-spacing-horizontal: var(--db-space-4);
17774
+ --db-snackbar-close-button-spacing: var(--db-space-2);
17775
+
17776
+ /* Color */
17777
+ /* No v1/v2 system token holds this theme-invariant white. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17778
+ --db-snackbar-text: #ffffff;
17779
+ /* No v1/v2 system token holds these theme-invariant close-icon colors. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17780
+ --db-snackbar-close-icon: #e8ecf0;
17781
+ --db-snackbar-close-icon-hover: #8acaff;
17782
+ --db-snackbar-close-icon-active: #bae1fc;
17395
17783
  }
17396
- .db-btn-icon-only:enabled:not(.db-btn-primary, .db-btn-tertiary, .db-btn-link, .db-btn-danger, :hover, :active)
17397
- > .anticon {
17398
- color: var(--db-btn-icon-glyph);
17784
+
17785
+ :where(:root), :where(.du-bois-light) {
17786
+ /* Color */
17787
+ /* No v1/v2 system token holds this light/dark surface pair. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17788
+ --db-snackbar-surface: #11171c;
17789
+
17790
+ /* Elevation */
17791
+ --db-snackbar-shadow: var(--db-elevation-med);
17399
17792
  }
17400
- .db-btn-icon-only.db-btn-danger:not(:hover, :active) > .anticon {
17401
- color: var(--db-btn-text);
17793
+
17794
+ /* Spacer's height is a spacing step. The size prop (xs/sm/mid/md/lg) selects one component token,
17795
+ * each aliasing the system space scale — theme-invariant, so a single block (no light/dark split). */
17796
+
17797
+ :where(:root), :where(.du-bois-light) {
17798
+ /* Density */
17799
+ --db-spacer-size-xs: var(--db-space-1); /* 4px */
17800
+ --db-spacer-size-sm: var(--db-space-2); /* 8px */
17801
+ --db-spacer-size-mid: var(--db-space-3); /* 12px */
17802
+ --db-spacer-size-md: var(--db-space-4); /* 16px */
17803
+ --db-spacer-size-lg: var(--db-space-6); /* 24px */
17402
17804
  }
17403
- .db-btn-icon-only.db-btn-danger:hover:not(:disabled, .db-btn-loading) {
17404
- background-color: var(--db-btn-surface-hover) !important;
17405
- color: var(--db-btn-text-hover) !important;
17805
+
17806
+
17807
+
17808
+ :where(:root), :where(.du-bois-light) {
17809
+ /* Shape */
17810
+ --db-switch-border-radius: var(--db-radius-full);
17811
+ --db-switch-border-width: 1px;
17812
+
17813
+ /* Color */
17814
+ --db-switch-surface: var(--db-selection-toggle-surface);
17815
+ --db-switch-surface-hover: var(--db-color-action-default-background-hover);
17816
+ --db-switch-surface-active: var(--db-color-action-default-background-press);
17817
+ --db-switch-surface-checked: var(--db-selection-toggle-surface-checked);
17818
+ --db-switch-surface-checked-hover: var(--db-selection-toggle-surface-checked-hover);
17819
+ --db-switch-surface-checked-active: var(--db-selection-toggle-surface-checked-active);
17820
+ --db-switch-surface-disabled: var(--db-selection-toggle-surface-disabled);
17821
+ --db-switch-surface-checked-disabled: var(--db-color-action-disabled-text);
17822
+
17823
+ --db-switch-border: var(--db-color-action-default-border-default);
17824
+ --db-switch-border-hover: var(--db-selection-toggle-border-hover);
17825
+ --db-switch-border-active: var(--db-selection-toggle-border-active);
17826
+ --db-switch-border-disabled: var(--db-color-action-disabled-border);
17827
+ --db-switch-border-checked: var(--db-selection-toggle-surface-checked);
17828
+ --db-switch-border-checked-hover: var(--db-selection-toggle-surface-checked-hover);
17829
+ --db-switch-border-checked-active: var(--db-selection-toggle-surface-checked-active);
17830
+ --db-switch-border-focus: var(--db-selection-toggle-surface-checked);
17831
+
17832
+ --db-switch-thumb: var(--db-selection-toggle-surface);
17833
+
17834
+ --db-switch-text: var(--db-selection-toggle-text);
17835
+ --db-switch-text-disabled: var(--db-selection-toggle-text-disabled);
17836
+
17837
+ /* Density */
17838
+ --db-switch-track-width: calc(var(--db-size-4) * 1.75); /* 28px */
17839
+ --db-switch-track-height: var(--db-size-4); /* 16px */
17840
+ --db-switch-thumb-size: var(--db-switch-track-height);
17841
+ --db-switch-label-spacing: var(--db-selection-toggle-label-spacing);
17842
+
17843
+ /* Motion */
17844
+ --db-switch-transition-duration: var(--db-selection-toggle-transition-duration);
17406
17845
  }
17407
- .db-btn-icon-only.db-btn-danger:active:not(:disabled, .db-btn-loading) {
17408
- background-color: var(--db-btn-surface-press) !important;
17409
- color: var(--db-btn-text-press) !important;
17846
+
17847
+ .db-switch {
17848
+ /* flex, not inline-flex: an inline-flex root's line-box leading pushes block siblings
17849
+ * (e.g. a following FormMessage) down a few px, breaking the form-flow spacing. */
17850
+ display: flex;
17851
+ align-items: center;
17852
+ cursor: pointer;
17853
+ user-select: none;
17854
+ width: fit-content;
17855
+ margin-inline-end: var(--db-switch-label-spacing);
17410
17856
  }
17411
17857
 
17412
- /* ── 9. Disabled — base rules paint the per-variant `*-disabled` tokens (see tokens.css) ──────── */
17413
- .db-btn:disabled {
17858
+ .db-switch:has(.db-switch-input:disabled) {
17414
17859
  cursor: not-allowed;
17415
- background-color: var(--db-btn-surface-disabled) !important;
17416
- color: var(--db-btn-text-disabled) !important;
17417
- }
17418
- /* Empty and loading disabled buttons keep a transparent border (set elsewhere), so exclude them.
17419
- * `:where()` on the exclusion holds this at (0,2,0): enough to beat the base border, while still
17420
- * letting a consumer's own `[disabled]` border override win on source order. */
17421
- .db-btn:disabled:where(:not(.db-btn-empty, .db-btn-loading)) {
17422
- border-color: var(--db-btn-border-disabled) !important;
17423
- }
17424
- .db-btn-icon-only:disabled {
17425
- border: none;
17426
17860
  }
17427
- .db-btn-icon-only:disabled:hover {
17428
- background-color: transparent !important;
17861
+
17862
+ /* The track. `appearance: none` strips the native checkbox rendering so the input itself can be styled.
17863
+ * Scoped via the `.db-switch-input` class, never a bare `input`: `.db-switch .db-switch-input` is
17864
+ * specificity (0,2,0), which out-ranks a host app's `input[type='checkbox'] { width: auto }` reset
17865
+ * (e.g. Bootstrap, loaded in webapp). A bare `.db-switch input` (0,1,1) merely ties that reset and, losing
17866
+ * on load order, has its `inline-size` overridden to `auto` — collapsing the track to zero width and
17867
+ * leaving only the floating thumb. Mirrors NativeCheckbox's `.db-checkbox-input`. */
17868
+ .db-switch .db-switch-input {
17869
+ appearance: none;
17870
+ position: relative;
17871
+ flex-shrink: 0;
17872
+ margin: 0;
17873
+ cursor: pointer;
17874
+ inline-size: var(--db-switch-track-width);
17875
+ block-size: var(--db-switch-track-height);
17876
+ border: var(--db-switch-border-width) solid var(--db-switch-border);
17877
+ border-radius: var(--db-switch-border-radius);
17878
+ background-color: var(--db-switch-surface);
17879
+ box-sizing: border-box;
17880
+ transition:
17881
+ background-color var(--db-switch-transition-duration) ease,
17882
+ border-color var(--db-switch-transition-duration) ease;
17429
17883
  }
17430
- .db-btn:disabled > .anticon {
17431
- color: inherit !important;
17884
+
17885
+ /* The thumb. A pseudo-element keeps the input the switch's only element, so nothing can overlay it
17886
+ * and intercept clicks. Offset left by the border width to sit flush with the track's outer edge. */
17887
+ .db-switch .db-switch-input::before {
17888
+ content: '';
17889
+ position: absolute;
17890
+ top: 50%;
17891
+ left: calc(-1 * var(--db-switch-border-width));
17892
+ inline-size: var(--db-switch-thumb-size);
17893
+ block-size: var(--db-switch-thumb-size);
17894
+ border: var(--db-switch-border-width) solid;
17895
+ border-color: inherit;
17896
+ border-radius: var(--db-switch-border-radius);
17897
+ background-color: var(--db-switch-thumb);
17898
+ box-sizing: border-box;
17899
+ transform: translateY(-50%);
17900
+ transition: transform var(--db-switch-transition-duration) ease;
17432
17901
  }
17433
17902
 
17434
- /* ── 10. Icon & content layout ───────────────────────────────────────────── */
17435
- /* The leading icon renders unwrapped as the button's direct `.anticon` child (see NativeButton.tsx),
17436
- * so `> .anticon` targets only a real icon glyph — a non-glyph `icon` (e.g. a Spinner) keeps its own
17437
- * sizing — and out-specifies AntD's runtime-injected global `.anticon` rule. */
17438
- .db-btn > .anticon,
17439
- .db-btn-end-icon,
17440
- .db-btn-end-icon > .anticon {
17441
- display: inline-flex;
17442
- align-items: center;
17443
- font-size: var(--db-btn-icon-size) !important;
17444
- line-height: 0;
17903
+ /* `<label for>` propagates `:hover` to the input, so scope hover/press to the control, not `input:hover`. */
17904
+ .db-switch:hover .db-switch-input:not(:disabled):not(:checked) {
17905
+ background-color: var(--db-switch-surface-hover);
17906
+ border-color: var(--db-switch-border-hover);
17445
17907
  }
17446
- .db-btn-small > .anticon .anticon {
17447
- line-height: 0;
17908
+
17909
+ .db-switch:active .db-switch-input:not(:disabled):not(:checked) {
17910
+ background-color: var(--db-switch-surface-active);
17911
+ border-color: var(--db-switch-border-active);
17448
17912
  }
17449
- .db-btn > .db-btn-content > .anticon {
17450
- display: inline-flex;
17451
- align-items: center;
17452
- line-height: 0;
17913
+
17914
+ .db-switch .db-switch-input:checked {
17915
+ background-color: var(--db-switch-surface-checked);
17916
+ border-color: var(--db-switch-border-checked);
17453
17917
  }
17454
- .db-btn-content {
17455
- display: inline-flex;
17456
- align-items: center;
17918
+
17919
+ .db-switch:hover .db-switch-input:checked:not(:disabled) {
17920
+ background-color: var(--db-switch-surface-checked-hover);
17921
+ border-color: var(--db-switch-border-checked-hover);
17457
17922
  }
17458
- .db-btn > .anticon + .db-btn-content {
17459
- margin-inline-start: var(--db-btn-gap) !important;
17923
+
17924
+ .db-switch:active .db-switch-input:checked:not(:disabled) {
17925
+ background-color: var(--db-switch-surface-checked-active);
17926
+ border-color: var(--db-switch-border-checked-active);
17460
17927
  }
17461
- .db-btn-end-icon {
17462
- margin-inline-start: var(--db-btn-end-icon-gap) !important;
17928
+
17929
+ .db-switch .db-switch-input:checked::before {
17930
+ transform: translateY(-50%) translateX(calc(var(--db-switch-track-width) - var(--db-switch-thumb-size)));
17463
17931
  }
17464
- .db-btn-small .db-btn-end-icon {
17465
- margin-inline-start: var(--db-btn-end-icon-gap-small) !important;
17932
+
17933
+ /* On focus, recolor the border to the checked-fill color on top of the outline ring. Matching AntD,
17934
+ * the ring sits flush when off and gains a 1px offset when checked.
17935
+ * TODO: width/offset are hardcoded 1px to preserve the AntD look; adopt the system 2px focus ring
17936
+ * (var(--db-focus-ring-width)) in a separate change. */
17937
+ .db-switch .db-switch-input:focus-visible {
17938
+ border-color: var(--db-switch-border-focus);
17939
+ outline: 1px solid var(--db-focus-ring-color);
17940
+ outline-offset: 0;
17466
17941
  }
17467
- .db-btn-default:not(.db-btn-danger, [href], :hover, :active, :disabled) > .anticon {
17468
- color: var(--db-btn-icon-glyph);
17942
+
17943
+ .db-switch .db-switch-input:checked:focus-visible {
17944
+ outline-offset: 1px;
17469
17945
  }
17470
- .db-btn-default[href]:not(.db-btn-danger, :hover, :active, :disabled) .db-btn-end-icon {
17471
- color: var(--db-btn-icon-glyph);
17946
+
17947
+ .db-switch .db-switch-input:disabled {
17948
+ cursor: not-allowed;
17949
+ background-color: var(--db-switch-surface-disabled);
17950
+ border-color: var(--db-switch-border-disabled);
17472
17951
  }
17473
- .db-btn-default:not([href], .db-btn-danger, :hover, :active, :disabled)
17474
- > .db-btn-content
17475
- > .db-btn-end-icon
17476
- > .anticon {
17477
- color: var(--db-btn-icon-glyph);
17952
+
17953
+ /* Disabled + on: more specific than the plain disabled rule, so it wins regardless of source order. */
17954
+ .db-switch .db-switch-input:checked:disabled {
17955
+ background-color: var(--db-switch-surface-checked-disabled);
17956
+ border-color: var(--db-switch-surface-checked-disabled);
17478
17957
  }
17479
17958
 
17480
- /* ── 11. Loading ─────────────────────────────────────────────────────────── */
17481
- .db-btn-loading {
17482
- cursor: default;
17959
+ @media (prefers-reduced-motion: reduce) {
17960
+ .db-switch .db-switch-input,
17961
+ .db-switch .db-switch-input::before {
17962
+ transition: none;
17963
+ }
17483
17964
  }
17484
- .db-btn-loading:disabled {
17485
- border-color: transparent !important;
17965
+
17966
+ .db-switch-wrapper {
17967
+ display: flex;
17968
+ align-items: center;
17486
17969
  }
17487
- .db-btn-spinner {
17488
- color: inherit !important;
17489
- pointer-events: none;
17970
+
17971
+ .db-switch-wrapper .db-switch-state-message {
17972
+ margin-inline-start: auto;
17973
+ margin-inline-end: var(--db-switch-label-spacing);
17490
17974
  }
17491
- .db-btn-loading-with-icon .db-btn-content {
17492
- padding-inline-start: calc(var(--db-btn-icon-size) + var(--db-btn-gap));
17975
+
17976
+ /* `:first-child` scopes this to the label-first layout; the plain layout renders the label last. */
17977
+ .db-switch-wrapper > .du-bois-light-label:first-child,
17978
+ .db-switch-wrapper > .du-bois-dark-label:first-child {
17979
+ margin-inline-end: var(--db-switch-label-spacing);
17493
17980
  }
17494
- .db-btn-loading .db-btn-spinner {
17495
- position: absolute;
17496
- left: 50%;
17497
- top: 50%;
17498
- transform: translate(-50%, -50%);
17981
+
17982
+ .db-switch-state-message {
17983
+ color: var(--db-switch-text);
17499
17984
  }
17500
- .db-btn-loading.db-btn-icon-only .db-btn-spinner {
17501
- position: static;
17502
- transform: none;
17985
+
17986
+ /* Target the <Label>'s class, not a bare `label` — that would also match the .db-switch root <label>. */
17987
+ .db-switch-wrapper:has(.db-switch-input:disabled) .du-bois-light-label,
17988
+ .db-switch-wrapper:has(.db-switch-input:disabled) .du-bois-dark-label {
17989
+ color: var(--db-switch-text-disabled);
17503
17990
  }
17504
17991
 
17505
- /* ── 12. dangerouslySetForceIconStyles ───────────────────────────────────── */
17506
- .db-btn-force-icon {
17507
- width: var(--db-btn-height) !important;
17508
- color: var(--db-btn-icon-glyph) !important;
17992
+ /* Indent a following hint/message past the track. The class is doubled to out-specify FormMessage's
17993
+ * own margin rule. Each rule's key (rightmost) selector is a single class, not a multi-item :is(),
17994
+ * so Chromium can hash-reject it instead of attempting the match against every element on every
17995
+ * recalc (the leading/middle :is() groups aren't the key selector, so they're left as-is). */
17996
+ :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-light-hint, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-dark-hint, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-dark-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
17997
+ + :is(.du-bois-light-hint, .du-bois-dark-hint)
17998
+ + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
17999
+ + :is(.du-bois-light-hint, .du-bois-dark-hint)
18000
+ + .du-bois-dark-form-message {
18001
+ padding-inline-start: calc(var(--db-space-2) + var(--db-switch-track-width));
17509
18002
  }
17510
- .db-btn-force-icon.db-btn-small {
17511
- width: var(--db-btn-height-small) !important;
18003
+
18004
+ :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper) + .du-bois-dark-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
18005
+ + :is(.du-bois-light-hint, .du-bois-dark-hint)
18006
+ + .du-bois-light-form-message, :is(.db-switch.db-switch, .db-switch-wrapper.db-switch-wrapper)
18007
+ + :is(.du-bois-light-hint, .du-bois-dark-hint)
18008
+ + .du-bois-dark-form-message {
18009
+ margin-top: 0;
17512
18010
  }
17513
18011
 
18012
+ /* Component tokens for Tabs (no family). Re-declared under the theme classes, not only :root, so the
18013
+ * var() chain re-resolves inside a .du-bois-dark subtree instead of freezing at the light value. */
17514
18014
  :where(:root), :where(.du-bois-light) {
17515
- /* Shape */
17516
- /* Must not scale with density; no non-scaling system token holds it. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17517
- --db-navigation-menu-indicator-thickness: 4px;
17518
- /* No system token holds this control-specific inset. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17519
- --db-navigation-menu-focus-ring-offset: -1px;
18015
+ /* ── Density ──────────────────────────────────────────────────────────── */
18016
+ --db-tabs-height: var(--db-size-8);
18017
+ --db-tabs-trigger-min-width: var(--db-size-6);
18018
+ --db-tabs-trigger-min-width-closable: var(--db-size-10);
18019
+ --db-tabs-list-margin-bottom: var(--db-space-4);
18020
+ --db-tabs-trigger-gap: var(--db-space-4);
18021
+ --db-tabs-trigger-padding-top: var(--db-space-1);
18022
+ --db-tabs-trigger-padding-bottom: var(--db-space-2);
18023
+ --db-tabs-close-icon-gap: var(--db-space-1);
18024
+ --db-tabs-add-button-margin-top: var(--db-space-0-5);
18025
+ /* No 1.5-unit space step exists; synthesize 6px from the nearest. TODO: use a direct step if one lands. */
18026
+ --db-tabs-add-button-margin-bottom: calc(var(--db-space-1) * 1.5);
17520
18027
 
17521
- /* Color */
17522
- --db-navigation-menu-indicator: var(--db-accent-surface);
17523
- --db-navigation-menu-focus-ring: var(--db-focus-ring-color);
17524
- --db-navigation-menu-text: var(--db-text-subtle);
17525
- --db-navigation-menu-text-active: var(--db-text-base);
17526
- /* No v2 semantic for default-action link text yet; bridges on the v1 --db-color-* token. */
17527
- --db-navigation-menu-text-hover: var(--db-color-action-default-text-hover);
17528
- --db-navigation-menu-text-press: var(--db-color-action-default-text-press);
18028
+ /* ── Shape ────────────────────────────────────────────────────────────── */
18029
+ --db-tabs-scrollbar-radius: var(--db-shape-control);
18030
+ --db-tabs-focus-ring-width: var(--db-focus-ring-width);
17529
18031
 
17530
- /* Density */
17531
- --db-navigation-menu-item-height: var(--db-size-8);
17532
- --db-navigation-menu-item-min-width: var(--db-size-6);
17533
- --db-navigation-menu-link-padding-top: var(--db-space-1);
17534
- --db-navigation-menu-link-padding-bottom: var(--db-space-2);
17535
- --db-navigation-menu-focus-ring-width: var(--db-focus-ring-width);
18032
+ /* ── Typography ───────────────────────────────────────────────────────── */
18033
+ --db-tabs-font-size: var(--db-font-size-base);
18034
+ --db-tabs-font-weight: var(--db-font-weight-semibold);
18035
+ /* No v2 token holds the tab line-height yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18036
+ --db-tabs-line-height: 1.25rem;
18037
+
18038
+ /* ── Color ────────────────────────────────────────────────────────────── */
18039
+ /* No v2 token holds the tab text / hover / press / active / disabled colors yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18040
+ --db-tabs-text: var(--db-color-text-secondary);
18041
+ --db-tabs-text-hover: var(--db-color-action-default-text-hover);
18042
+ --db-tabs-text-press: var(--db-color-action-default-text-press);
18043
+ --db-tabs-text-active: var(--db-color-text-primary);
18044
+ --db-tabs-text-disabled: var(--db-color-action-disabled-text);
18045
+ /* No v2 token holds the tab panel content text color yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18046
+ --db-tabs-content-text: var(--db-color-text-primary);
18047
+ /* No v2 token holds the active-tab underline yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18048
+ --db-tabs-indicator: var(--db-color-action-primary-background-default);
18049
+ /* No v2 token holds the list divider yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18050
+ --db-tabs-border: var(--db-color-action-default-border-default);
18051
+ /* No v2 token holds the focus ring color yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18052
+ --db-tabs-focus-ring: var(--db-color-action-default-border-focus);
17536
18053
  }
17537
18054
 
18055
+ /* No v2 token holds the scrollbar thumb yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17538
18056
  :where(:root), :where(.du-bois-light) {
17539
- /* Shape */
17540
- /* TODO(FEINF-6505): adopt --db-shape-container-* once a 4px container radius exists; kept on --db-radius-sm to preserve 4px. */
17541
- --db-snackbar-border-radius: var(--db-radius-sm);
18057
+ --db-tabs-scrollbar-thumb: rgba(17, 23, 28, 0.2);
18058
+ --db-tabs-scrollbar-thumb-hover: rgba(17, 23, 28, 0.3);
18059
+ }
17542
18060
 
17543
- /* Density */
17544
- --db-snackbar-spacing-vertical: var(--db-space-3);
17545
- --db-snackbar-spacing-horizontal: var(--db-space-4);
17546
- --db-snackbar-close-button-spacing: var(--db-space-2);
17547
18061
 
17548
- /* Color */
17549
- /* No v1/v2 system token holds this theme-invariant white. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17550
- --db-snackbar-text: #ffffff;
17551
- /* No v1/v2 system token holds these theme-invariant close-icon colors. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17552
- --db-snackbar-close-icon: #e8ecf0;
17553
- --db-snackbar-close-icon-hover: #8acaff;
17554
- --db-snackbar-close-icon-press: #bae1fc;
17555
- }
17556
18062
 
17557
18063
  :where(:root), :where(.du-bois-light) {
18064
+ /* Shape */
18065
+ --db-toolbar-border-radius: var(--db-shape-control);
18066
+
17558
18067
  /* Color */
17559
- /* No v1/v2 system token holds this light/dark surface pair. TODO(FEINF-6505): adopt a v2 system token when one is added. */
17560
- --db-snackbar-surface: #11171c;
18068
+ --db-toolbar-surface: var(--db-surface-subtle);
18069
+ --db-toolbar-border: var(--db-border-base);
18070
+ --db-toolbar-separator-surface: var(--db-border-base);
18071
+ --db-toolbar-toggle-item-text: var(--db-text-base);
18072
+ /* No v2 token holds the toggle hover / press text yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
18073
+ --db-toolbar-toggle-item-text-hover: var(--db-color-action-default-text-hover);
18074
+ --db-toolbar-toggle-item-text-press: var(--db-color-action-default-text-press);
18075
+ --db-toolbar-shadow: var(--db-elevation-med);
17561
18076
 
17562
- /* Elevation */
17563
- --db-snackbar-shadow: var(--db-elevation-med);
18077
+ /* Density */
18078
+ --db-toolbar-gap: var(--db-space-4);
18079
+ --db-toolbar-spacing: var(--db-space-2);
17564
18080
  }
17565
18081
 
17566
18082
  /* Tooltip renders an inverted surface (dark chip in light mode, light chip in dark mode), so it does
@@ -17587,38 +18103,9 @@ span.du-bois-light-typography-ellipsis-single-line {
17587
18103
  /* v1 blue primitives, held as literals until a v2 link-on-tooltip semantic exists. */
17588
18104
  --db-tooltip-link-text: #4299e0; /* blue500 */
17589
18105
  --db-tooltip-link-text-hover: #8acaff; /* blue400 */
17590
- --db-tooltip-link-text-press: #bae1fc; /* blue300 */
18106
+ --db-tooltip-link-text-active: #bae1fc; /* blue300 */
17591
18107
 
17592
18108
  /* Elevation */
17593
18109
  --db-tooltip-shadow: var(--db-elevation-med);
17594
18110
  }
17595
18111
 
17596
- /* Declared under the theme classes too (not just :root) so the var() chain re-resolves inside a
17597
- * `.du-bois-dark` subtree instead of freezing at the light values. */
17598
- :where(:root), :where(.du-bois-light) {
17599
- /* Shape */
17600
- --db-progress-border-radius: var(--db-shape-pill);
17601
-
17602
- /* Color */
17603
- --db-progress-track: var(--db-color-progress-track);
17604
- --db-progress-fill: var(--db-color-progress-fill);
17605
-
17606
- /* Density */
17607
- --db-progress-size: var(--db-size-2);
17608
-
17609
- /* Motion */
17610
- /* No v2 motion token holds this duration. TODO(FEINF-6505): adopt a v2 motion token when one is added. */
17611
- --db-progress-transition-duration: 300ms;
17612
- }
17613
-
17614
- :where(:root), :where(.du-bois-light) {
17615
- /* Color */
17616
- /* No v2 token holds tertiary action-text states yet. TODO(FEINF-6505): adopt a v2 token when one lands. */
17617
- --db-overflow-trigger-text: var(--db-color-action-tertiary-text-default);
17618
- --db-overflow-trigger-text-hover: var(--db-color-action-tertiary-text-hover);
17619
- --db-overflow-trigger-text-press: var(--db-color-action-tertiary-text-press);
17620
-
17621
- /* Density */
17622
- --db-overflow-spacing: var(--db-space-2);
17623
- --db-overflow-menu-spacing: var(--db-space-1);
17624
- }