@karmo-co/drift-kit-reference 0.0.0-stage → 23.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +24 -2
  3. package/bin/drift-kit-ref.js +197 -0
  4. package/manifest/components/accordion.md +47 -0
  5. package/manifest/components/action-bar.md +51 -0
  6. package/manifest/components/alert.md +53 -0
  7. package/manifest/components/app-shell.md +43 -0
  8. package/manifest/components/avatar.md +42 -0
  9. package/manifest/components/body.md +48 -0
  10. package/manifest/components/box.md +58 -0
  11. package/manifest/components/breadcrumb.md +59 -0
  12. package/manifest/components/button.md +46 -0
  13. package/manifest/components/calendar.md +69 -0
  14. package/manifest/components/card.md +71 -0
  15. package/manifest/components/checkbox.md +43 -0
  16. package/manifest/components/choice-card.md +61 -0
  17. package/manifest/components/code-input.md +56 -0
  18. package/manifest/components/date-picker.md +66 -0
  19. package/manifest/components/day-of-month.md +54 -0
  20. package/manifest/components/detail-list.md +37 -0
  21. package/manifest/components/divider.md +33 -0
  22. package/manifest/components/dropdown.md +52 -0
  23. package/manifest/components/dropzone.md +56 -0
  24. package/manifest/components/eyebrow.md +45 -0
  25. package/manifest/components/feedback-badge.md +35 -0
  26. package/manifest/components/glider.md +34 -0
  27. package/manifest/components/heading.md +47 -0
  28. package/manifest/components/hero.md +46 -0
  29. package/manifest/components/icon-button.md +41 -0
  30. package/manifest/components/icon-tile.md +44 -0
  31. package/manifest/components/icon.md +30 -0
  32. package/manifest/components/input.md +63 -0
  33. package/manifest/components/link.md +42 -0
  34. package/manifest/components/loading-spinner.md +42 -0
  35. package/manifest/components/logo.md +38 -0
  36. package/manifest/components/modal.md +53 -0
  37. package/manifest/components/pagination.md +54 -0
  38. package/manifest/components/payment-form.md +29 -0
  39. package/manifest/components/price-summary.md +30 -0
  40. package/manifest/components/radio.md +42 -0
  41. package/manifest/components/section-heading.md +41 -0
  42. package/manifest/components/select.md +99 -0
  43. package/manifest/components/shield.md +34 -0
  44. package/manifest/components/sidebar.md +73 -0
  45. package/manifest/components/stepper.md +53 -0
  46. package/manifest/components/switch.md +45 -0
  47. package/manifest/components/table.md +77 -0
  48. package/manifest/components/tabs.md +43 -0
  49. package/manifest/components/tag.md +43 -0
  50. package/manifest/components/textarea.md +52 -0
  51. package/manifest/components/toast.md +52 -0
  52. package/manifest/components/tooltip.md +42 -0
  53. package/manifest/components/top-bar.md +60 -0
  54. package/manifest/components/vehicle.md +43 -0
  55. package/manifest/index.json +1171 -0
  56. package/manifest/migrations/1.0.0.md +50 -0
  57. package/manifest/migrations/10.0.0.md +154 -0
  58. package/manifest/migrations/11.0.0.md +24 -0
  59. package/manifest/migrations/12.0.0.md +38 -0
  60. package/manifest/migrations/13.0.0.md +50 -0
  61. package/manifest/migrations/14.0.0.md +148 -0
  62. package/manifest/migrations/15.0.0.md +140 -0
  63. package/manifest/migrations/16.0.0.md +196 -0
  64. package/manifest/migrations/17.0.0.md +19 -0
  65. package/manifest/migrations/18.0.0.md +23 -0
  66. package/manifest/migrations/19.0.0.md +15 -0
  67. package/manifest/migrations/2.0.0.md +18 -0
  68. package/manifest/migrations/20.0.0.md +43 -0
  69. package/manifest/migrations/21.0.0.md +97 -0
  70. package/manifest/migrations/22.0.0.md +17 -0
  71. package/manifest/migrations/23.0.0.md +86 -0
  72. package/manifest/migrations/3.0.0.md +34 -0
  73. package/manifest/migrations/4.0.0.md +147 -0
  74. package/manifest/migrations/5.0.0.md +73 -0
  75. package/manifest/migrations/6.0.0.md +67 -0
  76. package/manifest/migrations/7.0.0.md +71 -0
  77. package/manifest/migrations/8.0.0.md +16 -0
  78. package/manifest/migrations/9.0.0.md +107 -0
  79. package/manifest/rules.md +37 -0
  80. package/manifest/setup.md +70 -0
  81. package/manifest/tokens/border-width.md +12 -0
  82. package/manifest/tokens/border.md +147 -0
  83. package/manifest/tokens/font.md +90 -0
  84. package/manifest/tokens/icon.md +84 -0
  85. package/manifest/tokens/interactive.md +53 -0
  86. package/manifest/tokens/layout.md +8 -0
  87. package/manifest/tokens/motion.md +97 -0
  88. package/manifest/tokens/radius.md +20 -0
  89. package/manifest/tokens/shadow.md +28 -0
  90. package/manifest/tokens/spacing.md +35 -0
  91. package/manifest/tokens/surface.md +138 -0
  92. package/manifest/tokens/text.md +92 -0
  93. package/package.json +20 -4
@@ -0,0 +1,196 @@
1
+ ## 16.0.0 - 2026-09-22
2
+
3
+ - **Select's keyboard exists.** Until now the arrow keys opened the menu and
4
+ then nothing worked: ArrowUp, ArrowDown, Home and typing all left focus on the
5
+ trigger with no way to reach an option, and Tab walked out of the page while
6
+ the menu stayed open. There was no way to choose a value without a mouse. It
7
+ is the APG's select-only combobox now, with focus staying on the trigger and
8
+ `aria-activedescendant` naming the highlighted option:
9
+
10
+ ```text
11
+ ArrowDown / ArrowUp open, or move the active option
12
+ Alt+ArrowDown open without moving
13
+ Home / End first / last
14
+ PageDown / PageUp ten at a time
15
+ Enter / Space choose
16
+ Escape close, keep the value
17
+ Tab close and let the page have it
18
+ any printable key typeahead, 700ms buffer, cycles, skips disabled
19
+ ```
20
+
21
+ Opening lands on the CURRENT SELECTION, which is where a reader expects to be
22
+ and what a native select does. Hovering moves the active option rather than
23
+ painting a second highlight, so the pointer and the keyboard never light two
24
+ different rows.
25
+ - **Forge's `check:contract` asserts the keyboard**, for any component that
26
+ declares one: open with a key, move with a key, choose with a key, and leave
27
+ the value alone on Escape. The old behaviour passed every gate in the repo
28
+ while being unusable, which is the whole argument for adding it. Verified by
29
+ breaking the arrow keys on purpose and watching it fail.
30
+ - **Its own token collection: 74 where there were 12.** Eight of the twelve
31
+ pointed straight at `--dk-component-input-*` and the stylesheet read Input's
32
+ collection another eleven times, which is component-to-component, a tier the
33
+ kit does not have, and the same fault Input's label was moved off
34
+ `--dk-component-eyebrow-*` for. The rungs are derived from the same primitives
35
+ by the same arithmetic rather than quoted, so both move together, and
36
+ `check:contract`'s field cohort is what asserts a select and a text field
37
+ still paint the same box.
38
+ - **BREAKING: `.dk-component-select--disabled` is gone.** The last BEM-style
39
+ modifier in a kit that uses attributes everywhere else, on a component whose
40
+ trigger is a real `<button>`. It was worse than untidy: it painted a trigger
41
+ without disabling it, so a select that looked locked still opened, still took
42
+ focus and still changed its value. Use the native `disabled` attribute on
43
+ `.dk-component-select__trigger`.
44
+ - **The field grammar it never had**: a label, a caption, an error with
45
+ `aria-invalid`, and the required asterisk, so a select and a text field in one
46
+ form can say the same things.
47
+ - **The menu can carry more**: option groups under real headings (a labelled
48
+ `role="group"`, which the keyboard walks past), a second line on an option, a
49
+ disabled option that is actually skipped, and an empty state for a menu with
50
+ nothing in it.
51
+ - **The floating surface is named for the shape, not the component.**
52
+ `--dk-component-menu-*` replaces `--dk-component-select-menu-radius` and
53
+ `-shadow` and picks up the literals beside them. Forge hand-rolls this box
54
+ five times over (seven `role="menu"` popups across the prototypes, the card
55
+ actions menu, the icon picker), and a Dropdown built later is menu semantics
56
+ on a surface that already exists rather than a second stylesheet that drifts.
57
+ - **On a phone the menu is a bottom sheet, by default and not by request.**
58
+ Below the kit's mobile breakpoint it stops being a dropdown: full width, off
59
+ the bottom edge where the thumb already is, with a scrim, a heading taken from
60
+ the label, 44px rows, and the page behind it locked from scrolling. A dropdown
61
+ anchored to a 100px trigger on a 375px screen is the worst of both, flipping
62
+ when there is no room below and clipping against the viewport.
63
+
64
+ It is not an option because a design system's defaults are what ship: an
65
+ opt-in would mean most call sites never opt in and mobile users keep the
66
+ cramped menu. `data-sheet="never"` is the escape hatch for the one case that
67
+ is genuinely wrong, a select inside something that is already a sheet or a
68
+ full-screen modal, and `data-sheet="always"` forces it at any width, which is
69
+ how the docs demonstrate it on a desktop.
70
+
71
+ THE BREAKPOINT IS DECLARED ONCE, in the stylesheet, and the behaviour reads it
72
+ back as a custom property when the menu opens. A media query cannot switch
73
+ JavaScript on, and a width hardcoded in both languages drifts the first time
74
+ one of them moves.
75
+ - **The sheet leaves the way it arrived.** It slid up and then vanished on the
76
+ frame the selection landed, which reads as the screen glitching rather than as
77
+ a panel closing: the thing you just touched is gone before you have finished
78
+ touching it. It slides out now, faster than it came in, and the scrim fades
79
+ with it, because the scrim is the sheet's own shadow and fading the element
80
+ fades the dim in one gesture. `aria-expanded` flips to false immediately: the
81
+ state a screen reader reads should not wait on an animation.
82
+ - **A sheet on its way out answers "closed" to everything that asks.** Tapping
83
+ the trigger mid-exit was swallowed, because the click handler asked whether
84
+ the menu was open, got true from an attribute that is still set while the
85
+ sheet slides, and called close, which returned since it was already leaving.
86
+ Tapping something and having nothing happen is the worst answer available.
87
+ Now the exit is cancelled and the sheet comes back up rather than a second one
88
+ animating in behind it.
89
+ - **The scrim is a box-shadow, not an element**: a 100vmax spread paints the
90
+ whole viewport in one declaration, so there is no node to create, none to
91
+ leak, and nothing to clean up if a page tears the menu out from under us. It
92
+ is not a hit target and does not need to be, since the outside-click handler
93
+ that closes the menu was already listening on the document.
94
+ - **New: `--dk-surface-backdrop-soft`**, 40% black, beside upstream's 80%.
95
+ A modal is the page for as long as it is open and a backdrop that heavy says
96
+ so; a select's sheet is a menu that happens to be full width, and dimming the
97
+ form behind it to black takes away the context you are choosing for. Declared
98
+ below the upstream fence, in both themes.
99
+ - **The highlight belongs to whichever input moved last.** Hovering an option
100
+ and then moving the pointer out of the menu left the row lit: one rule painted
101
+ both the hovered option and the ACTIVE one, and the active one is the
102
+ keyboard's cursor, which must exist the whole time the menu is open because
103
+ `aria-activedescendant` has to point at something. So the pointer's position
104
+ became permanent. The menu records `data-nav` now: on `pointer` the `:hover`
105
+ paints and stops the moment the pointer leaves, with the active option still
106
+ set silently for the screen reader and the next keypress; on `keyboard` the
107
+ active option paints and `:hover` is suppressed, or arrowing past a row the
108
+ pointer happens to be resting on would light two at once. A menu opened by
109
+ click starts in pointer mode and paints nothing until you are over a row.
110
+ - **The options are inset and rounded**, rather than running edge to edge. The
111
+ menu carries the inset, `--dk-component-menu-padding-inline`, and the option
112
+ carries the rest, and the two are chosen so their SUM is unchanged: nothing
113
+ moves but the highlight, which now stops short of the menu's edge and rounds.
114
+ The radius is concentric rather than chosen by eye, `--dk-border-radius-100`
115
+ against the menu's `-300` across a `spacing-2` inset, so the two curves sit
116
+ parallel; a rounder option looks deliberate on its own and wrong inside the
117
+ menu's own corner. The group headings keep their text on the option text's
118
+ line.
119
+ - **`data-raised` on the trigger**, the same attribute Input carries and the
120
+ same trade: the shadow is the whole of it, since padding belongs to the size
121
+ rung. It stacks with the focus ring rather than replacing it, in both of the
122
+ states that draw one, focused and open, because a select has two where a field
123
+ has one. Readable on the root or on the trigger, so a lone trigger can be
124
+ raised too, and the forced states carry the lift so the docs' state control
125
+ does not show a raised select losing its shadow on hover. React takes
126
+ `raised`.
127
+ - **The type rides with the box: 14 / 16 / 18**, on the same rungs a field
128
+ reads. The trigger was a rung low at small and medium, so a select and a text
129
+ field in one form were the same height and not the same type; the heights
130
+ matching all along is what let it go unseen, since the box arithmetic reads a
131
+ line-height rung and the value reads a text rung, and only one of the two was
132
+ Input's. And an option is the text you are about to get, so it is set at the
133
+ rung the trigger will show it at: options were fixed at 16/24 at every size, a
134
+ rung larger than the trigger's value with four more pixels of leading, so a
135
+ 14px control opened a 16px list. The menu is portalled, so `init()` mirrors
136
+ `data-size` onto it the way `dk.scope.adopt` mirrors the token legs.
137
+ - **The menu's width is its own, not the trigger's.** It took the trigger's
138
+ width outright, which reads fine on a 320px field and badly everywhere else:
139
+ Forge's own control rail opens 100px selects, and a 100px menu truncates every
140
+ option in it. A trigger only ever holds one line, so it is a poor guide to how
141
+ much room a list needs. The rule is now a floor, a fit and a cap: never
142
+ narrower than its trigger, as wide as its widest option needs,
143
+ `--dk-component-menu-max-width` (328px) and the viewport behind it.
144
+ - **`dk.position` gains `align`**, and finding that it needed one is the second
145
+ half of the same story. Alignment was inferred from `matchWidth`, which worked
146
+ only because a menu exactly as wide as its trigger centres and start-aligns to
147
+ the same number. The moment the menu could be wider, a 152px menu on a 117px
148
+ trigger sat 17px to the left of it. `align: 'start' | 'center' | 'end'` is
149
+ explicit now and `matchWidth` means only what its name says; Tooltip keeps the
150
+ centred default.
151
+ - **React `Select`**, which did not exist: the docs' TSX pointed at
152
+ `@karmo-co/component-library`, a different product. Uncontrolled by design,
153
+ because the menu is built and mutated by `behaviour.js` in DOM React does not
154
+ own; `defaultValue` seeds it and `onChange` reports it.
155
+
156
+ ### Rename map: `migrations/16.0.0-select-keyboard.json`
157
+
158
+ ```json
159
+ {
160
+ "version": "16.0.0",
161
+ "summary": "Select gains the keyboard it never had, its own token collection, and the field grammar (label, caption, error, required). The only breaking change is the removal of .dk-component-select--disabled.",
162
+ "breaking": {
163
+ "dk-component-select--disabled": {
164
+ "replacement": "the native disabled attribute on .dk-component-select__trigger",
165
+ "why": "It painted a trigger without disabling it, so a select that looked locked still opened, still took focus and still changed its value. It was also the last BEM-style modifier in a kit that uses attributes everywhere else.",
166
+ "before": "<div class=\"dk-component-select dk-component-select--disabled\">…<button class=\"dk-component-select__trigger\">…",
167
+ "after": "<div class=\"dk-component-select\">…<button class=\"dk-component-select__trigger\" disabled>…",
168
+ "javascript": "root.classList.toggle('dk-component-select--disabled', locked) → root.querySelector('.dk-component-select__trigger').disabled = locked"
169
+ }
170
+ },
171
+ "rename": {
172
+ "--dk-component-select-menu-radius": "--dk-component-menu-radius",
173
+ "--dk-component-select-menu-shadow": "--dk-component-menu-shadow"
174
+ },
175
+ "unchanged": {
176
+ "markup": "Every class is the same and every existing select keeps working: the root, __trigger, __value, __chevron, __menu and __option all mean what they meant, and the hidden input still carries the value. role=\"combobox\", aria-controls, aria-activedescendant and the option ids are written by init(), so markup from before 16.0.0 gets the keyboard without being touched.",
177
+ "api": "root.__dcSelect keeps setOptions / select / value / open / close, and the bubbling dk-component-select:change CustomEvent keeps its detail { value }. setOptions gained three optional fields per option: description, group and disabled.",
178
+ "sizes": "data-size=\"small|large\" mean what they meant, 38 and 52 against the unmarked 44."
179
+ },
180
+ "added": {
181
+ "keyboard": "Arrows, Home, End, PageUp, PageDown, Enter, Space, Escape, Tab and typeahead. Focus stays on the trigger; aria-activedescendant names the active option.",
182
+ "dk-component-select__label / __caption / __error": "The field grammar. Pair the error with aria-invalid=\"true\" on the trigger and aria-describedby pointing at the message.",
183
+ "dk-component-select__group / __group-label": "Options under a heading. The heading is a labelled role=\"group\", not an option, so the keyboard walks past it.",
184
+ "dk-component-select__option-text / __option-desc": "A second line on an option. The trigger shows the label alone.",
185
+ "dk-component-select__empty": "What a menu with nothing in it says.",
186
+ "--dk-component-menu-*": "The floating surface, named for the shape rather than for this component so a Dropdown can share it.",
187
+ "React Select": "import { Select } from '@karmo-co/drift-kit/react'. Uncontrolled: defaultValue seeds it, onChange reports it.",
188
+ "--dk-component-menu-max-width / --dk-component-menu-viewport-gap": "The menu sizes to its content now, floored by the trigger's width and capped by these two. It used to take the trigger's width outright, so a narrow select opened a narrow menu that truncated every option in it.",
189
+ "dk.position({ align })": "'start' | 'center' | 'end'. Alignment used to be inferred from matchWidth, which only ever agreed with the intent because the menu was exactly as wide as its trigger. Tooltip keeps the centred default.",
190
+ "data-raised": "The trigger lifted onto an elevation, the same attribute Input carries. The shadow is the whole of it: padding and height still come from the size rung. It stacks with the focus ring rather than replacing it.",
191
+ "data-sheet": "auto | always | never. On a phone the menu is a bottom sheet by default: full width, off the bottom edge, with a scrim, a heading, 44px rows and the page locked from scrolling. 'never' opts out for a select inside something that is already a sheet; 'always' forces it at any width. Nothing to change in existing markup: the default is the new behaviour.",
192
+ "--dk-surface-backdrop-soft": "40% black, beside upstream's 80% --dk-surface-backdrop-black. A sheet dims the page without owning it the way a modal does."
193
+ }
194
+ }
195
+ ```
196
+
@@ -0,0 +1,19 @@
1
+ ## 17.0.0 - 2026-09-24
2
+
3
+ ### Migration
4
+
5
+ Two renames, both on Modal, and both mechanical:
6
+
7
+ - **`data-size` on `.dk-component-modal__box`: `sm` / `md` / `lg` become
8
+ `small` / `medium` / `large`**, and `xlarge` joins them at 800px. Every other
9
+ component in the kit says small / medium / large; the modal was the one that
10
+ did not. `medium` is still the unmarked default, so a box with no `data-size`
11
+ is unaffected.
12
+ - **`.is-closing` on the overlay becomes `[data-closing]`.** It was the last
13
+ state-carrying class in a kit that spells every other state as an attribute.
14
+ Anything that ADDS the class to play the exit, or reads it to know a dialog is
15
+ leaving, has to move; `dk.component.modal.open()` and `.close()` already do.
16
+ The keyframe names are deliberately unchanged, because an animation's name is
17
+ an identifier consumers observe (`animationName === "dk-component-modal-slide-out"`),
18
+ and renaming it would break that silently.
19
+
@@ -0,0 +1,23 @@
1
+ ## 18.0.0 - 2026-10-01
2
+
3
+ ### Migration
4
+
5
+ - **Table's loading row uses the Loading spinner.** `.dk-component-table__spinner`
6
+ and its tokens (`spinner-size`, `spinner-track`, `spinner-head`,
7
+ `spinner-duration`) are gone; put a
8
+ `<div class="dk-component-loading-spinner" data-variant="ring" data-size="small" data-animation="enter">`
9
+ with a `__message` in the loading cell. React `loadingLabel` is now a string.
10
+ - **Table's `sort-icon-color-active` token now means pressed**, not sorted; the
11
+ sorted colour is `sort-icon-color-sorted`.
12
+ - **Dropzone's root is a container now.** The `<label>` that was the root
13
+ becomes `label.dk-component-dropzone__area` inside a
14
+ `div.dk-component-dropzone`, which can also hold a field label, a file list,
15
+ a caption and an error. Wrap the old markup in the div and rename the
16
+ label's class. `root.__dcDropzone.reset()` and `refresh()` keep working.
17
+ - **Tag's `data-pill` (React `pill`) is removed.** A tag's corner now always
18
+ comes from the theme's `data-border-radius` (sharp, normal, bubbly), like
19
+ every other component's. Delete the attribute or prop; nothing replaces it.
20
+ Under bubbly a tag is already nearly round, and a filter chip that must look
21
+ different will be its own clickable component. This is breaking, so the next
22
+ release is a major.
23
+
@@ -0,0 +1,15 @@
1
+ ## 19.0.0 - 2026-10-06
2
+
3
+ ### Migration
4
+
5
+ - **Stepper's `data-labels="beside"` is removed** (React `labels` is now
6
+ `'visible' | 'hidden'`). Each variant puts its labels where they read best:
7
+ beside the number for `data-variant="inline"`, below the indicator for the
8
+ rest. For the inline look, use `data-variant="inline"`; `data-labels="hidden"`
9
+ is unchanged.
10
+ - **Stepper's narrow summary line is removed** (`.dk-component-stepper__summary`
11
+ and `messages.summary`). On a narrow stepper the current step now keeps its
12
+ own words in place instead; see Changes.
13
+ - **Stepper's `--dk-component-stepper-connector-gap` token is removed**:
14
+ connectors now touch the indicators in both orientations.
15
+
@@ -0,0 +1,18 @@
1
+ ## 2.0.0 — 2026-09-09
2
+
3
+ - The `dk` vocabulary: every token is `--dk-…`, tier words `primitive` /
4
+ `responsive` / `dynamic` / `component`, classes `.dk-component-{name}`, JS
5
+ namespace `dk.component.{name}.{fn}`. Component-library / Figma name parity
6
+ dropped by decision; `refresh:tokens`, `check:upstream` and the upstream
7
+ parity test removed. Map in `migrations/2.0.0-dk-vocabulary.json`.
8
+ - Components added since 1.0.0: body, heading, hero, eyebrow, icon (with the
9
+ Karmo glyph runtime); surface levels and tone scopes (`data-surface`,
10
+ `data-tone`, `data-tone-fill`); fixed Karmo alias rungs for body, overline and
11
+ input type.
12
+
13
+ ### Rename map: `migrations/2.0.0-dk-vocabulary.json`
14
+
15
+ Token names before → after the dk vocabulary. Classes: dc-{x} → dk-component-{x}. JS: window.dc{X} → dk.component.{x}.{fn}.
16
+
17
+ The full map is `migrations/2.0.0-dk-vocabulary.json` in the drift-kit repo.
18
+
@@ -0,0 +1,43 @@
1
+ ## 20.0.0 - 2026-10-12
2
+
3
+ ### Migration
4
+
5
+ - **Tabs is now the underline tabs; the old Tabs is Segmented control.** The
6
+ sliding pill in a raised frame (`.dk-component-tabs` > `.dk-component-tab`
7
+ + `.dk-component-tab-indicator`) is `.dk-component-segmented` > `__option`
8
+ + `__indicator`, with radio semantics (`role="radiogroup"`, `role="radio"`,
9
+ `aria-checked`, written by the runtime); `.active` is no longer read. Its
10
+ tokens are `--dk-component-segmented-*`, its runtime
11
+ `dk.component.segmented`, its event `dk-component-segmented:change`
12
+ `{ value, values, option }`. The full map is
13
+ `migrations/20.0.0-tabs-to-segmented.json`. React `Tabs` keeps its name and
14
+ props but renders the new tabs; use `SegmentedControl` for the old look.
15
+
16
+ ### Rename map: `migrations/20.0.0-tabs-to-segmented.json`
17
+
18
+ ```json
19
+ {
20
+ "version": "20.0.0",
21
+ "summary": "Tabs is now the underline tabs Forge's design system pages use (with a pill variant); the old Tabs, a sliding pill in a raised frame, is now Segmented control, with radio semantics, and gains multiple selection, icon-only options and full width.",
22
+ "rename": {
23
+ ".dk-component-tabs (with .dk-component-tab children)": ".dk-component-segmented",
24
+ ".dk-component-tab": ".dk-component-segmented__option",
25
+ ".dk-component-tab-indicator": ".dk-component-segmented__indicator",
26
+ ".dk-component-tab-ico": ".dk-component-segmented__icon",
27
+ "--dk-component-tabs-*": "--dk-component-segmented-*",
28
+ "dk.component.tabs (the slider)": "dk.component.segmented",
29
+ "dk-component-tabs:change { index, tab }": "dk-component-segmented:change { value, values, option }"
30
+ },
31
+ "semantics": {
32
+ "role=\"tablist\" + role=\"tab\" + aria-selected": "role=\"radiogroup\" + role=\"radio\" + aria-checked (the runtime writes them). A strip that switched panels keeps doing so: aria-controls on an option shows its panel.",
33
+ ".active": "no longer read or written; aria-checked is the state"
34
+ },
35
+ "added": {
36
+ ".dk-component-tabs": "New: a tablist of .dk-component-tabs__tab (with __icon, __label, __count), an underline by default and data-variant=\"pill\", data-size, data-full-width, data-activation=\"manual\"; the runtime adds the __indicator.",
37
+ ".dk-component-segmented[data-multiple]": "Several chosen: a group of toggle buttons with aria-pressed, each pressed option on its own pill.",
38
+ "React": "Tabs keeps its name and props and renders the new tabs (adds variant, count, fullWidth, activation); SegmentedControl is new (options, multiple, value/defaultValue/onChange, size, fullWidth)."
39
+ },
40
+ "migrate": "Replace the classes as in rename. Leave role, aria-selected and tabindex to the runtime, or write role=\"radio\" with aria-checked yourself."
41
+ }
42
+ ```
43
+
@@ -0,0 +1,97 @@
1
+ ## 21.0.0 - 2026-10-14
2
+
3
+ ### Migration
4
+
5
+ - **Segmented control is now Glider.** `.dk-component-segmented` and its
6
+ parts are `.dk-component-glider`, `__option`, `__indicator`, `__icon` and
7
+ `__label`; its tokens are `--dk-component-glider-*`, its runtime
8
+ `dk.component.glider`, its event `dk-component-glider:change`, and React's
9
+ `SegmentedControl` is `Glider` (types `GliderProps`, `GliderOption` and so
10
+ on). Nothing else changes. The map is
11
+ `migrations/21.0.0-segmented-to-glider.json`.
12
+ - **Tabs has no pill variant.** Tabs is the underline alone:
13
+ `data-variant="pill"` and React's `variant` are gone, with the pill's
14
+ tokens (`--dk-component-tabs-pill-*`, `-count-on-pill-surface`); the
15
+ count's corner is `--dk-component-tabs-count-radius`. A pill look that
16
+ picks a value, one or several, is the Glider's. The map is
17
+ `migrations/21.0.0-tabs-pill-removed.json`.
18
+ - **Motion is a scale and patterns.** Seven durations became four (quick 120,
19
+ brisk 200, moderate 320, slow 480) and five easings four (standard,
20
+ emphasized, exit, spring). Components read patterns: state, press, travel,
21
+ enter, enter-large, exit, exit-large, expand, feedback, loop and cascade.
22
+ The old names are removed; the map is `migrations/21.0.0-motion-scale.json`.
23
+
24
+ ### Rename map: `migrations/21.0.0-motion-scale.json`
25
+
26
+ ```json
27
+ {
28
+ "version": "21.0.0",
29
+ "summary": "Motion is four durations, four easings and the patterns components read (13 Oct: snappy and fluid). Read a pattern by its job; the scale names below map to the nearest step, but a component should move to a pattern, not to the step.",
30
+ "rename": {
31
+ "--dk-motion-duration-snap": "--dk-motion-duration-quick (or --dk-motion-state-duration)",
32
+ "--dk-motion-duration-fast": "--dk-motion-duration-quick (or --dk-motion-state-duration)",
33
+ "--dk-motion-duration-default": "--dk-motion-duration-brisk (or --dk-motion-press-duration)",
34
+ "--dk-motion-duration-slower": "--dk-motion-duration-slow (or --dk-motion-enter-large-duration)",
35
+ "--dk-motion-duration-slowest": "--dk-motion-duration-slow (or --dk-motion-enter-large-duration)",
36
+ "--dk-motion-easing-in-out": "--dk-motion-easing-standard (or --dk-motion-state-easing)",
37
+ "--dk-motion-easing-out": "--dk-motion-easing-standard (or --dk-motion-state-easing)",
38
+ "--dk-motion-easing-ease": "--dk-motion-easing-standard (or --dk-motion-state-easing)",
39
+ "--dk-primitive-motion-duration-125": "--dk-primitive-motion-duration-120",
40
+ "--dk-primitive-motion-duration-150": "--dk-primitive-motion-duration-120",
41
+ "--dk-primitive-motion-duration-250": "--dk-primitive-motion-duration-200",
42
+ "--dk-primitive-motion-duration-300": "--dk-primitive-motion-duration-320",
43
+ "--dk-primitive-motion-duration-400": "--dk-primitive-motion-duration-480",
44
+ "--dk-primitive-motion-duration-500": "--dk-primitive-motion-duration-480",
45
+ "--dk-primitive-motion-easing-in-out": "--dk-primitive-motion-easing-standard",
46
+ "--dk-primitive-motion-easing-out": "--dk-primitive-motion-easing-standard",
47
+ "--dk-primitive-motion-easing-ease": "--dk-primitive-motion-easing-standard"
48
+ },
49
+ "changed": {
50
+ "--dk-motion-duration-moderate": "250ms -> 320ms",
51
+ "--dk-motion-duration-slow": "300ms -> 480ms",
52
+ "reduced motion": "fades at 120 to 160ms instead of cutting to 0.01ms"
53
+ },
54
+ "migrate": "Inside drift-kit every component already reads patterns."
55
+ }
56
+ ```
57
+
58
+ ### Rename map: `migrations/21.0.0-segmented-to-glider.json`
59
+
60
+ ```json
61
+ {
62
+ "version": "21.0.0",
63
+ "summary": "Segmented control is renamed Glider (13 Oct). Same markup, tokens, behaviour and semantics under the new name.",
64
+ "rename": {
65
+ ".dk-component-segmented": ".dk-component-glider",
66
+ ".dk-component-segmented__option": ".dk-component-glider__option",
67
+ ".dk-component-segmented__indicator": ".dk-component-glider__indicator",
68
+ ".dk-component-segmented__icon": ".dk-component-glider__icon",
69
+ ".dk-component-segmented__label": ".dk-component-glider__label",
70
+ "--dk-component-segmented-*": "--dk-component-glider-*",
71
+ "dk.component.segmented": "dk.component.glider",
72
+ "dk-component-segmented:change": "dk-component-glider:change",
73
+ "React SegmentedControl": "React Glider",
74
+ "React SegmentedControlProps, SegmentedSingleProps, SegmentedMultipleProps, SegmentedOption, SegmentedSize": "GliderProps, GliderSingleProps, GliderMultipleProps, GliderOption, GliderSize"
75
+ },
76
+ "migrate": "Replace the names as in rename; nothing else changes."
77
+ }
78
+ ```
79
+
80
+ ### Rename map: `migrations/21.0.0-tabs-pill-removed.json`
81
+
82
+ ```json
83
+ {
84
+ "version": "21.0.0",
85
+ "summary": "Tabs loses its pill variant (13 Oct): tabs are the underline alone, and a pill look that picks a value is the Glider's.",
86
+ "removed": {
87
+ "[data-variant=\"pill\"] on .dk-component-tabs": "Gone. Tabs that switch panels drop the attribute and become the underline; a pill strip that picks a value becomes a .dk-component-glider (data-multiple for several).",
88
+ "React Tabs variant, TabsVariant": "Gone; drop the prop.",
89
+ "--dk-component-tabs-pill-gap, -pill-height, -pill-small-height, -pill-large-height, -pill-padding-inline, -pill-small-padding-inline, -pill-large-padding-inline, -pill-surface, -pill-selected-color, -pill-surface-hover, -count-on-pill-surface": "Gone with the variant."
90
+ },
91
+ "rename": {
92
+ "--dk-component-tabs-pill-radius": "--dk-component-tabs-count-radius"
93
+ },
94
+ "migrate": ""
95
+ }
96
+ ```
97
+
@@ -0,0 +1,17 @@
1
+ ## 22.0.0 - 2026-10-15
2
+
3
+ ### Migration
4
+
5
+ - **Breadcrumb's separator is a slash, drawn as text.**
6
+ `--dk-component-breadcrumb-separator-icon` is gone; the separator's colour
7
+ is still `--dk-component-breadcrumb-separator-color`. Past pages are now
8
+ subtle neutral pills rather than primary links, and an item with a menu
9
+ wraps its link (or current page) and its `__more` in `span.__pill`. The
10
+ old markup still renders; it only lacks the joined pair.
11
+ - **Edged buttons are 2px smaller.** Plain, raised and outline Buttons and
12
+ Icon buttons now take their border out of their padding, so they are the
13
+ size borderless ones are (44px at medium, was 46). A layout that measured
14
+ around the old size moves by that much.
15
+ - **App shell sits on white.** `--dk-component-app-shell-surface` is the page
16
+ surface, not the grey canvas.
17
+
@@ -0,0 +1,86 @@
1
+ ## 23.0.0 - 2026-10-17
2
+
3
+ ### Breaking
4
+
5
+ - **Box has five sizes** (16 Oct): `xsmall` (12), `small` (18), `medium`
6
+ (24, the default), `large` (32) and `xlarge` (40). Past medium the corner
7
+ and the header grow too (radius 300 and 400, an h5 and an h4 title over a
8
+ body-md description). The tokens moved with the names:
9
+ `--dk-component-box-padding` is now 24, `-small-padding` 18 and
10
+ `-large-padding` 32; `-xsmall-*`, `-xlarge-*`, `-large-radius`,
11
+ `-xlarge-radius` and the large and xlarge title and description tokens are
12
+ new.
13
+
14
+ ### Migration
15
+
16
+ | Was | Now, for the same box |
17
+ |---|---|
18
+ | `data-size="small"` (`size="small"`) | `data-size="xsmall"` |
19
+ | no `data-size` (`size="medium"`) | `data-size="small"` |
20
+ | `data-size="large"` (`size="large"`) | no `data-size` (`size="medium"`) |
21
+
22
+ An override of `--dk-component-box-padding`, `-small-padding` or
23
+ `-large-padding` moves the same way.
24
+
25
+ ### Behaviour changes
26
+
27
+ - **Glider labels a rung smaller, with more air** (17 Oct): small, medium and
28
+ large labels are 10, 12 and 14px (were 12, 14, 16), the padding beside them
29
+ 12, 16 and 20px (were 8, 12, 16), and the icons 12, 14 and 16px (were 14,
30
+ 16, 20). Heights are unchanged (36, 44, 52). An option is wider than it was.
31
+
32
+ - **Card is rebuilt** (16 Oct). Same markup (`__media`, `__body`, `__title`,
33
+ `__subtitle` still render), new look and reach:
34
+ - `data-variant` raised (default), outline, flat; `data-size` small,
35
+ medium, large; `data-orientation="horizontal"`; `data-media="inset"`;
36
+ `__media` takes `data-ratio` (16/9 default, 1/1, 4/3, 3/2, 21/9) and
37
+ `data-fit="contain"`; new `__eyebrow`, `__meta`, one full-width `__cta`,
38
+ and `__corner[data-corner="start|end"]` for buttons over the top corners
39
+ (a heart toggle that fills when pressed, a Dropdown menu); with either,
40
+ a `__link` in the title stretches over the card. `data-corners="hover"`
41
+ fades the corners in under the pointer or the focus (a pressed toggle and
42
+ an open menu stay shown; a screen that cannot hover always shows them);
43
+ shown always on a card with no media, they take a row above the words,
44
+ and beside the body both sit on the media.
45
+ - It paints the kit surface, so `data-tone`, `data-tone-fill` and
46
+ `data-surface` drive it, and hover and press climb that surface's
47
+ control rungs. It is no longer a tone boundary: on a solid or tinted
48
+ panel it takes the panel's colours, as Box does. `data-surface="0"`
49
+ gives it the page back.
50
+ - Surface tier: grows on hover, presses on the surface rung. Only a link,
51
+ a button, a tab stop or a stretched card moves; a static card is still.
52
+ - The corner is radius 300 (was a literal 14px), the media is 16/9 (was
53
+ 132px), the title body-md (was 15px), and the subtitle wraps to two
54
+ lines (was one) through `--dk-component-card-subtitle-lines`.
55
+ - Selected (`aria-pressed`, `aria-current`) wears the primary edge;
56
+ `aria-disabled` stills it and dims it; the focus ring is an outline.
57
+ - React `Card` gains variant, size, orientation, tone, toneFill, surface,
58
+ eyebrow, meta, cta, cornerStart, cornerEnd, mediaRatio, mediaFit,
59
+ mediaInset, selected and disabled.
60
+ - **Two control press rungs** (16 Oct): text-width controls (Button, Glider,
61
+ Tabs, Breadcrumb and Top bar links, the phone sidebar tabs) press to 0.97;
62
+ small squares take the new compact rung, 0.93 (Icon button, Pagination
63
+ pages, Calendar days, Day of month cells), with `--dk-interactive-press-compact`
64
+ and `data-interactive="compact"`. Accordion headers press on the panel rung
65
+ (0.99): full width, they lurched at the item rung.
66
+ - **The quiet ramp follows the tone.** `--dk-interactive-surface-hover` and
67
+ `-active` and the nested fill read the control fills: the same greys on a
68
+ plain surface, the tone's and the level's next rung on a toned one.
69
+ - **Words step a rung per state** on Breadcrumb, Sidebar, Pagination, Glider,
70
+ Tabs and Top bar links (they rest a step lighter); Dropdown and Select menu
71
+ words stay full strength. On a solid tone the quiet word ramp steps to the
72
+ tone's white.
73
+ - **Presses:** Dropdown and Select options, Sidebar items and the Action bar
74
+ summary press on the item rung; Breadcrumb and Top bar links on the control
75
+ rung, Pagination, Calendar and Day of month cells on the compact rung (see
76
+ above); Choice card grows on hover.
77
+ - **Selected and highlighted:** the Sidebar's current item, Pagination's
78
+ current page and a picked day read the foundation's selected; a checked
79
+ Table row its highlighted tint.
80
+ - **Sidebar** items press a rung past hover and hover with the fill alone.
81
+ - **Accordion** sits on the surface it is on, and **Dropzone** on the control
82
+ fill, so both follow the tone, the level and the fill; Dropzone's hover,
83
+ press, chosen-file and focus states follow a status tone too.
84
+ - **Inset Glider** words go full strength inside a status tone, as inset
85
+ Breadcrumb's do; an inset Select chevron keeps its tone colour on hover.
86
+
@@ -0,0 +1,34 @@
1
+ ## 3.0.0 — 2026-09-09
2
+
3
+ ### Breaking
4
+
5
+ - `data-theme` values are lowercase: `data-theme="light"` / `data-theme="dark"`,
6
+ matching every other Drift attribute (`data-surface`, `data-tone`,
7
+ `data-density`, `data-border-radius`, `data-border-width`). `Light` / `Dark`
8
+ no longer match any selector. Kit selectors are normalised to the quoted form
9
+ `[data-theme="light"]` (238 selectors; the 3 unquoted `[data-alignment=…]`
10
+ selectors are quoted too). Token values are unchanged — 1,542 tokens across
11
+ six theme/surface/tone scopes compare equal between 2.0.0 with `Light`/`Dark`
12
+ and 3.0.0 with `light`/`dark`.
13
+ - Consumers: set `<body data-theme="light|dark">`, lowercase any stored theme
14
+ preference on read, and update your own `[data-theme=Light|Dark]` selectors
15
+ and `=== 'Light'` / `'Dark'` comparisons. Map in
16
+ `migrations/3.0.0-data-theme.json`.
17
+
18
+ ### Rename map: `migrations/3.0.0-data-theme.json`
19
+
20
+ ```json
21
+ {
22
+ "version": "3.0.0",
23
+ "date": "2026-09-09",
24
+ "summary": "data-theme values are lowercase like every other Drift attribute. Selectors in the kit are quoted [data-theme=\"light\"] / [data-theme=\"dark\"].",
25
+ "attributes": {
26
+ "data-theme": {
27
+ "Light": "light",
28
+ "Dark": "dark"
29
+ }
30
+ },
31
+ "consumers": "Set <body data-theme=\"light|dark\">; lowercase any stored preference on read; update your own [data-theme=Light|Dark] selectors and === 'Light'/'Dark' comparisons. Token values are unchanged (1,542 tokens x 6 scopes compared equal)."
32
+ }
33
+ ```
34
+