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