raft-ui 0.21.0
更新日志
raft-ui 每个版本的改动,最新的在前。
0.21.0 (2026-10-22)
0.20.1 was not published to npm. This release includes its changes, listed under 0.20.1 in the changelog.
Design System
- Timeline rhythm: a conversation uses three distances between its lines, measured from text to text: 8px between the rows of one turn, the message step (16px) between turns, and the section step (24px) at a boundary: either side of a date line, from the top of the list to its first line, and from the last line to the composer's frame. It applies to MessageItem, SystemMessage, SystemMessageGroupHeader, MessageListDateDivider, MessageListNotice, ThreadPanelOriginalMessage, ThreadPanelReplySummary, ThreadPanelReplyCount, ConversationPanelFooter and ThreadPanelFooter. (#694)
- Why: each part kept its own spacing, so a date line sat closer to the message above it than to the one below and looked attached to it, an event line had more space under it than over it, and the composer's frame read as touching the last message; the parts now read one rhythm from tokens, one step wider for each kind of break.
- Affects: new tokens
timeline-gap-message16px,timeline-gap-section24px andtimeline-row-inset4px (the reach of a row's highlight above and below its text). - Docs: https://raft-design.botiverse.build/foundations/spacing/#timeline-rhythm
- A control and its label: a checkbox, radio or switch sits 8px before its label, centered on the label's first line, and keeps its size wherever it sits. In a settings row with a description, both ends center on the title's first line. A tap between two options lands on the nearer one. It applies to Checkbox, RadioGroupItem and ListRow; FieldItem already followed it. (#686)
- Why: a box in a
<label>row followed the row instead of the text's line, so callers nudged it with a margin, and a settings row's ends centered on the title and description together, level with neither; the components now place the control themselves. - Affects: switch 50 × 30px and checkbox and radio 20px, unchanged; 8px from control to label.
- Docs: https://raft-design.botiverse.build/foundations/alignment/#a-control-and-its-label
- Why: a box in a
Breaking Changes
- ConversationPanelFooter and ThreadPanelFooter: no space of their own above the composer; the end of the list sets it. (#694)
- Why: the footer's 12px stacked on whatever space the list left under its last message, so the gap to the composer differed from list to list; now the list ends with one token and the composer's frame sits a section step, 24px, under the last line.
- Affects: footer top padding 12px → 0;
pb-timeline-endis 20px (the 24px section step less the last row's 4px inset). - Migrate: end your message list with
pb-timeline-endunder its last row, in place of any spacer you had there; without it the composer moves 12px closer to the last message. You can also start the list withpt-timeline-startover its first row, so its top follows the same step. - Docs: https://raft-design.botiverse.build/components/conversation-panel/
Added
- Timeline spacing utilities:
pt-timeline-startandpb-timeline-endfor the top and end of a message list you build, andtimeline-message,timeline-section,timeline-row,timeline-turnandtimeline-lead, which the components use. All are registered with thecnhelper, so they merge with other padding and margin classes. (#694)- Why: a list's top and end were written by hand in each product, which is how the gaps drifted; two utilities now give the section step with the rows' own insets taken out.
- Affects:
timeline-start12px,timeline-end20px,timeline-turn8px,timeline-lead12px,timeline-message16px,timeline-section24px,timeline-row4px. Use onlytimeline-startandtimeline-endin a product. - Docs: https://raft-design.botiverse.build/foundations/spacing/#timeline-rhythm
ButtonSizeandDeprecatedButtonSizetypes, and thebutton-sizescodemod, which renames deprecated Button sizes to the size they render as. (#686)- Why: the deprecated names could only be found by hand, and
smandxsare also sizes of other components; the codemod parses TSX and renamessizeonly on Buttons, the raft-ui parts that passsizeto a Button and wrappers you name, plusicon-sm/icon-xson any element andsizeinbuttonVariants()calls. - Affects: run
node node_modules/@botiverse/raft-ui/codemods/button-sizes.mjs <src> [--dry] [--components=MyButton]; it renamesxs,sm→mdandicon-xs,icon-sm→icon-md, then lists everysm,xs,icon-smandicon-xsit left for review. It needs@babel/parser, a new optional peer dependency (pnpm add -D @babel/parser); nothing else uses it. - Docs: https://raft-design.botiverse.build/components/button/#sizes
- Why: the deprecated names could only be found by hand, and
Changed
- Button
size:xsandsmare deprecated aliases ofmd, andicon-xsandicon-smoficon-md, kept until raft-ui 1.0. They already rendered as those sizes, so nothing on screen changes. (#686)- Why: nine names for five sizes made a smaller button look available where there is none; a new Size by place table says which size each place takes.
- Affects: sizes
md(32px),lg,xl,icon-mdandicon-lg;sizeis typedButtonSize | DeprecatedButtonSize, so every existing size name still type-checks. No runtime warning. - Docs: https://raft-design.botiverse.build/components/button/#size-by-place
- Checkbox and RadioGroupItem directly in a
<label>: the box takes one line of the label's height and centers on it, so it sits on the first line in anitems-startrow and on the one line in anitems-centerrow, at any min-height. (#686)- Why: the box followed the row rather than the text's line, so it sat above the center of a line taller than 20px and callers added a
margin-top; see A control and its label above. - Affects: vertical margin
max(0, (1lh - 20px) / 2)from the<label>'s type (2px on a 24px line); the box stays 20px and atext-xsrow stays 20px tall. Amargin-topyou added to line it up is no longer needed. Set the label's type on the<label>, useitems-startwhen the text can wrap, and give a wrappingtext-xslabelleading-5. - Docs: https://raft-design.botiverse.build/foundations/alignment/#a-control-and-its-label
- Why: the box followed the row rather than the text's line, so it sat above the center of a line taller than 20px and callers added a
- DescriptionList
direction="inline": every fact is at most 24rem wide, or the row's width if that is narrower, and keeps the same 24px between facts and 8px between lines as the others. (#697)- Why: a long value, such as a message, made its fact as wide as the row on a line of its own, so one line of facts no longer read as one flow; every fact now wraps the same way, and a long value wraps at the cap, or is cut there when the caller truncates it.
- Affects: fact
max-widthnone →min(100%, 24rem)(384px); plain text longer than that wraps inside the 24rem. To cut a long value on one line instead, truncate it with an ellipsis and make the whole value available elsewhere, such as a tooltip. - Docs: https://raft-design.botiverse.build/components/description-list/
- ListRow
variant="plain"with a description: the leading icon, avatar or tile and the trailing switch, checkbox, radio, value or chevron center on the title's first line instead of the middle of the title and description. With a title alone, nothing changes. (#686)- Why: with a description the ends sat between the title and the description, level with neither and with no line of text.
- Affects: the ends move without changing size, so row heights are unchanged (54px on a phone for a one-line row with a switch).
- Docs: https://raft-design.botiverse.build/components/list-row/
- MessageListDateDivider, SystemMessage, SystemMessageGroupHeader, MessageListNotice, ThreadPanelOriginalMessage, ThreadPanelReplySummary and ThreadPanelReplyCount: spaced on the timeline rhythm. (#694)
- Why: see Timeline rhythm above.
- Affects: a date line's rule sits 24px from the text above and below it (was 24px / 32px on a phone, 25px / 33px on a desktop); SystemMessage padding 12px / 12px → 12px / 4px, 16px from the text on both sides; a closed SystemMessageGroupHeader keeps the same 12px / 4px around its disclosure's text (padding 6px / 6px → 6px top and −2px bottom on a phone, 7px and −1px on a desktop), and an open one keeps its 6px under the header; MessageListNotice padding 0 / 8px → 12px / 4px; ThreadPanelOriginalMessage bottom padding 8px → 20px and ThreadPanelReplySummary top padding 8px → 12px, so the thread's rule sits 24px from the text on both sides; ThreadPanelReplyCount gains 4px above it (margin 0 / 4px → 4px / 4px). MessageItem keeps its 4px / 8px, now read from the tokens.
- Docs: https://raft-design.botiverse.build/foundations/spacing/#timeline-rhythm
Fixed
- Select, Combobox, ComboboxSelect and InputGroup: a value cut with an ellipsis gets its full text as a
titlewhenever it is cut. (#695)- Why: the check compared whole pixels with 1px to spare, so it missed cuts of about 0.6–1.3px that still showed an ellipsis, and it did not measure again when a web font finished loading; it now compares fractional sizes and measures again after fonts load.
- Affects: tolerance 1px (whole pixels) → 0.5px (fractional).
- Docs: https://raft-design.botiverse.build/components/select/
- Checkbox and RadioGroupItem hit areas: in a
<label>row the box or circle reaches 4px past its line, half the 8px gap to the next option, so a tap between two options lands on the nearer one; the label is the target for the rest. On its own, its hit area is 44px tall on a touch screen. (#686)- Why: each option's hit area reached across the gap and the next option's covered it, so a tap just under one option could select the next one.
- Affects: standalone touch hit area 44px tall (was 36px for a checkbox, 33px for a radio); standalone mouse hit area unchanged; in a
<label>row the hit area is the line plus 4px above and below, for every pointer. - Docs: https://raft-design.botiverse.build/foundations/alignment/#a-control-and-its-label
0.20.1 (2026-10-22)
Design System
- A mark that leads text: an icon or status light that starts a line of text is centered on the first line of that text and stays there when the text wraps or a description follows. Only the place is shared; each component keeps its own icon size and gap. A status light beside text is always 9px. It applies to Banner, PanelNotice, ToastIcon, MessageActionCardNotice, Badge and PanelActivity. (#670, #687)
- Why: each component placed its mark with its own fixed offset, which held for one type size and drifted at the next; one rule, the line box center (the cap-height center in Space Grotesk), now places every mark.
- Affects: offset
(1lh - size) / 2on the row's own type; status light 9px with a 1.5px ink edge. - Docs: https://raft-design.botiverse.build/foundations/alignment/#a-mark-that-leads-text
- Popups stay on their trigger: when the window or a pane scrolls under an open popup, the popup moves with the element that opened it. A context menu stays with the point where it was opened. It applies to SelectContent, ComboboxContent, PopoverContent, PreviewCardContent and NotificationCenterPopup. (#669)
- Why: popups stayed where they opened while the page scrolled, pointing at whatever had scrolled into place; the wrappers no longer turn Base UI's anchor tracking off.
- Docs: https://raft-design.botiverse.build/foundations/elevation/#popups
Added
lead-iconandlead-dotutilities: center an icon (lead-icon) or aStatuslight (lead-dot) you place yourself on the first line of the text beside it. (#670, #687)- Why: a mark placed outside the components above had no way to follow the same rule except a hand-tuned margin.
- Affects:
lead-dotsizes the light 9px with a 1.5px edge, so don't pass theStatusasize. Both read1lh, so put the text's type on the row that holds the mark and the text. - Docs: https://raft-design.botiverse.build/foundations/alignment/#a-mark-that-leads-text
Changed
- Status in Banner, PanelNotice, ToastIcon, Badge and PanelActivity: held at 9px and centered on the first line, whatever
sizeor vertical margin it was given. (#670, #687)- Why: the same light showed at different sizes in a toast, a banner and a chip; a light beside text has one size, 9px, the least color that tells green from yellow from orange, plus its ink edges.
- Affects: a
Statuspassedxs(6px),sm(8px) orlg(11px) in these components renders 9px; thexsedge 1px → 1.5px (its 1px corner stays). The defaultmdis unchanged, as are icon sizes and gaps. - Docs: https://raft-design.botiverse.build/components/status/#sizes
Fixed
- PanelNotice: a leading status light or icon is centered on the first line of the notice's text. (#670, #687)
- Why: the mark sat at the top of the first line, a light 6.5px above the line's center on a desktop (5.5px on a phone) and a 16px icon 3px (2px); the notice now follows A mark that leads text.
- Affects: icon size and gap are unchanged (your icon's size, 10px before the text).
- Docs: https://raft-design.botiverse.build/components/panel/
- Popups: SelectContent, ComboboxContent, PopoverContent, PreviewCardContent and NotificationCenterPopup follow their trigger while the window or a scrolling pane moves. (#669)
- Why: these wrappers turned anchor tracking off by default, so an open popup kept its opening position and drifted off its trigger as soon as anything scrolled; they now leave it to Base UI's default.
- Affects:
disableAnchorTrackingdefaulttrue→ unset (Base UI's default, tracking on). ContextMenuContent drops itstruedefault too; it is anchored to a point, so it looks the same. PassdisableAnchorTrackingto turn it off for one popup. Select still turns tracking off withalignItemWithTrigger. - Docs: https://raft-design.botiverse.build/foundations/elevation/#popups
0.20.0 (2026-10-22)
Design System
- Hover and press: hover and press are one translucent ink layer over the resting colour, never a fill swap, in two tiers sized by what is under the pointer. Subtle for rows, messages, readable cards and a row whose menu is open; medium for small controls and for pressing anything clickable. A colour sticker and the frame get their own medium alphas. Selected items and fields don't hover, a message has no press, and every hover sits under
(hover: hover). It applies to every row, card, button, badge, tab, segment, sidebar and rail item, table row, inbox item and menu. (#681)- Why: the same hover looked different on paper, the sidebar and a card, rows and messages used three different steps, and some clickable parts gave no feedback on press; one layer in two tiers reads the same everywhere.
- Affects: subtle
fill-subtle4% light / 3% dark; mediumfill-medium7.8% light / 6.5% dark (was 7.8%);sticker-medium10% (was 12.2%);fill-frame-medium10% light (was 12.2%) / 6% dark (was 7.8%); darkbrand-soft16% → 20%; colour-hover/-activepairs mix 10% ink (was 12%). - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
- Inside a frame: a control embedded in another component's frame (a field, the composer, a toolbar, a card or media corner) wears
radius-chip(6px) and sits the same distance from every side of the frame and from its siblings; frames themselves don't change. It applies to ComboboxSelect, Combobox, InputGroup, the composer, the message hover and text-selection toolbars, the lightbox and multi-select bars, CodeBlock, media and file card actions, popover close, menus and SegmentedControl / ToggleGroup. (#682)- Why: embedded buttons at the free-standing 8px corner read nearly round at 20px, and their offsets differed per component; one corner and one even inset make them read as one kind of thing.
- Affects: new tokens
control-embedded-inset6px andcontrol-embedded-size20px desktop / 32px phone (utilitiesembedded-inset,control-embedded); insets 6px in a field, the composer and a corner, 4px in a floating toolbar and for a popup's highlight, 3px between segments. - Docs: https://raft-design.botiverse.build/foundations/shape/#inside-a-frame
Breaking Changes
- Dialog
size:smis 400px (was 384) andlgis 640px (was 512);md448 andwide896 are unchanged. Sizes are chosen by content:smfor a decision,mdfor a small task,lgfor content that needs width. (#676)- Why: the steps between sizes were too small to mean anything, and a confirmation could be 384 or 400 wide depending on the component; now every confirmation is 400, the same as AlertDialog.
- Affects:
smmax-w-sm(384px) → 400px;lgmax-w-lg(512px) → 640px. - Migrate: dialogs with
size="sm"grow 16px andsize="lg"128px; check their content and pick the size by content. Set width withsizeonly, not your ownmax-w-*. - Docs: https://raft-design.botiverse.build/components/dialog/#variants
- Hover tokens: renamed to their tier. The old names stay as deprecated aliases until 1.0, but they now resolve to the tier values, so some hovers change. (#681)
- Why: names that said where a fill was used (row hover, message hover, row open, pressed) could not describe two tiers that apply everywhere.
- Affects:
fill-row-hover,fill-row-open,fill-message-hover→fill-subtle(row open 5% → 4%, message hover 3% → 4%);fill-pressed→fill-medium;fill-frame-pressed→fill-frame-medium;sticker-pressed→sticker-medium; utilitiesrow-hover-overlay,row-open-overlay→overlay-subtle,press-overlay→overlay-medium,press-overlay-sticker→overlay-sticker-medium. Overlays are registered with thecnhelper as background images, so a laterhover:bg-noneor another tier replaces them. - Migrate: run
node node_modules/@botiverse/raft-ui/codemods/token-names.mjs <paths>; it renames classes,var(--…)andvar(--color-…)forms. Then move readable cards that usedpress-overlayfor hover tooverlay-subtle. Tests that assert a token name in a class (for examplevar(--fill-row-open)on a message) look for the new name. - Docs: https://raft-design.botiverse.build/foundations/color/#hover-overlays
- MessageItemToolbar and MessageTextSelectionToolbar: 36px tall (was 32), buttons 4px from the frame and 4px apart (was 2px), at
radius-chip. The hover toolbar rides 18px above the row (was 16). (#682)- Why: at a 2px inset the buttons' hover fill nearly touched the frame; see Inside a frame above.
- Affects: toolbar height 32 → 36px; inset and gap 2 → 4px; the message header's default reserve
--mi-toolbar-reserve6.5rem → 7rem (three buttons). - Migrate: if you set
--mi-toolbar-reserveyourself, compute it as1rem + 2rem × buttons(was1.25rem + 1.75rem × buttons). Leave at least 18px above the first message of a scroll area and above a thread's root message. - Docs: https://raft-design.botiverse.build/components/message-item/
- MessageMultiSelectToolbar: 36px tall (was 48) with 28px buttons (was 32) 4px from the frame; on a touch screen each button takes a 44px tap. The buttons go in the new
MessageMultiSelectToolbarActions. (#682)- Why: every floating toolbar is now 36px with 28px buttons; the touch hit area keeps the smaller buttons easy to tap.
- Affects: height 48 → 36px; buttons 32 → 28px; inset 8 / 10 → 4px; gap 6 → 4px.
- Migrate: wrap the toolbar's buttons in
MessageMultiSelectToolbarActions, which owns their 4px gap and the touch hit areas; remove your own gap or padding there. - Docs: https://raft-design.botiverse.build/components/message-item/
- ComboboxSelect, Combobox and InputGroup chips and end buttons: chips and embedded buttons in a field are 20px on a desktop and 32px on a phone (chips were 28px, end buttons 32px), 6px from the field's edge and from each other, at
radius-chip, so a field with chips stays 32 / 44px tall (it grew to 42). (#682)- Why: 28px chips made a field with chips taller than one without, and the embedded buttons sat at different offsets per component.
- Affects: chips 28 → 20px desktop / 32px phone; chip remove 16 / 28px at
radius-sticker; InputGroup end-addon Button 32 → 20 / 32px with a 16px icon; ComboboxSelectsmkeeps 22px chips at 3px,lguses 40px chips in a 52px field. - Migrate: remove size, offset or radius overrides on chips and end-addon buttons in fields. In an InputGroup end addon, put a Kbd before the Button, so on a phone (Kbd hidden) the button is the last child and ends 6px from the edge.
- Docs: https://raft-design.botiverse.build/components/combobox-select/
Added
- CodeBlockPre: renders the code's
pre. While the code overflows it is a keyboard-reachable named region (tabindex=0,role=region,aria-label"Code" by default), so it can be scrolled from the keyboard; code that fits adds no Tab stop. (#682)- Why: a scrolling code block could not be reached from the keyboard in Safari.
- Docs: https://raft-design.botiverse.build/components/code-block/
- MessageMultiSelectToolbarActions: the multi-select toolbar's actions at its end, 4px apart, with touch hit areas. (#682)
- Why: see MessageMultiSelectToolbar above.
- Docs: https://raft-design.botiverse.build/components/message-item/
- Press feedback: Badge, the mention sticker, InboxItem, Table rows, the system-message disclosure and the selected SidebarItem now press with the medium tier. (#681)
- Why: anything clickable presses with the medium tier; these gave no feedback on press.
- Affects: solid Badge
sticker-medium; soft and outline Badge, InboxItem, Table row, system-message disclosurefill-medium; selected SidebarItemsticker-medium. - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
Changed
- MessageItem, MessageAttachmentCard, MessageReplies, TaskCard and menu-open rows: hover with the subtle tier. (#681)
- Why: a message, a readable card and a row whose menu is open are large things people read; one subtle step keeps text contrast and never stacks with the menu-open fill.
- Affects: message hover 3% / 2.2% → 4% / 3%; menu-open row 5% / 4% → 4% / 3%; attachment card, replies bar and task card 7.8% → 4% / 3%. Press stays medium.
- Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
- SegmentedControl, ToggleGroup and Select: a selected segment or toggle no longer darkens on hover and presses with
sticker-medium; the Select trigger and the top-bar SearchTrigger don't hover, only press. SegmentedControl and ToggleGroup items are 3px apart (was 2). (#681, #682)- Why: selected items and fields don't hover, like selected tabs and the active sidebar item.
- Affects: segment gap 2 → 3px.
- Docs: https://raft-design.botiverse.build/components/segmented-control/
- DropdownMenu, ContextMenu, Select, Combobox, ComboboxSelect, the suggestion list and quick reactions: the highlighted item sits 4px inside the popup at
radius-chip, and separators take 4px. (#682)- Why: see Inside a frame above.
- Affects: highlight inset 6px (quick reactions 8px) → 4px; highlight radius 8 → 6px; separator margins 6 → 4px.
- Docs: https://raft-design.botiverse.build/components/dropdown-menu/
- CodeBlock: the copy and other actions sit 6px from the corner at
radius-chip, and without a header they take a column of their own instead of covering the first line; the code scrolls beside them. With a header, the actions sit 6px from its end, centred in it, 4px apart. (#682)- Why: on a phone the copy button covered the first line of code.
- Affects: action offset 9px (no header) / 5px (header) → 6px; radius 8 → 6px; a one-line block keeps 6px under the button.
- Docs: https://raft-design.botiverse.build/components/code-block/
- Composer, MessageActionCard, Popover header close, Lightbox info bar, video preview, gallery and file card actions: embedded buttons at
radius-chip, 6px from the frame's sides (4px in the lightbox bar, whose buttons are 28px with a 44px touch target). (#682)- Why: see Inside a frame above.
- Affects: composer buttons 8 / 13px from the sides and 9px from the bottom → 6px; media and file card actions 9 / 7 / 5px → 6px; lightbox buttons 32 → 28px.
- Docs: https://raft-design.botiverse.build/foundations/shape/#inside-a-frame
Fixed
- ComboboxSelect and FloatButton: hover no longer sticks after a tap on a touch screen; their
:hoverrules are under(hover: hover), and the ghost FloatButton hovers and presses with the overlay instead of a fill swap. (#681)- Why: ungated
:hoverrules stayed applied after a tap. - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
- Why: ungated
0.19.0 (2026-10-21)
Design System
- Collapsing the sidebar: on desktop the sidebar toggle is the first item of the rail's footer, above Help and Settings. Its icon shows what pressing it does (
panel-left-closewhile the sidebar is shown,panel-left-openwhile it is hidden), its tooltip says Collapse Sidebar or Expand Sidebar with the shortcut (⌘\ on macOS, Ctrl+\ elsewhere), and the state is remembered on the device. It applies to AppShellSidebarTrigger. (#674)- Why: the toggle belongs to the frame rather than to a page, so it needs one fixed place and one behavior whatever the main area shows.
- Docs: https://raft-design.botiverse.build/patterns/navigation/#collapsing-the-sidebar
Breaking Changes
- BannerAction: the buttons sit at the bottom right, at the end of the last line of text and centered on it, while the text fits beside them on one line. When the text would wrap beside them, or the banner is narrower than 480px, they take a line of their own under the text, aligned to the end.
BannerActionplaces itself; it setsdata-wrapwhen it takes its own line. (#672)- Why: the button sat in a start-aligned row under the text, away from where the eye ends; it now comes after what it acts on and ends where reading ends, as a dialog's commit button does.
- Affects: action placement from a row under the text (start-aligned,
mt-2) → the end of the last line from 452px of banner content (a 480px banner atmd), otherwise its own line under the text, end-aligned;size="sm"banners with an action get vertical padding that centers a 32px button on the line. - Migrate: remove layout overrides on
BannerAction, such ascol-span-full,col-start-*,row-start-*ormt-*, which now fight its placement. GiveBannera width (or let it fill its parent); neverw-fit. Usevariant="default" size="sm"for the button, and put a ghost Dismiss (variant="ghost" size="sm") before it when the banner can be dismissed; move a × out ofBannerActionintoBannerClose, on a banner with no button. - Docs: https://raft-design.botiverse.build/components/banner/
- AppShellSidebarTrigger: the trigger is a disclosure button. It has
aria-expandedandaria-controlsnaming the sidebar slot, instead ofaria-pressed, and its labels are Title Case: "Collapse Sidebar" and "Expand Sidebar". (#674)- Why: the trigger shows and hides a region, which assistive technology announces as expanded or collapsed, not as a pressed toggle.
- Affects:
aria-pressed→aria-expanded; newaria-controls;AppShellSidebarSlotgets a generatedidwhen the root issidebarCollapsibleand none is passed; label andtitle"Collapse sidebar" / "Expand sidebar" → "Collapse Sidebar" / "Expand Sidebar". - Migrate: update tests and selectors that read
aria-pressedor the old labels toaria-expandedand the Title Case labels. When you wrap the trigger in a Tooltip, passtitle={undefined}and repeat the label in the tooltip; to translate it, pass your ownaria-label. - Docs: https://raft-design.botiverse.build/components/app-shell/
- AppShellMainSlot and AppShellSidebarSlot: in a collapsible shell, the main area has a z-index only while it slides over the sidebar, and the collapsed sidebar is
inert. (#674)- Why: the main area sat at z-index 45 at rest and covered the app's own fixed layers, such as a bar at z-40, and a collapsed sidebar's controls stayed in the tab order while hidden.
- Affects: main slot
z-45always →z-2withdata-slidingonly during the slide, none at rest; the expanded sidebar slot isrelative z-1, so a resize handle past its edge stays grabbable; the collapsed sidebar slot addsinerttoaria-hidden. - Migrate: remove workarounds that lowered the app's own layers below z-45 or raised them over the main slot, and any code or test that reads an inline or computed z-index on the main slot at rest. Don't make the collapsed sidebar focusable yourself; a shortcut that collapses it while focus is inside should move focus to the trigger first.
- Docs: https://raft-design.botiverse.build/components/app-shell/
Added
- BannerClose: the × of a banner that has no button, a frameless icon button at the end of the first line whose icon box ends on the banner's padding. Its label defaults to "Dismiss". (#672)
- Why: a × put in
BannerActionread as one of the actions and sat on the wrong line; the close control now has a place of its own. - Affects:
variant="ghost",size="icon-sm"(32px), centered on the first line. WithBannerActionin the same banner, raft-ui warns in development and moves the actions to a line of their own. - Docs: https://raft-design.botiverse.build/components/banner/
- Why: a × put in
Fixed
- BannerTitle and BannerDescription: a long word or URL breaks onto the next line instead of pushing the banner wider. (#672)
- Why: an unbroken string, such as a URL, ran out of the text column and under the actions; the text now wraps inside its column.
- Docs: https://raft-design.botiverse.build/components/banner/
- ResizableHandle: under a coarse pointer, such as a finger on a tablet, the handle is easier to grab. (#667)
- Why: a 12px handle was too narrow to hit with a finger.
- Affects: handle hit area 12px → 24px under
pointer: coarse, still centered on the boundary; mouse and pen keep 12px. - Docs: https://raft-design.botiverse.build/components/resizable/
0.18.0 (2026-10-21)
Design System
- Field frames: a field's ink frame is 1.5px, the same weight as a button's. It applies to Input, Textarea, InputGroup, Select, Combobox, ComboboxSelect, SearchTrigger, the composer and an option card. Read-only fields keep their light, thin edge. (#661)
- Why: a field sits next to its send button, its submit button and the selected row, all 1.5px ink; at 1px it was the one thin control among them and read as a lighter style rather than the same system.
- Affects:
line-fieldwidth 1px → 1.5px. - Docs: https://raft-design.botiverse.build/foundations/shape/
- Autofill: a field the browser filled in looks like any other field,
layer-cardwith ink text. (#661)- Why: Chrome's pale blue and Safari's yellow made the filled field the one field on the page that matched nothing else, and the colour tells a person nothing.
- Docs: https://raft-design.botiverse.build/foundations/states/#autofill
Fixed
- Input, Textarea, Select and every other field: browser autofill no longer paints a field pale blue or yellow. raft-ui's
styles.csshandles everyinput,textareaandselecton the page; a disabled autofilled field keeps the disabled ink. (#661)- Why: see Autofill above.
- Docs: https://raft-design.botiverse.build/foundations/states/#autofill
0.17.0 (2026-10-21)
Design System
- Dragging on a touch screen: on an item that can be moved, holding still opens its menu and holding then moving drags it. The item arms after 500ms, the same moment raft-ui's long-press menu opens, and the drag starts on the first 10px of movement; moving earlier is a scroll, and a second finger cancels. The component that owns the item's menu closes it when the drag starts. (#662)
- Why: on a phone, a long press on a draggable item opened its context menu first, and a scroll that started on a tab, task card or attachment picked the item up instead.
- Affects: touch drag hold 0ms (tabs, cards and attachments picked up at once) → 500ms, then 10px; held items get
select-none,-webkit-touch-callout: noneandtouch-manipulation. Mouse and pen still start a drag after 5–6px, with no hold. - Docs: https://raft-design.botiverse.build/foundations/states/#dragging-on-a-touch-screen
Added
- SortableTabsList
onDragStart: called when a tab drag starts, so an app that puts its own menu on a tab can close it. (#662)- Why: the component that owns an item's menu closes it when the item's drag starts.
- Docs: https://raft-design.botiverse.build/components/tabs/
Fixed
- SortableTabsList, task drag-and-drop and SortableComposerAttachments: on a touch screen a finger no longer starts a drag at once; it has to hold still for 500ms and then move. Scrolling that starts on a card or an attachment scrolls again. (#662)
- Why: the pointer sensor took every touch before the touch sensor could, and
touch-nonestopped the page from scrolling. - Docs: https://raft-design.botiverse.build/foundations/states/#dragging-on-a-touch-screen
- Why: the pointer sensor took every touch before the touch sensor could, and
0.16.0 (2026-10-21)
Breaking Changes
- VirtualList
items: compared by reference. A row renders again only when its item, its index or one of the list's render props changes, so replace an item to change it instead of editing it in place. (#658)- Why: keeping rows across range updates is what removes the per-frame re-render.
- Migrate: if your code edits an item object in place, or a render function reads mutable state outside the props, replace the item (a new object) when it changes.
- Docs: https://raft-design.botiverse.build/components/virtual-list/
Changed
- VirtualList: scrolling no longer restyles or re-renders every mounted row. Rows that stay in range keep their elements; rows are mounted ahead of the view in batches and passed rows stay mounted for a while; only the rows that fill the view below are mounted; rows above the view are planned at the average measured height; resize passes where nothing changed size write nothing. (#658)
- Why: in a long chat timeline the list dropped more frames than a plain list while scrolling, most with a slow CPU: every row was restyled on almost every frame, and each range change re-rendered every row.
- Affects: with a 4x slower CPU, frames over 33ms scrolling 320 messages 9 → 0 down and 24 → 0 up, p95 33ms → 16.7ms; main-thread time per frame 10–17ms → 2–5ms. Up to 2.5 viewports of passed rows stay mounted; after a jump only the rows in view mount, as before.
- Docs: https://raft-design.botiverse.build/components/virtual-list/
0.15.0 (2026-10-21)
Design System
- Server switcher: on every surface (the web top bar, the phone web's Home and the mobile app) the server switcher is one button: the server's avatar, its name and a chevron, whatever sits beside it. The tilted ink pill on the phone web's Home is retired; build the switcher from
WorkspaceButton,Avatar type="server" size="sm"andWorkspaceButtonName. (#655)- Why: the switcher had three different shapes across the web top bar, the phone web and the mobile app, and the desktop button changed shape with the window width.
- Affects: avatar
sm27px, gaps 8px / 4px, name max 200px then an ellipsis, chevron 16px, on an on-framemdbutton (36px). - Docs: https://raft-design.botiverse.build/patterns/navigation/#servers
Added
- WorkspaceButton and WorkspaceButtonName: the server switcher's button. (#655)
- Why: one component keeps the switcher the same on every surface.
- Affects: on-frame
mdbutton, 36px; name max 200px. - Docs: https://raft-design.botiverse.build/components/workspace-button/
- Avatar
type="server": a server's avatar, an ink fill with its initial in brand yellow. (#655)- Why: each app drew a server's avatar as a person's avatar recoloured to ink.
- Affects: fill
sticker-ink, initialbrand. - Docs: https://raft-design.botiverse.build/components/avatar/
0.14.0 (2026-10-21)
Design System
-
Option height: an option in any popup list is as tall as its content, and the space above and below its text is constant. (#647)
- Why: options had a fixed 6px above and below whatever their content, which is less than a one-line 36px row leaves around its text, so a two-line option (a wrapped label, or a name with an id under it) looked more cramped than its one-line neighbors.
- Affects: space above and below an option 6px → 7px with a mouse (22px line), 8px on a touch screen (20px line); one line stays 36px; a second line adds only its own height. On short windows (600px tall or less) the menus' extra
py-1is removed. - Docs: https://raft-design.botiverse.build/patterns/forms/#option-height
-
The ends of a header: the icon buttons at both ends of every header (panels, threads, phone top bars, drawers, sheets, a dialog's ×, the sidebar) put their icon box on the content margin, and their empty hit area hangs outward. (#650)
- Why: a 44px button on a touch screen was placed by its own edge, so Back sat 11px inside the content line while the ⋮ at the other end stayed on it, and the two ends stopped mirroring.
- Affects: icon box on the margin, 16px on a phone and 20px on a desktop and in a dialog, at every button size (32px or 44px); a phone's top bar moves its icon boxes from 14px to 16px from the edge.
- Docs: https://raft-design.botiverse.build/foundations/alignment/#the-ends-of-a-header
-
A tab's own controls: when a tab's content has controls (a view switch, filters, search, a sort), they go in one pinned toolbar row under the tabs, the same row the Tasks tab draws: filters and search on the left, a view switch on the right; a tab with no controls has no row. (#652)
- Why: a view switch sat directly on the divider under the tabs, and each tab placed its controls differently, so a switch under the selected tab read as a second row of tabs.
- Affects: the row has the content margin at its sides (20px desktop, 16px phone) and a
line-seambelow it. - Docs: https://raft-design.botiverse.build/components/tabs/#a-tabs-own-controls
Added
- Utilities
py-option-insetandmin-h-option, the constant option padding and the floor for an option with no line of text. (#647)- Why: a product that builds its own option row keeps the same padding as raft-ui's.
- Affects:
py-option-inset= (36px − one line) / 2;min-h-option36px. - Docs: https://raft-design.botiverse.build/patterns/forms/#option-height
Changed
- Select, Combobox, ComboboxSelect, DropdownMenu and ContextMenu options: their padding is
py-option-insetinstead of a fixed 6px. (#647)- Why: see Option height above.
- Affects: padding 6px → 7px (mouse) / 8px (touch); two-line options 2px taller with a mouse, 4px on a touch screen; one-line options unchanged at 36px.
- Docs: https://raft-design.botiverse.build/patterns/forms/#option-height
- PanelHeader, ThreadPanel, Drawer, Dialog, AlertDialog, Lightbox, NotificationCenter and Sidebar headers: frameless icon buttons at the ends align by their icon box on the content margin. (#650)
- Why: see The ends of a header above.
- Affects: edge icon boxes at 16px (phone) / 20px (desktop, dialog) from the edge at every button size.
- Docs: https://raft-design.botiverse.build/foundations/alignment/#the-ends-of-a-header
Fixed
- InputGroup: a text addon is centered with flex, so older iOS Safari no longer sets its text at the top; long text still truncates. (#646)
- Why: the previous centering needed Safari 17.4.
- Docs: https://raft-design.botiverse.build/components/input-group/
0.13.0 (2026-10-21)
Design System
- Row hover: rows hover with their own lightest visible step, not the press overlay; a row whose menu is open is marked with one slightly stronger step. (#641, #643)
- Why: the press fill made hovered messages and list rows read as grey and heavy, and on a phone a long-press menu left no mark of which row it belonged to.
- Affects: hover
fill-pressed7.8% →fill-row-hover4% (dark 3%), messagesfill-message-hover3% (dark 2.2%), menu-openfill-row-open5% (dark 4%). Press and selection are unchanged; hover stays lighter than selection. - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
Added
- Tokens
fill-row-hover,fill-message-hoverandfill-row-open, with utilitiesrow-hover-overlayandrow-open-overlayfor rows on a card or a tinted surface. (#641, #643)- Why: rows need a hover step lighter than press, and rows on a non-transparent surface need it as an overlay so the surface shows through.
- Affects:
fill-row-hover4% / 3%,fill-message-hover3% / 2.2%,fill-row-open5% / 4% (ink, light / dark). - Docs: https://raft-design.botiverse.build/foundations/tokens/
Changed
- Message, SidebarItem, List, Inbox, Search, Table and File rows: hover with
fill-row-hover(messages withfill-message-hover) instead offill-pressed. (#641)- Why: see Row hover above.
- Affects: hover background 7.8% → 4% (messages 3%).
- Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
- Rows with a menu open (right-click, ⋯ menu, phone long-press): take
fill-row-openwhile the menu is open; selection still wins. Comment cards hover withfill-row-hover. (#643)- Why: the open menu needs one marker of which row it belongs to, including on a phone where there is no hover.
- Affects: menu-open background
fill-row-open5% / 4%. - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
- Select, Combobox:
collisionPaddingdefaults to 8px. (#642)- Why: popups touched the screen edge on phones.
- Affects: popup inset from the screen edge, 8px.
- Docs: https://raft-design.botiverse.build/patterns/forms/#long-values-in-fields
Fixed
- Field, FieldItem, InputGroup, SelectValue, ComboboxSelect, chips and options: long values stay inside form fields on narrow screens. Labels, descriptions and errors wrap. A FieldItem label next to a checkbox, radio or switch shrinks. An InputGroup text prefix takes at most half the field and truncates. Values and chips truncate, with the full value as
title. Options wrap to 2 lines, then clamp. Select and Combobox popups are never wider than the screen less 8px on each side. (#642)- Why: long names and values could push fields and popups past the screen edge on phones, so every form field, picker and option list was checked and fixed together.
- Affects: InputGroup prefix
max-w-1/2; options clamp at 2 lines; popups are never wider than the screen less 8px on each side, and on a desktop at most the wider of the trigger and24rem. - Docs: https://raft-design.botiverse.build/patterns/forms/#long-values-in-fields
0.12.0 (2026-10-21)
Breaking Changes
ComboboxSelectalways takes typing on a phone, as on a desktop: tapping the field focuses it and typing filters;createand remote search work in the field. The phone-only pick-only mode and bottom sheet from 0.10.5 are gone, with their propssheetThreshold,sheetTitleandmessages.search/done/close. A pick-only field usesSelect. The list still picks its side once and doesn't flip while the keyboard opens. (#635)
Added
ComboboxSelecttakeslistMaxHeight(a CSS length or pixels, default16rem, the whole list's height); the side the list opens on follows it. (#635)- Jump controls that float over a conversation, from
@botiverse/raft-ui/wip:MessageListJumpControls,MessageListJumpPill(a Buttonoutline md),MessageListJumpPillGroupandMessageListJumpPillDismiss. (#637) - A message an assistant sent for its owner, from
@botiverse/raft-ui/wip:MessageItemDelegatedBy,MessageItemDelegatedByName,MessageItemDelegatedByButton,MessageItemDelegatedAvatar(withdeleted) andMessageQuotedPreviewAuthorDelegatedBy. (#633)
Fixed
- The 32px and 36px buttons' hit area reaches 44px; the overhang now counts the 1.5px border. (#637)
- Toast title and description use
text-callout(desktop line height 20 to 22px). (#637) - In a message header the name, notes (model, role, badge text) and time stand on one baseline. (#639)
MessageItemSenderGroup: the gap before a role squeezed to nothing collapses with it. (#633)VirtualList: a jump holds its target row by key, so rows prepended while it settles don't move it; the focused row stays mounted across a window switch. (#636)
0.11.0 (2026-10-21)
Added
VirtualList: a virtualized list for long, variable-height content such as a conversation, exported from the main entry. Rows are measured in block flow, so their margins collapse as in a plain list; it holds its anchor through items added above or below, jumps exactly to an item (scrollTowithalign), lays out multi-column rows, and keeps mounted the row that holds focus (so End, Home, paging and wheel scrolling don't lose it) and the rows a text selection reaches into. The scroller takes native HTML attributes and its ref asdomRef; styles are in.r-virtual-list. The implementation borrows from DeepSeek web's virtual list. (#627)
0.10.7 (2026-10-21)
Fixed
- A passage with comments now shows in dark mode:
raft-commentpaintsbrandover the page, 35% in light mode (as before) and 22% in dark, instead ofbrand-soft, which in dark mode barely differed from the page. The text on it stays at body-text contrast (APCA Lc 75 or more). (#626, #629) - In
MessageActionCardProperties, a label lines up with the first line of its value instead of the middle of a value that wraps. (#631)
0.10.6 (2026-10-21)
Added
- Comments beside a document, as in Google Docs, through
@botiverse/raft-ui/wip:CommentMargin,CommentCard(withCommentCardTrigger,CommentCardMeta,CommentCardQuote),CommentEntryandCommentMarker. Cards sit level with their passages and keep the document's order; on a narrow viewer aCommentMarkerbeside each passage opens the discussion in a bottom sheet instead.useCommentMarginFits(viewer, { minWidth })picks between the two,setCommentHighlightswithCOMMENT_HIGHLIGHT/COMMENT_HIGHLIGHT_ACTIVEhighlights the passages, andresolveCommentMarginLayoutis the layout for callers that place cards themselves. New pattern: Comments. (#614) AvatarGroupreads--avatar-group-surface(defaultlayer-canvas) for the ring between overlapping avatars, so a stack on another surface can match it. (#614)
0.10.5 (2026-10-20)
Added
ComboboxSelecttakessheetThreshold(default 10) andsheetTitle, and itsmessagestakesearch,doneandclosefor the phone's bottom sheet. (#618)
Fixed
ComboboxSelecton a phone (a touch screen under 48rem) no longer focuses a text input and raises the keyboard when people only pick a value. Up tosheetThresholdoptions open as an anchored list that picks its side once and doesn't flip while the keyboard or the address bar moves; more options,createand remote search open a bottom sheet with the search input at its top. Desktop and touch tablets are unchanged. (#618)
0.10.4 (2026-10-20)
Fixed
- A page in Traditional Chinese (
<html lang="zh-TW">, alsozh-HK,zh-MOandzh-Hant) falls back to Traditional Chinese faces (PingFang TC, Microsoft JhengHei, Noto Sans TC) before the Simplified ones, so characters keep their Traditional glyph shapes. Simplified Chinese and default pages are unchanged. (#610)
0.10.3 (2026-10-20)
Added
MessageItemSenderGroupwraps the name, badges and role and sets the order they give way in when the header is too narrow: the role truncates first, then the name; badges keep their width and the time never truncates. Existing headers without it keep working. (#607)MessageItemEdited: an inline "(edited)" marker at the end of the message text, on every row. (#607)
Changed
MessageItemTimesits right after the sender (name, badges, role) instead of at the far right, and no longer fades out under the hover toolbar: hover only adds, it never hides who said it or when. With a mouse the header keeps the toolbar's width free (--mi-toolbar-reserve, default 6.5rem for three buttons; set it on the list when the toolbar has more). This is visible whereverMessageItemTimeis used, also withoutMessageItemSenderGroup; wrap the name, badges and role in it to get the give-way order. (#607)MessageItemTimeandMessageItemContinuationTimerender a<time>element and takedateTime. (#607)
Fixed
- A long name no longer pushes the time out of the header (it was cut to "14:3"):
MessageItemSender/MessageItemSenderButtoncan shrink now. (#607)
0.10.2 (2026-10-20)
Fixed
PanelActionandPanelToggleActionfollow the same desktop condition as every other control ((width >= 48rem) and (pointer: fine)). On a touch tablet they were 32px while the rest stayed 44px; they now keep 44px with 22px icons there. Phone and desktop sizes are unchanged. (#605)
0.10.1 (2026-10-20)
Fixed
- A page in Japanese or Korean (
<html lang="ja">/"ko", including regional tags likeja-JP) falls back to its own system faces (Hiragino Sans, Yu Gothic UI, Meiryo, Noto Sans JP; Apple SD Gothic Neo, Malgun Gothic, Noto Sans KR) before the Chinese ones, so kanji keep Japanese glyph shapes and Hangul gets a Korean face. Chinese and default pages are unchanged. (#603)
0.10.0 (2026-10-20)
Breaking Changes
- On a desktop (
(width >= 48rem) and (pointer: fine): a wide screen used with a mouse or trackpad) type and controls follow Feishu PC's density. Touch devices keep the phone sizes at any width. Existing names keep working, but their sizes change there, so check any layout that matched them by hand (#601):text-headline16/24;text-body,text-subheadandtext-callout14/22;text-footnote,text-captionandtext-mono-meta12/18;text-mono-code13/20. Hero, display, title and label are unchanged.- Fields (
Input,Select,Combobox,ComboboxSelect,InputGroup) go from 44px to 32px, with 14/22 field text; card titles are 14/22. Buttonlgandicon-lggo from 44px to 32px (icon 22 → 18) and keep a 44px hit area;xlgoes from 52px to 40px. The defaultmdstays 32px; its label goes from 13px to 14px.- Anything hardcoded to
h-11/size-11to line up with a field no longer matches on desktop: useh-control/size-controlinstead. - The desktop type values sit after Tailwind's theme output, so they win over an app's own
@themevalues for these names; an app that changes one sets it inside the same query.
- Field frames (
Input,Textarea,InputGroup,Select,Combobox,ComboboxSelect,Composer, optionCard,SearchTrigger) are drawn at 1px inline-fieldinstead of 1.5px, on the phone too. (#601)
Added
- Control tokens that switch with the desktop query:
--control-height,--control-height-cta,--control-icon-size,--control-hit-inset, and theh-control,size-control,size-control-iconandtext-controlutilities (registered with tailwind-merge). (#601)
Changed
Tooltip,ComboboxSelectoptions,StepsandTimelineuse the type scale instead of fixed sizes, so they follow the desktop values. (#601)
0.9.6 (2026-10-20)
Added
installInputModality()and theInputModalitytype: raft-ui records whether the person last used the keyboard, a pointer, or a screen reader's virtual cursor, on<html data-input-modality>.ThemeProviderinstalls it for you (with the defaultsyncDom); an app withoutThemeProvidercalls it once on the client. (#598)toast.add/info/success/warning/error(title, { leading })puts a node, such as a person's avatar, inToastIconin place of the status pixel. It isaria-hiddenand still shows withicon: false. Calls withoutleadingrender exactly as before. (#599)
Changed
- Focus rings show only after keyboard navigation. A click, a tap, typing, or focus moved by a click no longer draws the ring, including on text fields (the caret marks them). Tab, F6, arrow keys outside a text field, and keys that move focus out of a text field (Esc closing a dialog, Enter submitting) still do, and so does focus a screen reader moves or that arrives with no input before it. Without the tracker installed every ring shows as before. Rings drawn with
shadow-focusfollow this automatically; other indicators take the newkeyboard:variant, and groups ring onhas-focus-visibleinstead offocus-within. (#598)
0.9.5 (2026-10-20)
Fixed
- On a touch screen, a tap no longer leaves a hover highlight on:
TableBodyrows, anInboxItem's pressed toggle action, the Activity inbox sort button and aFileRow(when itsFileRowOpenis hovered) now only show their hover on devices that can hover. With a mouse they look and behave as before. (#596)
0.9.4 (2026-10-20)
Changed
- Clickable text no longer sits underlined at rest: it underlines on hover in its own colour and never turns pink. This covers
TextLink(both emphases;strongno longer turns its underline pink on hover),MessageItemSenderButton,@mentions, the forwarded-bundle and translation text actions, empty-state description links (now semibold so they stand apart from the hint text), the video-preview failure action and toast actions. Disabled ones do not underline. Rendered Markdown links are the one exception: they keep a faint underline at rest (link blue at 40%), because colour alone is below 3:1 against body text, and on hover the underline goes full strength and the text darkens. (#594)
Fixed
MessageItemSenderButtonshows its hover state again when the app wraps it, for example in a tooltip trigger. (#594)- A
SelectItemwith aSelectItemDescriptionis named by itsSelectItemTextalone and described by the description, so screen readers no longer read the two run together as the option's name. Items without a description are unchanged. (#575)
0.9.3 (2026-10-20)
Fixed
- Message reference chips sit on the sentence's baseline. Chips with a leading icon (task, joint channel, thread, link) sat 3px high and text-only chips like
#general1px high, which also pushed lines apart (26px instead of 24px). Height, colors and padding are unchanged. (#583) InlineCodein a message centres its letters in the fill (they sat about 0.6px low). (#583)
0.9.2 (2026-10-19)
Fixed
- On an
InboxItemcard row,InboxItemTrailing(the timestamp, and on a touch screen the room forreveal="hover"actions) takes width only beside the first two content lines; the body, message and chips below run the full width. Before, every line was narrowed, so phone cards wrapped more and grew taller. Compact rows and rows without a trailing column are unchanged. (#586)
0.9.1 (2026-10-19)
Added
InboxItemActions reveal="hover": with a mouse, the actions stay hidden (still reachable by Tab) until the row is hovered, focused, active or has a menu open, and the timestamp, marks and dot step aside while they show. On a touch screen they show at rest and the trailing column makes room. (#584)InboxItemLeading: a 36px leading visual (an avatar or channel tile); the divider starts after it. (#584)InboxItem density="compact": a one-line title and excerpt for a conversation list, with the timestamp beside the title andInboxItemMarks(mention, muted, unread count, draft) beside the excerpt. In a compact rowdata-unreadbolds the title without the pink dot. (#584)InboxItemHoverTarget: lets a badge inside the row take the pointer so its tooltip opens; a click on it still opens the row. (#584)
0.9.0 (2026-10-19)
Breaking Changes
Text.Monono longer hassize="eyebrow"(10px all-caps mono). Head a section with<Text.Sans size="group">in sentence case instead; nothing in the product is in all caps. (#576)
Added
Text.Sans size="group": the heading of a group of rows or a section in a panel (13px at 700,foreground-hint). (#576)SegmentedControltakesiconsOnlyBelow("xs"to"2xl", Tailwind's container sizes; typeSegmentedControlIconsOnlyBelow). Mark the row@container; below that width each segment shows only its icon as a 32px square, its label and count stay the accessible name, and a tooltip shows the label. Use it only when every segment has an icon. (#578)DescriptionList direction="inline": facts in a wrapping row on one text baseline, 12px from term to value and 24px between facts, with no stacked-list gap. A first-child Badge, TaskChip, TaskStatusBadge or Button in a value hangs its padding outward, so its text starts where plain text does. (#579)
Fixed
- In a
SelectItemwith aSelectItemLeading, theSelectItemDescriptionstarts under the label instead of under the icon. (#577)
0.8.3 (2026-10-19)
Added
SelectItemDescription: an optional second line under an option's label, for what the value means. The trigger still shows only the label. (#573)- The
layer-backdrop-nestedcolor token (bg-layer-backdrop-nested): the lighter scrim of a modal opened from another modal. (#558)
Fixed
- A
SelectItemgrows with its content and keeps 6px above and below its text. A one-line item stays 36px. (#573) - A
DialogorAlertDialogopened from a sheet or another dialog draws its own, lighter scrim, so it no longer sits undimmed on the surface below. A sheet opened from a dialog does the same; a sheet over a sheet is unchanged. Nesting follows the modal roots, as in Base UI, and amodal={false}parent doesn't count. (#558) - The message hover toolbar and
MessageTextSelectionToolbarhave a 10px corner (radius-container) around their 8px buttons, so a hovered button's corner keeps an even 2px gap from the frame instead of pinching it. (#572)
0.8.2 (2026-10-19)
Added
createMessageTextSelectionAnchorandgetTextSelectionFocusRect(with theMessageTextSelectionAnchortype) give a selected-text toolbar a virtual anchor on the line that holds the selection's focus end, measured from text rects only. Pass the anchor to your popover so a full-line selection no longer anchors at the row's right edge. (#569)
Fixed
MessageTextSelectionToolbarmatches the hover toolbar: 28px buttons with 16px icons in ap-0.5frame. Its labels no longer wrap. (#569)- On a touch screen (
pointer: coarse, at any width), each action inInboxItemActionskeeps a 44px target and the actions sit 12px apart, so their targets don't overlap. With a mouse they stay 32px. (#567)
0.8.1 (2026-10-19)
Added
TaskDndProvidertakesaccessibility(theTaskDndAccessibilitytype), passed through to dnd-kit, so you can translate the screen-reader announcements and drag instructions. (#561)
Fixed
- Keyboard dragging in
TaskDndProvidermoves a card between drop targets. Before, Space picked the card up but the arrow keys never moved it. (#561) - The
MessageItemhover highlight sits 4px above and below the text on every row, including a merged message, instead of touching the first line. The 8px between groups is now a transparent top border (border-t-8 bg-clip-padding) and acompactrow drops it (border-t-0instead ofpt-0). Spacing between messages and row heights are unchanged. (#564) - Below
md, aSidebarItemrow is at least 44px high in the 16px title type, so a phone sidebar row can be tapped without help from the app. (#557) - The codemods run when called through a pnpm symlink. (#565)
0.8.0 (2026-10-19)
Breaking Changes
Buttonno longer hasvariant="link"orsize="inline". Underlined text is nowTextLink. A link inside a sentence (a banner's "View Billing") becomes<TextLink variant="strong">; a link on its own (Forgot Password, See All) becomes<TextLink>, which is muted.TextLinkhas no height, border or padding, so drop the classes that took them off. (#554)AnnouncementDialogSecondaryActionis a ghost button instead of an underlined link. Its API is unchanged, and it now looks like the frameless minor action of a dialog. (#554)- The activity inbox sidebar no longer styles
[data-activity-inbox-show-all]as a link (it keeps onlywhitespace-nowrap). Render Show All as aTextLink. (#554) FieldLabelno longer carriesleading-none. tailwind-merge already dropped it beside the type class, so most labels don't change, but a label whose class list kept it is now a full line high. (#541)SegmentedControlno longer shrinks withmax-w-full; it keeps its width (shrink-0). In a narrow row, give it room or let the row degrade asresponsive.mdsays, or it overflows. (#542)
Added
FieldItemtakessize(sm,md), to match the size of its label. (#541)TextLink: the bottom step of the button ladder, underlined text that takes the size of the text around it. It renders abutton, or a real link withrender={<a href="…" />}.variant="muted"(default) is for a link on its own;variant="strong"(ink, 600) is for a link inside a sentence. (#554)
Fixed
- A checkbox, radio or switch is centered on the first line of its label, whether or not the label wraps or has a description. (#541)
- Timeline content runs the full width under its title, instead of staying in the column beside the time. (#547)
SegmentedControlkeeps its width and no longer gets squeezed by a neighbor in the same row. (#542)- The time beside a merged message no longer wraps onto two lines, and it takes the person's 12-hour or 24-hour time. (#544)
- The presence light on a 19px avatar (
xs,2xs) is 7px with a 1px edge, so it no longer covers the face. (#549) - The message hover toolbar is 32px high with 28px buttons and 16px icons. It reaches 16px above the row, so leave 16px above the first message of a scroll area and above a thread's root message. (#553)
Changed
- The design-system pages explain the rules behind button emphasis, empty-state titles, times, message text size and row degradation. (#542), (#543), (#544), (#545), (#548)
0.7.1 (2026-10-19)
Added
stack-y-*/stack-x-*(including negative and arbitrary values) andrule-y/rule-x/rule-<colour>: space and rules between siblings, written as the owl> * + *at zero specificity. Use them instead of Tailwind'sspace-*/divide-*. (#534)
Fixed
- iOS scroll and open/close jank from structural selectors. Tailwind v4 compiles
space-*/divide-*to:where(.x > :not(:last-child)), which WebKit tests against every element; appending or removing a last child anywhere then restyled the previous last child's whole subtree (180–430ms under a long chat timeline). The package and its docs no longer use them, andno-unanchored-structural.test.tskeeps any rule from ending in an unanchored structural pseudo-class. Consumers should move their ownspace-*/divide-*tostack-*/rule-*. (#534) codemods/package-name.mjskeeps a deliberateraft-uinpm alias instead of rewriting it. (#522)
Changed
- The design-system pages and component guides describe what the product ships. (#523, #524, #526, #528–#530, #532)
0.7.0 (2026-10-18)
Breaking Changes
- The package is now
@botiverse/raft-ui. Install it under that name and change imports fromraft-ui(andraft-ui/styles.css,raft-ui/cn, …) to@botiverse/raft-ui:node node_modules/@botiverse/raft-ui/codemods/package-name.mjs <src>rewrites imports, CSS@import/@sourcepaths and the dependency, and lists the aliases to check by hand. The unscopedraft-uigets no further releases; this release also carries the changes of 0.6.1, which never reached npm. MessageItemSavedIndicatorno longer takeselevation, and theMessageItemSavedIndicatorElevationtype is removed. Its"xs"and"sm"values pointed at empty shadows, so the prop never drew one: delete it at call sites. (#488)
Added
@botiverse/raft-ui/tokens.json: every color, elevation and metric token in the Design Tokens Community Group format, with light and dark values, for design tools and code generators.foundation.cssis generated from it. (#482)codemods/token-names.mjsships in the package.node node_modules/@botiverse/raft-ui/codemods/token-names.mjs <src>rewrites Tailwind utilities andvar(--…)references from the former token names to the current ones (see Changed). (#476)foreground-on-hud-muted(text-foreground-on-hud-muted): secondary text on a tooltip or toast. (#496)- Shadow utilities
shadow-float,shadow-stickerandshadow-focus, registered with tailwind-merge in@botiverse/raft-ui/cn. (#476)
Changed
-
Token names follow the Raft Design System. Every former name keeps working as a deprecated alias (CSS variable and utility) until 1.0, and the values are unchanged; run the codemod to move:
line→line-seam,line-muted→line-hairlineprimary-*→brand-*(yellow),accent-*→attention-*(pink)layer-panel→layer-canvas,layer-inset→fill-muted,layer-canvas-muted→layer-sidebarforeground-strong→foreground,foreground-icon→foreground-muted,foreground-placeholder→foreground-hint,layer-hud-foreground→foreground-on-hud--color-brutal-<color>-<step>→--palette-<color>-<step>(purple is lavender),bg-brutal-<color>/bg-soft-signal→bg-palette-<color>--theme-shadow-float/-lg/-xl→--elevation-float,--theme-shadow-sticker→--elevation-sticker,--theme-focus-ring→--focus-ring; utilitiesshadow-raft-*→shadow-float/shadow-sticker/shadow-focus
Components render the current utility names, so tests that assert class names may need the new names. (#476, #479)
-
Bannerrendersrole="status", androle="alert"only forstatus="destructive", so a notice that isn't urgent no longer interrupts a screen reader. Passroleto override. (#496) -
Deprecated, to be removed in 1.0:
FloatButton/FloatButtonGroup(not part of the design system),LegacyTaskPanel*(unused), andLiveAgentActivityBar*(the design system shows working agents where they appear). (#488) -
The package includes its MIT
LICENSE, and its metadata points to raft-design.botiverse.build. (#487)
Fixed
Skeletonstops pulsing underprefers-reduced-motion. (#496)EmptyStateIconis hidden from assistive technology, andNotificationCenterItemIconis announced as an image named by its status. (#496)
0.6.1 (2026-10-18)
Added
ListRow, the Design System list row (rf-row):ListRowGroup(the inset card),ListRowIcon,ListRowBody/ListRowTop/ListRowBottom,ListRowTitle,ListRowDescription,ListRowMeta,ListRowValue,ListRowChevron. Variantsdefault(64, hairline from the text),plain(setting row, 48) andentry(48, no hairlines);tone(unread/muted/away),selected(cyan-soft),disabled, andinteractive={false}for a display-only row. (#469)IconTile, the neutral tile (rf-tile) for things with no face, on the avatar size ladder (19 / 27 / 35 / 51), withwarningandinformationnotice washes;aria-hiddenunless given a role. (#470)SearchTrigger(SearchTriggerLabel,SearchTriggerShortcut): the web top bar's search box, a button drawn as a field. (#470)StatusandAvatarBadgetakepresence(online/busy/error/offline/external), from one shared colour map (typePresence). (#471)SidebarItemtakestone(RowTone, shared with ListRow) andinteractive={false}for a display-only row whose own buttons are the targets. (#471)Buttonvariant="on-frame": a frameless button on the yellow frame (rail, web top bar, desktop title bar); on the framemdandicon-mdare the DS 36px frame item. (#471)
Changed
- A disabled frameless (
ghost) icon button only greys its icon; it no longer draws the sunken box. Disabled ghost text buttons and outline icon buttons keep it. (#471) - The repository and its docs are now Raft Design; the npm package is still
raft-ui. (#472, #473)
0.6.0 (2026-10-18)
Breaking Changes
- Raft is the only look. The brutal and elegant theme families are removed.
ThemeProvidertakes only the mode (mode,defaultMode,onModeChange,storageKey,syncDom) and sets a.light/.darkclass andcolor-scheme;data-theme,useThemeFamily,Theme,SetTheme,SetThemeOptions,BrutalOnlyandElegantOnlyare gone. Tokens live on:where(:root),.lightand.dark. (#467) - Props that no longer change anything are removed:
Badgeuppercase;Checkboxsize,variant,color;Statuspulse,attention(status never animates);Switch/SwitchThumbsize;SidebarItemvariant;SidebarItemCount/SidebarItemIndicatorvariantandappearance(the count is always the pink sticker, the indicator the yellow @);NotificationCenterActionButtonvariant;DrawerContentwidth(one-go forms use the wideDialog);AppRailItemIndicatorattention;AppRailItemAttentionchildren;Stepssize,track,renderIndicator,renderTraveler;FilePreviewBadgetype,color. Remove them at call sites. (#467) - Parts and exports removed:
TabsBackground,TabsIndicator,MessageReferenceBadge,AppRailItemAttentionMask,IslandIndicator,OtterTraveler,tabsRecipe,taskStatus,taskStatusBorder,SidebarItemVariant,FilePreviewBadgeColor,FilePreviewBadgeType,StepIndicatorState,StepsSize,StepsTrack,StepTravelerState.OtterIndicatorstays as a standalone illustration. (#467)
Added
CheckboxIndicator: the 20px checkbox box as a visual part for picker rows. (#467)FilePreviewBadgeplacement="inline": the file-type chip before a name ("corner"stays the default). (#467)- The Raft Design System lives in the repository's
design/folder: pages, component guides with reference previews, tokens, fonts and assets. (#467)
Fixed
- A focused mention, filter trigger or active sidebar row keeps its ink edge: inset sticker edges are
inset-ring, so the focus ring no longer replaces them. (#467) - A
.darkscope under a light root no longer inherits the lightforeground-active/foreground-hover/line-field/line-field-hoveraliases. (#467) - Disabled outline, ghost and danger-outline buttons no longer change on hover. (#467)
- The thread panel head's actions end on the 20px edge like every other head; panel, section, drawer and thread heads share one edge rule. (#467)
- Horizontal
Stepsdraw their connector (the column was 0px wide). (#467) dark:classes that restated a token no longer override a caller's class in dark mode. (#467)
Changed
Buttontransitions are colour-only (notransition-all);xs/smrender asmd,icon-xs/icon-smasicon-md. (#467)FormSection's first section starts flush with its container. (#467)
0.5.64 (2026-10-15)
Changed
SidebarItemThreadCountdraws the package'sThreadIcon(was lucideMessageSquare), at 12px with a 1.75 stroke; badge height is unchanged. (#463)
0.5.63 (2026-10-15)
Added
MessageActionCardHeaderDescription: a line under the title, placed insideMessageActionCardHeaderTitle, for what happened on a Done or Declined card or why you cannot act on a View only one. It stays visible while the card is folded. (#461)
Changed
MessageActionCardHeaderaligns to the top: icon, status and collapse toggle line up with the title's first line when the title wraps or carries a description. (#461)MessageActionCardNoticeis a tinted block (Elegant: soft fill, rounded; Brutal: inset fill, hairline border) meant to open the content, above the properties; it now carries a bottom margin instead of a top one. (#461)MessageActionCardHintLabelandMessageActionCardScopeListLabelmatch the property label style in Elegant;MessageActionCardActionsNotereads in the foreground colour; the notice icon is centred on the first line. (#461)
0.5.62 (2026-10-15)
Added
AvatarBadgeacceptsoffset?: [x, y]— the badge centre measured from the avatar's top-left: numbers are px, strings are CSS lengths, percentages are of the avatar's size (["100%", 0]= top-right at every size and theme). Omitted, the theme's default corner and the recipe class are untouched. (#459)
0.5.61 (2026-10-14)
Added
MessageActionCardis a disclosure:defaultOpen/open/onOpenChange, andMessageActionCardCollapseTrigger(chevron toggle,aria-labelrequired) folds the content with a 200ms height animation (off under reduced motion). (#454)- Header parts
MessageActionCardHeaderIcon(type icon; a generic action icon when given no children),MessageActionCardHeaderTitleandMessageActionCardStatus(state label: solid and uppercase in Brutal, soft in Elegant). (#454) MessageActionCardHintLabel("Reason") above the agent's italicMessageActionCardHintquote, andMessageActionCardNoticefor Raft's note about what the action does or who may run it: one plain line with a default info icon (iconreplaces it). (#454)MessageActionCardScopeListViewportscrolls a long permission list inside a cap that cuts through the third row (132px Brutal, 172px Elegant).aria-labelis required; it is a Tab stop only while the list overflows. (#454, #457)MessageActionCardActionsNoteholds state text on the left of the actions. (#454)
Changed
MessageActionCardActionsaligns its buttons to the right. (#454)- Brutal
MessageActionCardframe: 2px border, 2px hard shadow, tinted title band ruled off at 2px (the rule goes away when folded). (#454)
0.5.60 (2026-10-14)
Added
SidebarItemThreadCount(thread icon + number of followed threads with new replies, with a visually hidden, localizablelabelfor screen readers) andSidebarItemCountSeparator(the dot between counts), so a channel row'sSidebarItemCountcan read5 · [thread] 1. Both also work as quiet text insideSidebarItemMetafor muted channels.SidebarItemCountstays as tight as a plain number when it holds the thread icon. (#455)
0.5.59 (2026-10-14)
Reverted
- The
--layer-framesurface token and ElegantAppRailpainting it, released in 0.5.58, are taken back out until the app-frame colour is confirmed. Consumers should skip 0.5.58 and take 0.5.59. (#452, reverts #449)
0.5.58 (2026-10-14)
Added
StepstakesrenderTraveler(state)ontrack="river": one traveler (drawn from{ index, status }) swims upstream from marker to marker. With a traveler each step after the first is the reach that leads into its marker, the river is full all along and runs back toward the first step, and the markers look as the traveler has got to them: it lands at each finished marker in turn (never skipping one), rests and sets off again, so a marker never shows finished ahead of it. Leg timing comes from--r-steps-traveler-arrive-durationand the new--r-steps-traveler-rest-duration(0s under reduced motion). The plain river track is unchanged. NewStepTravelerStatetype. (#445)IslandIndicator(an island marker that flies the Raft flag once finished, red shallows on error) andOtterTraveler(an otter forrenderTraveler). (#445)--layer-framesurface token (bg-layer-frame) for the app frame (top bar and rail); ElegantAppRailpaints it. (#449)
Fixed
TaskChip/TaskStatusBadgesize="sm"take thesmBadge's own box (padding, border, line height), so it no longer drifts from the Badge beside it. (#450)
0.5.57 (2026-10-14)
Added
OtterIndicator: an otter step marker forStepsrenderIndicator, taking a stepstatus(waitsleeps on the dry bank,processpaddles on its back,finishhugs its parcel,errorshivers as the parcel floats off). Use withtrack="river"and--r-steps-indicator-size: 48px; its CSS now ships in the published package. (#441)
Fixed
Stepstrack="river"in a wide row sizes each step to its own content and shares the remaining space evenly, so every river reach is the same length even when titles differ in length. The plain connector and the narrow layout are unchanged. (#442)
0.5.56 (2026-10-13)
Added
Stepstakestrack="river": the connector becomes a channel that is full of water behind finished steps, has water flowing in behind the step in progress, stalls red half way after a failed step and is a dry bed ahead. Brutal and Elegant recipes, horizontal and vertical, RTL; under reduced motion the water stands still. In a column,--r-steps-river-overlap-start/--r-steps-river-overlap-end(default 0) reach the channel into a custom marker above / below. (#439)StepstakesrenderIndicator(state), which draws a step's marker from{ index, status, current, marker }. The slot keeps the marker size (--r-steps-indicator-size) and drops the default box; returningundefinedornullkeeps the default marker for that step. NewStepsTrackandStepIndicatorStatetypes. (#439)
0.5.55 (2026-10-12)
Fixed
TaskChip/TaskStatusBadgesize="sm"now also match thesmBadge's font weight and letter spacing (Elegant 500 / 0.22px, Brutal 700), so the chip no longer reads as a different font beside the Badges in slock's Activity card row. The default size and the inline variant are unchanged. (#437)
0.5.54 (2026-10-12)
Added
TaskChipandTaskStatusBadgetakesize="sm", which gives the chip thesmBadge's text size and height (Elegant 11px / 18px, Brutal 10px / 20px; icon 10px) for a chip that shares a row with Badges, such as slock's Activity card metadata row. The default size and the inline variant are unchanged. (#435)
0.5.53 (2026-10-12)
Fixed
MessageListno longer lets wide content (aw-maxtab strip, a long code line) stretch every message past a narrow list on a desktop viewport: itsScrollAreaContentresets Base UI's inlinemin-width: fit-contentto 0, so the content keeps the list width and wide children scroll inside their own box. (#431)
0.5.52 (2026-10-12)
Fixed
- The collapsed system-note toggle's box is its real clickable height: no element overlaps (the historic negative margin is gone), both themes read
pb-0, one seam value serves every note kind (data-follows-systemis a plain marker again), and the 12/6/12 rhythm is unchanged. (#425 → #426 → #427, consolidated pre-release)
0.5.51 (2026-10-11)
Fixed
- The system note's seams read 12 after the marker fix: the follower's top lands
pt-[12px](elegant) /mt-[5px](brutal); the single-note lead reads 3 / 0; the group layer flattens (title top 1 / 0, disclosure keeps its padding for the tap area behindrelative); in-group note leads re-hang app-side. (#423)
0.5.50 (2026-10-11)
Changed
- The narrow tier joins the rhythm: the mobile group break lands 20 via the group-first
pt-[13px], and the narrow avatar image reads 28 through--mi-avatar-narrow(default1.75rem) in both themes; brutal's narrow shift keeps its default 3. (#421)
0.5.49 (2026-10-11)
Fixed
- The message-row avatar centers the box that occupies its slot (the image-level
mx-autowas inert — the slot now flex-centers its child, putting the image's left edge back at 22). (#419) - Brutal's +3 image shift becomes
--mi-avatar-shift(default 3px) so the small font tier can set 0 — removing the tier's row-propping and the 1.4px overshoot. (#419)
0.5.48 (2026-10-11)
Changed
- The system note keeps a net 12/12 rhythm: the note owns a 4px lead-in and no trailing margin, and the message that follows cancels the rest of the seam via
data-follows-system(elegantpt-[10px]; brutalmt-0with the note's ownpt-0.5on its different row rhythm). Expanded note groups read 6 apart. (#417)
0.5.47 (2026-10-11)
Changed
- Group interiors tighten to 12: the followed-by collapse splits by follower kind —
data-[followed-by=compact]:pb-0.5(interiors) vsdata-[followed-by=group]:pb-1.5(breaks stay 24). (#415) - Message-row avatars read at 32 via
--mi-avatar(default2rem; the app raises it to 34px on the large font tier), horizontally centered in the unchanged 36 slot — the text line and header icon keep the 68 line, and the shorter avatar column stops propping the small-tier first row. (#415) - Per-theme avatar alignment: elegant keeps top alignment; brutal shifts the image 3px down to meet its name line's optical position (provisional — Josh's post-merge capture decides 2 vs 3). (#415)
0.5.46 (2026-10-11)
Changed
- Message rhythm becomes marker-driven: the dead sibling collapse (
has(+[data-compact=true]), blinded by per-message wrappers) is replaced by an explicitdata-followed-byon any row another row follows — rows collapse their bottom padding to land 16 inside a group; group-first rows takept-[17px]so every group break measures 24; the reply-card row collapses identically. Brutal's own rhythm is untouched. (#412) - Docs:
packages/ui/TYPOGRAPHY.md(Typography & Colour Foundations v2) with the review fixes and the corrected brutal clause. (#410, #411, #413)
0.5.45 (2026-10-11)
Changed
- Dark elegant
hint/placeholderdarken to 0.68/0.64 (explicit dark block + the pre-hydration mirror) so the note tiers separate the way they do in light; both stay above the 4.5 readability line. (#408) - The reply-preview time aligns to its own theme's header time: elegant reads
font-sansat 11.5px tabular (drops mono), brutal takesfont-monoat 11.5px tabular (it was the odd sans one out). (#408)
0.5.44 (2026-10-11)
Changed
- The message header's right-side reserve (kept clear of the hover toolbar) becomes variable-driven:
pr-(--mi-hdr-pr,6rem)with the 6rem default unchanged, so narrow hosts — the Thread panel inside a wide window — lower it locally instead of relying on viewport breakpoints. Contract test pins the variable and the default in both themes. (#406)
0.5.43 (2026-10-10)
Changed
- Elegant colour ladder lands as decided: panel titles move to
foreground(base) under the 17/600 line; unselected tabs rest onhintin both elegant tab compounds (operable 13px text clears 4.5:1; the segmented variant's own selected token is untouched); message notes (meta / time / inline status) move tohintand the sender name goes 500→600 in the same content ink;--foreground-placeholder(elegant light) rises 0.58→0.55 for 4.85:1 on white and 4.56:1 on the header tint. Brutal unchanged this round. (#404)
0.5.42 (2026-10-10)
Changed
- Elegant panel titles sit at semibold: one title size (17) with weight and colour carrying the hierarchy (sidebar 700, panel 600). Brutal keeps its own bold 16. (#402)
0.5.41 (2026-10-10)
Changed
TaskStatusBadgenow renders throughTaskChipand has no look of its own: a status badge in a task panel and the task chip under a message share one shape, size, border and colour and differ only in content (status icon + the caller's label). Visible change: in Elegant the badge goes from 12px to the chip's 13px with a hairline border and a 19px height; in Brutal from bold 12px at 20px tall to the chip's medium 13px at about 18px tall, so rows that carry it are about 2px shorter.data-slot="task-status-badge"anddata-statusare kept; no API change. (#398)
0.5.40 (2026-10-10)
Fixed
- Elegant
PanelHeader's icon chip:fill-mutedmeasured 1.04:1 against the canvas-muted header (effectively invisible) and becomesfill-strong; avatar slots declaredata-icon-kind="avatar"and drop the chip entirely, so a round avatar no longer sits on a square. Brutal's black-bordered chip is unchanged. (#399) - Elegant
ThreadPanelno longer casts a dark drop-shadow over the chat divider: the leftward gradient (and its paired vertical ambient shadow) is removed. (#399)
0.5.39 (2026-10-10)
Fixed
- Brutal
TaskStatusBadgedraws a 1px black keyline instead of 2px, matchingTaskChipandBadge;py-pxkeeps it exactly 20px tall, so surrounding layout does not move (Elegant unchanged). (#396)
0.5.38 (2026-10-10)
Fixed
TaskChip'sinlinevariant no longer paints its own neutral fill and transparent border over the status styling: task references inside message bodies take the same per-status fill, border, text and icon colours as the task chip under a message (Elegant; Brutal unchanged; font size and baseline rules untouched). (#394)
0.5.37 (2026-10-10)
Fixed
- Brutal
Comboboxkeeps its shadow when open, asSelectdoes: the control lifts 1px withshadow-raft-mdinstead of dropping the shadow. In Brutal,SelectContentandComboboxContentnow defaultsideOffsetto 8px (was 4px), so the open trigger's shadow stays clear of the popup instead of running into it. Elegant keeps 4px; an explicitsideOffsetstill wins. (#392)
0.5.36 (2026-10-09)
Changed
- Panel alignment: the elegant
PanelHeaderputs its icon box on the 20px line and the title on the 68px line (36px to 20px atmd+, gap 8 to 12);MessageItemrows in both themes carry the 8px inset (7px padding + 1px border, 6px + 2px in Brutal) that lands the avatar on 20 and the text on 68 next to the 12px the app shell keeps. Brutal also drops the row'smx-3, taking the mobile avatar inset from 32 to 20. The thread panel uses the chat panel's figures. (#388) - Elegant
Tabsunderline: the bar starts on the 20px line at every width (the first icon lands at 32), and the strip has one source now — the indicator defaults tobg-primary-400and the placement blocks no longer draw a static hairline, so the conversation panel, the agent-details strip and the docs read the same look. Brutal underline: the conversation panel drops its asymmetric first-tabpl-[22px]; the strip sits at 16 and reads centred, as the agent-details strip. (#389) MessageRepliesstays on the message text line: the dead right margin is gone (a 100%-wide block drops an over-constrained-mr-2) and a browser spec now pins the rendered right edge, so a class-only assertion can no longer regress it silently. Run it withvp test --browser.enabled. (#389)
0.5.35 (2026-10-09)
Changed
- Layout rules across the library no longer style descendants from an ancestor
:has(). Selectors of that shape make Chromium fall back to recalculating the whole page's style on unrelated mutations — measured in the app, one such rule costs over 5ms per text-node change in a 300-message channel (~13.8k elements); this release removes the 28 in this library. Rendering is otherwise unchanged; the caller-visible points follow. (#386) SidebarSection's attention badge no longer hides on its own while a section menu is open. The section's own open state and header hover still hide it; to also hide it while a menu is open, the header must mirror the menu's open state onto itself asdata-popup-open(the docs composition shows the wiring). (#386)Banner's action spans two rows only when it comes after both the title and the description; an action placed before them no longer spans. The status column now always spans two rows. (#386)InputGroupaddon and text colours are driven by variables on the group root. A caller-provided text-colour class on those parts replaces their colour in every state, focus and disabled included. (#386)ToastContentmeasures its rendered content and writesdata-has-icon,data-has-descriptionanddata-has-actionson itself; the content layout keys off these attributes instead of descendant selectors. (#386)
0.5.34 (2026-10-09)
Fixed
- In a horizontal
Steps, the connector follows the title on its line and runs on to the next step's marker, as in naive-ui; the description goes on the line below, under the title and the connector. A description wider than its title no longer shortens the connector to a stub. Below 640px the stacked layout (text under the marker) is unchanged. CSS only; the DOM is the same. (#384)
0.5.33 (2026-10-09)
Changed
Stepsmarkers take the task-status colours shared withTaskChip(--task-status-*), so each status has its own colour: finished = Done (green), in progress = In Progress (blue), failed = danger (red), and waiting is neutral grey (the Closed ink). Brutal: solid status fill, ink outline and ink text, as the BrutalTaskChip. Elegant: the status's soft wash, strong ink and a 35% hairline, as the ElegantTaskChip; waiting steps follow naive-ui with no fill and the ring, number and title in the disabled grey. (#381)
0.5.32 (2026-10-09)
Added
- Added
StepsandStep: numbered steps through a process, as an ordered list. Steps beforecurrentare finished, the ones after it waiting, and the current step takesstatus(process,finish,errororwait) andaria-current="step". Supportssm/mdsizes, averticallayout, customfinishIcon/errorIcon/ per-stepicon, screen-readerfinishLabel/errorLabel, and clickable steps throughonUpdateCurrent(withdisabledsteps). Below 640px a horizontal Steps stacks each step's text under its marker. Styled for Brutal and Elegant from semantic tokens;steps.cssships instyles.cssand asdist/components/steps/steps.css. (#378)
0.5.31 (2026-10-08)
Fixed
TaskStatusBadgein Brutal is a square, hard-edged tag again (no radius, 2px black border, bold, 20px tall), matching the BrutalTaskChipandBadge. 0.5.30 rendered it as a rounded pill. Elegant is unchanged.
0.5.30 (2026-10-08)
Added
- Added the
--task-status-<status>colour tokens (with-softand-strong) for the five task states, defined for Brutal, Elegant light and Elegant dark, and exposed to Tailwind astask-status-*. - Added
TaskStatusBadge: a status badge that takes astatusand caller-provided text. - Added the
taskStatusprop onTimelineItem, which paints the marker from the status colour. Omitting it leaves the item unchanged.
Changed
- Task status colours now come from the tokens above everywhere (
TaskStatusIcon,TaskChip, task card, board and list). In Elegant this changes three states so each status keeps one hue family across themes: Todo grey → orange, In Progress yellow → blue, In Review orange → purple. Closed uses the disabled foreground colour. Brutal colours are unchanged. TaskStatusIconrenders the lucideCircle,Play,Eye,CheckCircleandBanglyphs instead of the previous hand-drawn icons, and forwards SVG props.
0.5.28 (2026-10-07)
Added
- Added the composable
AnnouncementDialogthroughraft-ui/wip, built on existing Dialog and Button primitives, with a label, picture, scrolling body and fixed footer. Narrow dialogs stack both actions at full width. (#363) - Updated the canonical announcement example to use the new component. Existing Dialog, Button and theme behavior remain unchanged.
0.5.1 (2026-08-12)
Fixed
- Removed the duplicate inset outline from Elegant dark
ThreadPanelContenton large screens, leaving the header/content seam with a single visual owner. (#213)
0.5.0 (2026-08-10)
Breaking Changes
- Brutal
SidebarItemactive styling is neutral by default. Active items now use a white surface instead of pink. Passvariant="accent"to preserve the previous pink treatment, orvariant="primary"for yellow. (#207) - Elegant
PanelHeaderIconnow owns its container treatment. It renders as a 36px rounded muted surface instead of a bare auto-sized slot. Remove redundant consumer wrappers and update layouts that relied on the previous intrinsic size. (#208)
Added
- Graduated the composable
ActivityInboxshell, sidebar, selection toolbar, and standaloneInboxItemparts to the stable package entry point. (#209) - Graduated the Search shell and entity, message, and thread result primitives to the stable package entry point. (#210)
- Graduated
PickerTriggerButtonfor compact Select and Combobox filter triggers. (#208, 0fef4c1) - Added
beam={false}toLiveAgentActivityBarto disable the Elegant animated border and glow while preserving enter and exit transitions. (#211)
Changed
- Icon-only
MessageFooterSavedIndicatorcontrols now receive dedicated compact sizing and theme interaction styling. (#209)
0.4.0 (2026-08-07)
Breaking Changes
- Layer tokens renamed.
--layer-pageand--color-layer-pageare removed in favor of--layer-canvas/--layer-canvas-muted(plus new--color-layer-canvasand--color-layer-canvas-muted). Update any custom CSS or utility classes that referencedlayer-page. (2b9b5b2) - Invalid-state styling now keys off
data-invalidinstead ofaria-invalid.Input,Textarea, and form controls style the error visual fromdata-invalidand mirror it toaria-invalid; passingaria-invalidalone no longer shows the error style. Update callers to passdata-invalid. (b3786da) CopyableCodeActionlabel props removed.ariaLabelandcopiedAriaLabelare gone; use the standardaria-labelprop instead. The default label still swaps between "Copy code" and "Copied code". (b3786da)PanelHeaderIconno longer sizes its icon. The slot dropped the[&_svg]sizing (brutal 18px / elegant 14px stroke 2.5); consumers must now size icons themselves, e.g. with asize-*class or thesizeattribute. (0295c2e)
Changed
- AppShell no longer draws column dividers.
AppShellRailSlotandAppShellSidebarSlotdropped their border classes in both themes. The rail|sidebar divider now lives onAppRailRoot(brutal); seam borders the shell used to own (e.g. sidebar|main with an inset main panel) must be added by the caller. (e219fd8) - Button press feedback unified. Buttons now always apply the active press scale, including when they act as popup triggers; size variants use a consistent
rounded-smcorner radius. Select, Combobox, and DropdownMenu share the same press model. (7295b34) - Brutal polish: the active AppRail item received its hard shadow (eedc270) and the inline thread replies surface was aligned with the source app's wash style (4974cfc).
Fixed
- Switch dark mode. The unchecked thumb resolved to near-white in dark scope (
--inkis "white ink" there) and is now a mid-dark warm gray; disabled thumbs are desaturated versions of their normal-state colors in both light and dark; the dark disabled track uses a black ring instead of the bright--line-mutedhairline. (7c81bd3)
0.3.0 (2026-08-06)
Breaking Changes
- Replaced the previous surface, line, state, shadow, and motion CSS names with the semantic token contract used by the completed Elegant dark theme. Migrate
layer-muted/layer-strongtofill-muted/fill-strong,line-subtletoline-muted, lighter borders toline-hairline,state-<tone>-*to the corresponding semantic tone family,shadow-*toshadow-raft-*, andease-sheettoease-slide. Thesafe-top,safe-bottom,safe-left, andsafe-rightutilities are no longer provided. (#178) - Renamed
PanelHeaderFrametoPanelHeaderRootand updated the corresponding type anddata-slotfrompanel-header-frametopanel-header-root. Panel header parts remain available from the package root and are now owned by the shared Panel vocabulary. (#175) - Renamed
MessageForwardedBundleToggletoMessageForwardedBundleFooterAction, including its props type anddata-slot. (#189)
Added
- Completed Elegant dark mode across the component library, with dark-aware page, panel, inset, card, popover, backdrop, and HUD surfaces; semantic text, border, state, and interaction colors; elevated shadows; native color-scheme integration; and correct nested theme-scope behavior. Dialogs, menus, form controls, messages, task surfaces, navigation, search, activity, and multi-panel layouts now adapt consistently when rendered in dark mode. (#178, #180, #181, #195, #196, #204)
- Added the composable
Cardcomponent withdefaultandinsetvariants and the finalCardHeader,CardLeading,CardTitle,CardDescription,CardTrailing,CardContent, andCardFooterparts. (#184, #202) - Graduated
ProfilePanelandToggleGroupto the stable package entry point. (#197) - Added
AppShellPanelSlotandAppShellOverlaySlot, overlay-aware main slots, attached/inset Panel edges, and the sharedPanelActivityheader part for composable multi-panel layouts. (#175, #198, #199) - Included the raft-ui guide and critique agent skills in the published package. (#127, #193)
Changed
- Aligned existing components with the new semantic theme contract so surfaces, text hierarchy, borders, focus treatments, state colors, and elevation remain consistent across Brutal, Elegant light, and Elegant dark themes. (#178)
- Upgraded the Base UI runtime dependency from 1.6 to 1.7. (3a76bec)
Fixed
- Normalized Button sizing and focus treatments and restored the Brutal danger-outline lift interaction. (#174, #183)
- Restored component-owned Combobox input styling and corrected Select, Combobox, Input, Textarea, RadioGroup, and Switch interaction states across light and dark themes. (679de8a, ef814f9, #179, #186)
- Preserved message attachment order and spacing and improved reference and avatar alignment in message rows. (c9118b7, #194)
- Restored App Rail state contrast, opaque Avatar group rings, panel resizing behavior, and Toast dismiss styling. (#180, #188, #196, fcf0d14)
0.2.1 (2026-08-01)
Fixed
- Restored the Brutal Toast action and close button treatment. (#173)
0.2.0 (2026-07-31)
Breaking Changes
- Renamed the stable
ChatSidebar*compound API toSidebar*. Replace eachChatSidebar-prefixed component and prop type with itsSidebarcounterpart, and update selectors fromchat-sidebar-*tosidebar-*. (#167) - Generalized panel chrome: replace
PanelHeaderwithPanelHeaderFrame, and replaceConversationPanelActions,ConversationPanelHeader*,ConversationPanelHeading,ConversationPanelMeta,ConversationPanelSectionHeader,ConversationPanelStatus, andConversationPanelTitlewith theirPanel*counterparts. Update selectors targeting the renameddata-slotvalues. (#169) - Renamed task status values from
in_progressandin_reviewtoin-progressandin-review. (#159)
Added
- Added stable
CodeBlock*and semanticMarkdown*compound primitives. (#132, #133) - Added the stable message core:
MessageItem*, reactions, references, selection toolbars, quoted previews, reply previews, and system messages. (#143, #150, #156, #160, #161, #163) - Graduated the remaining message surfaces to the stable entry: action cards, attachments and media previews, embeds, forwarded bundles, translation parts, and message lists. (3bf4790)
- Graduated
DescriptionList, Files panel primitives, andLiveAgentActivityBarto the stable entry.DescriptionListincludes horizontal layout support. (#120, #131, #168) - Added
TaskChip, named task-status icon exports, andPanelSection. (#159, #169)
Changed
- Added consistent
data-slotattributes to the retained core Panel parts. (#169) - Elegant badges now preserve caller-provided casing when
uppercaseis set; Brutal badges continue to render uppercase. (#156)
Fixed
- Fixed conditional-hook rendering in
AppShellSidebarTriggerandLegacyTaskPanelRoot, and corrected sortable Composer drag-overlay mapping. (#128) - Aligned the AppRail attention indicator. (#165)
0.1.0 (2026-07-24)
Breaking Changes
- Flattened
CopyableCode: the preset andCopyableCode.Codecompound API are removed. ComposeCopyableCodeRoot,CopyableCode, andCopyableCodeAction;CopyableCodeCodePropsis gone and data-slots were renamed. (#76) - Removed public shadow CSS tokens/utilities:
shadow-soft-popover,shadow-brutal-sm,shadow-brutal, andshadow-brutal-lg. Use theme shadows (shadow-sm…shadow-xl) or inline values. (#98, #104)
Added
- Graduated
AppRail,AppShell(with sidebar collapse trigger),ChatSidebar,Composer(including sortable attachments), andResizable*to the stable entry. (#55, #68, #88) - Added
ConversationPanel,ThreadPanel,Panel,ScrollArea,FilePreview,RaftLogo/ lockup / wordmark, andMobileNav. - Added the task board / card / dnd / list / panel suite and
LegacyTaskPanel. (#99, #100, #101, #102, #103, #115) - Extended
Lightboxwith thumbnail strip and related chrome parts; addedComboboxTriggerCountandContextMenuPopup. (#73, #89) - Added safe-area utilities:
safe-top,safe-bottom,safe-left, andsafe-right.
Changed
- Unified elegant focus rings across inputs and controls. (#114)
AvatarBadgeis auseRendershell for custom indicators; the default dot still inherits parent size. (#94, #107)- Input invalid styling is driven by
data-invalid(still mirrored fromaria-invalid). (#82, #105) - Banner status surfaces and action layout refined. (#77, #106)
- Elegant menu item presentation tuned. (#90)
cnnow merges classes withtailwind-merge(theme shadow-aware). Package dependencies droppedborder-beamand addedtailwind-merge.
Fixed
- Tooltip leading icon aligns with content. (#109)
- Avatar badge inherits size from the parent avatar. (#107)
- Banner action height follows content. (#106)
- Thread panel search chrome alignment. (#71)
0.0.22 (2026-07-14)
Changed
- Let elegant dropdown menu triggers inherit hover and focus styling from their rendered button instead of applying menu-specific overrides. (#44)
Fixed
- Fixed stacked toasts losing hover state while the pointer crosses gaps between visible toasts. (#43)
0.0.21 (2026-07-14)
Added
- Added
LiveAgentActivityBarcompound parts (Row,Avatar,Status,Content,Text) for composable live agent activity status. (#38) - Added generic
ResizableGroup,ResizableHandle, andResizablePanelprimitives for resizable panel layouts. (#37)
Changed
- Extracted
AppShell,AppRail, andAppPanelStackout of chat-shell into standalone components, with sidebar resizing now built on the generic resizable primitives. (#37) - Refined drawer snap point interactions and drove drawer styling through recipe variants. (#39, #40)
Removed
- Removed
DrawerSwipeHandleand theshowSwipeHandleprop; the swipe handle now renders automatically when snap points are set. (#39) - Removed
AppShellMobileBar,AppPanelResizeHandle, andChatSidebarResizeGroup/ChatSidebarResizeHandle/ChatSidebarResizePanelin favor of the shared resizable primitives. (#37)
0.0.20 (2026-07-14)
Added
- Added
ContextMenuItemLabel,ContextMenuFooter,DropdownMenuItemLabel, andDropdownMenuFooterfor truncatable menu labels and structured footer content. (#30) - Added opt-in selected, emphasized, and signal/accent interaction styling for brutal dropdown menu items. (#30)
Changed
- Improved elegant combobox, context-menu, dropdown-menu, and select presentation with roomier variable-height items and consistent label spacing, and removed visible focus rings from combobox, dropdown-menu, and select triggers. (#26)
- Enhanced drawers with directional nested stacking and faster, swipe-aware motion while preserving the existing component API. (#32)
- Refined sortable-tab cursor feedback and elegant underline-tab spacing, and simplified elegant attention-status styling. (#30)
Fixed
- Fixed stacked toasts so top placements animate and swipe from the correct edge and right placements stay right-aligned. (#31)
- Fixed theme-family and explicit light/dark propagation across portaled dialogs, drawers, menus, comboboxes, selects, lightboxes, notifications, popovers, preview cards, toasts, and tooltips. (#36)
0.0.19 (2026-07-13)
Features
- chat-shell: make elegant panel header title inline (d941a18)
- chat-shell: polish elegant chat sidebar and playground (e8150cb)
- chat-shell: polish elegant dm demos and notif trigger (6a272c4)
- chat-shell: refine elegant workspace experience (bd358d0)
- drawer: add responsive placement and mobile full width (dedfa89)
- status: add elegant attention prop with pulse compose (8d3769e)
- status: polish attention and document trigger glow (cc7dba8)
- theme: add theme-only rendering helpers (#3) (0363ae0)
- ui: add chat main and thread panel parts (db63532)
- ui: add chat page secondary surfaces (08459d1)
- ui: add chat shell app shell parts (eb597a3)
- ui: add message timeline and thread parent parts (cce48c6)
- ui: add resizable chat sidebar (1324892)
- ui: add sortable chat sidebar list primitives (929186e)
- ui: align playground chat, tasks, and files with source (#11) (0b8c9ee)
- ui: refine chat app shell docs parity (a3f808a)
Bug Fixes
- badge: tune soft badge colors (00db53e)
- chat-shell: remove elegant sidebar badge outline (7a15242)
- chat-shell: stabilize elegant sidebar item height (2dc935e)
- doc: scope preview themes and fix source css collisions (55d5f02)
- docs: unblock rsc deploy (d3cb663)
- doc: tighten inline code and lead line height (20dc9ba)
- ui: align message item states with source (ff88cc6)
- ui: align tooltip icons and tidy doc previews (61dfeb9)
- ui: brutal sidebar selected item keeps medium weight to match source (ec0fbe4)
- ui: close 1px seam at resizable sidebar/main divider (01d90f6)
- ui: handle empty tv recipes (#7) (3ee3307)
- ui: import composer data attributes correctly (7f591b7)
- ui: move body↔composer separator from footer to body slot so the composer is self-contained (brutal, visual-identical) (685b3bc)
- ui: refine elegant focus rings (c5b8733)
- ui: restore input focus rings (4c57774)
- ui: scope dark: variant to elegant theme context (a5070b4)
- ui: share composer and align thread footer (8e5d758)
- ui: stable useId dnd context id for SortableTabsList to fix SSR hydration mismatch (9673b22)