Changelog
Every notable change to Beaket UI, newest first.
@beaket/ui#
4.2.1#
Patch Changes#
-
#1170
368e504Thanks @jihnma! - Describe the package as what it is: React + Tailwind components the CLI copies into your project, not only a CLI. The npm description dated from when the package was the CLI alone and was not updated when the registry joined it. -
#1168
22dfe95Thanks @jihnma! - DropdownMenu: a disabledSubTriggernow renders infg-disabledink and drops pointer affordance, like the disabledItem,CheckboxItemandRadioItemrows beside it.Root cause: Radix marks a disabled sub-trigger with
data-disabled, butSubTrigger’s class list was the only menu row without thedata-[disabled]:text-fg-disabled data-[disabled]:pointer-events-nonepair, so a disabled submenu parent rendered in full ink and looked identical to an enabled one. No story rendered<DropdownMenu.SubTrigger disabled>, so neither the visual baselines nor the play tests could catch it. -
#1174
70f4bc8Thanks @jihnma! - Label and Field.Label: render the label of a disabled control infg-disabled.DESIGN.md’s Dashed-Disabled Rule gives a disabled control
fg-disabledtext, and Quiet Ink names “disabled labels” as one of that token’s jobs, butLabelandField.Labelset onlytext-fgand had no disabled variant. A disabled Checkbox, Radio or Switch therefore sat next to a label in full ink that still read as live.Labelnow dims when it follows a disabledpeercontrol (Checkbox, Radio and Switch all carrypeer) or wraps a disabled control.Field.Labeldims when its Field’s control is:disabledor carriesdata-disabled(Radix roots such as RadioGroup). No props change. -
#1172
d00458fThanks @jihnma! - NavigationProgress now holds a full, static bar underprefers-reduced-motion: reduce. Root cause: the global reduced-motion rule collapses the sweep to a single 0.01ms run with no fill mode, so the bar fell back to its restingw-1/3at the left of the track and read as “a third done” rather than indeterminate. The bar now addsmotion-reduce:w-full motion-reduce:animate-none.
4.2.0#
Minor Changes#
-
#996
04e10b2Thanks @jihnma! - Breaking change: rebuild all five palettes in OKLCH, as ten separately designed schemes.Light and dark are no longer one palette inverted. Every ramp is built on an even OKLab lightness step — only Solace was ever checked for progressivity, and the other four were violating it badly, with jumps of 0.17–0.20 at
tone-3→4against a 0.12 ceiling, which is why their mid-greys collapsed into each other. Nothing in the set reaches#ffffffor#000000at either end.Each palette carries its own page, and they are spread on purpose. Porcelain
#fdfdfeis the starkest ground; Solace#fdfcfathe near-white; Marigold#faf9fca bright panel with a trace of its own violet; Eucalyptus#f4f7f9a soft canvas that was never pure white; Tobacco#f6f3f0an aged stock. Dark grounds are five different rooms rather than one: a slate night#0f171e, a neutral black[#151617](https://github.com/beaket/ui/issues/151617), a warm#1b150e, a violet-black#18141f, and a grey[#222527](https://github.com/beaket/ui/issues/222527)that deliberately sits well above black.A ramp may travel in hue as well as lightness. Solace runs warm paper under cool ink. That cast is interpolated through OKLab a/b rather than by rotating hue, because rotating drags the mid-tones around the colour wheel and straight through green.
Signal hue follows convention; its treatment follows the palette. Green has to read as green, so the hue is not the palette’s to choose — but its chroma, lightness tier and temperature are. Tobacco is the quietest set and Marigold the loudest, measured rather than asserted.
Role lightness is tiered on purpose. Hue cannot carry status separation alone: tritanopia collapses blue against green, and protanopia and deuteranopia collapse red against green — and against brown, which is why a warm accent could not coexist with a red danger. Each role sits on its own lightness step, and every palette clears the project’s colour-vision floors.
Accent is never quieter than danger. The interaction colour marks what you can do; the destructive one marks a rare path. Tobacco’s accent takes the red its reference actually carries, with danger stepping down to a deeper oxblood.
Migration. Re-run
npx @beaket/ui theme <name>to pick up the palette block. Any consumer that hard-coded a palette value rather than reading a semantic token needs to re-read it; the 69 semantic names are unchanged. -
#1010
edc2485Thanks @jihnma! - Breaking change: return Solace’sinfosignal to the blue band, held apart from the accent by step rather than by hue.The regrade moved
--signal-infoto a plum at hue 315° and recorded the reason inDESIGN.md: “Plum is deliberately not a blue: the accent already owns the blue band, and an informational blue beside it would read as actionable.” The diagnosis was right and the remedy was not.The old blue’s problem was step, not hue. Solace light’s previous info
#53628fmeasured L* 0.503, C 0.074, h 269.5 against an accent at L* 0.501, C 0.195, h 263.9 — the same lightness and effectively the same hue, separated by chroma alone. That is why it cleared the project’s colour-vision floor by only 0.0485 against a 0.04 minimum, and why it read as a duller version of the thing you can click rather than a different kind of mark.Rotating 45° into plum bought margin by abandoning the convention. It also made Solace the only palette whose info left the blue band: Porcelain, Eucalyptus and Tobacco all ship a blue info, and Porcelain does it beside a blue accent at 0.0407 — a thinner margin than the Solace blue that was replaced. The stated rule was never held system-wide.
--signal-infois now[#275085](https://github.com/beaket/ui/issues/275085)light and#5f8bc6dark: the regrade’s own lightness and chroma tiers for the role, unchanged (L* 0.430 / 0.630, C 0.100), with only the hue moved from 315° to 256°. Separation from the accent is now carried by a full lightness step and roughly half the chroma, which is the relationship Eucalyptus already ships between its blue accent and blue info — and it measures 0.0900 light and 0.0743 dark, better than both the old blue and Porcelain’s shipping pair. The role’s display name follows the value from Plum to Prussian Blue.Migration. Re-run
npx @beaket/ui theme solaceto pick up the palette block. Only Solace changes; the other four palettes are untouched. Any consumer that hard-coded#643d75or#a076b3rather than reading--color-info-*needs to re-read it. -
#996
04e10b2Thanks @jihnma! - Breaking change: retune the surface, type, and instrument layers so a drawn edge means “you can act on this”.Measured on the Storybook Overview, 21.7% of rendered elements carried a border while ink fill covered 0.46% of the page — the weight was never the black, it was the lines. Containers now read by surface and drawn shade; strokes are rationed to instruments.
The page is now painted.
--color-bgwas defined and never applied — nobodyrule anywhere in the token layer or the CLI’s injected block. That was invisible while--color-bgwas near-white and the host’s default page was white, and it makes every surface claim in this release conditional, sosemantic.cssnow setsbody { background-color; color }as an element selector any consumer rule overrides.Surfaces. Solace’s light ladder landed raised surfaces 1.02:1 from the page — inside the range of its own hover fills — so every container had to draw a border simply to be seen. The direction was never the problem; the step size was. Ladder steps are now large enough to read on their own (1.08:1 and 1.16:1 in Solace light, 1.13:1 and 1.28:1 in dark), and
--shadow-sizegoes to 2px so the drawn shade, not a stroke, is what says “raised”. A ladder step means separation from the page; which way it travels is a per-theme decision, and half the light schemes now step darker than their page rather than climbing toward white. The palette values themselves are set by the accompanying regrade.Body ink.
--color-fgmoves from the ramp’s deepest step to--tone-10, taking body text from 17.7:1 to 15.2:1 against the page. The deepest step measured well over 2x the AAA threshold and read as a high-contrast accessibility mode rather than a printed page.--color-border-strongand--color-bg-emphasisstay deepest, so emphasis still wins; the One Ink, Three Pressures Rule now covers type as well as stroke, and--tone-10leaves the reserved set (28 functional palette values, not 27, with only--tone-8and--tone-9held back).Type.
--text-xs11px → 12px and--text-sm13px → 14px, with--text-xs--line-heightand--text-sm--line-heightpinned at 1.45 / 1.55 (Tailwind derives leading from its own scale, so overriding size alone left 14px text on a 1.43 ratio). Hangul packs two or three jamo into one em and kanji can carry a dozen strokes; both collapse below 12px where Latin does not.CJK.
--font-sansgains CJK families — none of the Latin entries carry Hangul or Kana, so resolution was uncontrolled OS fallback that differed per platform. One flat list is not enough: fallback runs per codepoint and a Korean face such as Apple SD Gothic Neo also covers the CJK ideograph block, so whichever script is named first captures every ideograph and the other renders in the wrong national glyph forms.@layer basetherefore re-declaresfont-familyper:lang(ko|ja|zh|zh-Hant)— re-declares rather than swaps a variable, because preflight setsfont-familyonce onhtmland descendants inherit the already-substituted value.:lang(zh-Hant)cannot infer the script from a region-only tag, sozh-TW,zh-HKandzh-MOare named alongside it — otherwise the Simplified rule stays active and Traditional text renders in Simplified glyph forms.:lang(ko)also setsword-break: keep-all, without which Korean wraps mid-eojeol (관리|하려면). All of this requires the host to setlangon the document.Alert.Titledropstracking-tight, which tightened glyphs that are already full-width. A newDesign/CJKstory renders Korean and Japanese screens so the floor has a visual baseline — nothing else in the set contained a single Hangul or Kana glyph.Instruments. Checkbox and Radio move from a 24px chassis to 16px, and
Switchdefaults tosm(28×16). Hit expanders are re-measured from the padding box, so the inset moves to-15pxand every control still clears 44×44.Containers. Card, the DataTable container, the Table wrapper and Alert drop their resting borders. Alert becomes one 2px role rule down its leading edge. Table row seams move to the new
--color-border-subtle(69th semantic name) and data cells can wrap again.Fixes. Fused-strip hit expanders overlapped their neighbours —
elementFromPoint2px inside a Navigation cell returned the adjacent one, and ~8px of every Tabs cell activated the wrong tab. Horizontal expansion is removed inside a strip and kept on its outer edges — keyed off the<li>in Navigation, where every link is alone in its own list item andfirst:/last:had matched all of them. A disabled Switch rendered track and thumb at the same grey (1.00:1) — unchecked, both landed onborder-muted; the thumb now draws infg-disabled, the disabled ink Radio’s indicator already uses, which clears both disabled track values (worst of the ten schemes: 1.79:1 unchecked, 3.34:1 checked). Taking it tobg-disabledinstead would have moved the collapse onto the checked track rather than removed it — that track isbg-disabledtoo.Card.Titlelosesleading-none, which clipped its own descenders. A read-only Input now sits flush with the page instead of being materially identical to a writable one.ring-0is removed from the Switch thumb — a live zero shadow in a system that refuses rings. A read-only Textarea gains the same treatment as Input, which had been split. Stacked bare instruments in the Checkbox, Radio and Switch stories move to a 44px pitch: a 16px chassis reserves a 44px target, so a tighter stack made the lower third of each control belong to its neighbour.Migration. Re-run
npx @beaket/ui theme <name>to pick up the palette and foundation block. Consumers who copied components earlier keep their files; re-runaddfor the component changes. Anything that pinned a 24px checkbox or radio in its own layout, or relied onSwitchdefaulting tomd, needs the size passed explicitly. Setlangon your document for the Korean line-breaking rule to apply.
Patch Changes#
-
#1020
3092754Thanks @jihnma! - Breadcrumb: widen the gap around the separator from 4px to 8px.At
gap-1the separator sat 4px from the labels on both sides, which reads as one crowded string rather than a trail. DESIGN.md spends 4px on icon gaps and 8px on control gaps; a separator standing between two labels belongs to the latter.Breadcrumb.ListandBreadcrumb.Itemboth move togap-2, so the spacing is symmetric whether the separator is composed inside the item (as the shipped examples do) or as a sibling in the list. -
#1011
a3b7a81Thanks @jihnma! - Stop treating a commented-out@import "tailwindcss"as a real Tailwind import.initpicks your Tailwind entry by looking for that import, but it tested the raw file while the setup check tested the file with comments stripped. The two disagreed, so a stylesheet whose only@import "tailwindcss"sat inside a/* */comment could be chosen as the theme target and then reported as not importing Tailwind. Both paths now share the comment-aware check.Internal tidying with no visible change: one BOM-tolerant JSON reader instead of four, one
package.jsonread for the Next.js check instead of two, and the component-levelup-to-date/outdatedstatus has been dropped fromcompareComponent—diffreports per-file status and never read it.
4.1.1#
Patch Changes#
-
#991
7cc7c5cThanks @jihnma! - Polish sweep across the component set: one motion grammar, one focus grammar, and the removal of styling that never reached the browser.Classes that produced no CSS.
tw-animate-csswas never a dependency of this repo, so everyanimate-in/fade-*/zoom-*/slide-*class copied into Dialog, Sheet, Tooltip and DropdownMenu emitted nothing — overlays have always appeared instantly, which is what DESIGN.md already documents (“no entrance choreography for ordinary controls”). Those classes are gone, along with Sheet’s now-emptysideAnimationsmap. Consumers copying these files into a project that does havetw-animate-cssinstalled lose an accidental fade.Radix height caps, written in Tailwind 3 syntax.
max-h-radix-dropdown-menu-content-available-heightandmax-h-radix-select-content-available-heightare not Tailwind 4 class names and generated no rule, so a long menu or select panel could run off the bottom of the viewport instead of scrolling. Both now use the v4 arbitrary-property form and measure correctly (max-height: 792pxin a 885px viewport).DropdownMenu.SubContentgains the same cap. The twoorigin-radix-*classes were equally dead and, with no entrance animation to originate, are removed rather than repaired.Select.Trigger’ssizeprop did nothing. It setdata-sizeand no rule consumed it; the trigger also carried no height at all, so it rendered 36.57px beside a 36px Input.sizenow selects from the documented height ladder —h-9(36px) beside Input and a default Button,h-8(32px) beside a small one.Sheet’s gap never applied.gap-4sat on adisplay: blockelement, which is whySheet.Headercarried anmb-4to compensate while nothing else in the panel was spaced. Sheet now stacks withflex flex-col gap-4at 24px padding, and the header’s compensating margin is removed — measured parity with Dialog: 24px padding, 16px between sections, 32px before the footer. (Dialog’s owngridis deliberately not copied here: a left/right sheet ish-full, and a grid taller than its content stretches every row — it put the header, body and footer in thirds of the viewport.)One motion grammar. Button’s transition list omitted
background-color, so its hover fill snapped while its edge faded; Switch’s thumb, Pagination’s cells and the Dialog/Sheet close buttons ran at Tailwind’s default 150ms, and Slider’s thumb had no transition at all. All are 100ms now, leaving Progress as the system’s single 150ms — which is what DESIGN.md’s Motion section claims.One focus grammar. Table’s scroll region was the only focus outline in the system without
outline-offset-2.Consistency. Navigation and Tabs now share body weight (DESIGN.md calls them the identical grammar; Tabs was 500 and Navigation 400).
Select.LabelandDropdownMenu.Labelare one role and now one treatment — Label type, 11px/500 in muted ink. The menu leading rule is named once indropdown-menu.tsxinstead of hand-written in five places.DropdownMenu.Shortcutno longer recolors to accent on a highlighted row, where the label is meant to stay ink. Alert hoists what all five variants share into its base. Pagination’s ellipsis is a real ellipsis, and icons usesize-4throughout. -
#993
c7bc59fThanks @jihnma! - Skeleton breathes in ink instead of fading.animate-pulsecyclesopacityfrom 1 to 0.5, which is the one thing this system never does: opacity is not a styling device here, and a fading placeholder dims whatever sits behind it rather than re-inking the block itself. It was also the last component contradicting the Drawn-Not-Lit rule.The block now moves one step along the neutral ramp and back —
bg-hover↔bg-active— on a new--animate-skeleton-pulsetoken in the semantic layer. The cadence is unchanged at 2s, so only the channel moved; the placeholder reads the same, it just does it in ink. Both ends are semantic names, so the pulse follows a palette swap and reads correctly in dark mode.Because the keyframes drive
background-color, aclassNamebackground is painted over while the animation runs. Recolor by editing the keyframes — the file is yours.animate-nonewill not stop it: tailwind-merge cannot dedupe a customanimate-*utility against it, the same gotcha that stopsshadow-noneopting a variant out ofshadow-offset-action.style={{ animation: "none" }}works, andprefers-reduced-motion: reducealready collapses it globally.
4.1.0#
Minor Changes#
- #974
5b1be75Thanks @jihnma! - Breaking change: RemoveDataTable’s inaccessibleonRowClickprop. ComposeDataTable.Rowwith a cell-level link or button instead.
4.0.2#
Patch Changes#
- #976
df7567eThanks @jihnma! - Refine Solace’s neutral palette, status signals, and component borders.
4.0.1#
Patch Changes#
-
#969
9d7020fThanks @jihnma! - Fix opt-in component smoke tests when run withtsxin projects using the automatic JSX runtime. -
#971
373ff8bThanks @jihnma! - Install Node type definitions for opt-in component smoke tests in projects that restrict ambient types.
4.0.0#
Major Changes#
-
#906
0d51ce4Thanks @jihnma! - Breaking:Tooltipno longer mounts its ownTooltipProviderTooltipwrapped every instance in its ownTooltipPrimitive.Provider, so aTooltipProviderplaced around a group was shadowed by the inner one and itsdelayDurationcould never reach a tooltip. The provider is now what it is in Radix — required, and the single place the delay is configured.- <Tooltip delayDuration={700}>…</Tooltip> - <Tooltip delayDuration={700}>…</Tooltip> + <TooltipProvider delayDuration={700}> + <Tooltip>…</Tooltip> + <Tooltip>…</Tooltip> + </TooltipProvider>Every
Tooltipmust now sit inside aTooltipProvider; Radix throws otherwise. Mount one near the root of your app.delayDurationstays available on a singleTooltipas a per-tooltip override of the provider’s value, and the provider still defaults to0.
Minor Changes#
-
#954
025f5c8Thanks @jihnma! - Addui list [query]to discover registry components with descriptions before initializing a project. Support the same explicit registry-ref/latest options as add and diff. Enforce the documented TypeScript-only contract before init or add writes files; plain-JavaScript projects receive actionable guidance instead of silently acquiring unconfigured TSX. -
#952
e7eede1Thanks @jihnma! - Contain Table overflow with a keyboard-accessible scroll container. Expose Dialog and Sheet content props, sizing and close labels, Button loading labels, and DataTable/Pagination localization without editing installed source.Add Navigation longest-prefix matching with route-segment boundaries and a custom matching callback, preserving exact matching and explicit active overrides.
-
#960
b6ad115Thanks @jihnma! - Add Field with composable Label, Control, Hint and Error parts plus optional render-function sugar. Share generated IDs and validation state across native and Radix controls, track mounted descriptions, and leave validation and layout policy to the application. -
#951
8325a00Thanks @jihnma! - Generate light, dark and system theme preferences with persistent override examples. Add managed-block hashes, edit warnings and read-only theme previews. Honor reduced motion globally, remove unused 7px spacing tokens in favor of existing Tailwind spacing, and clarify generated CSS ownership. -
#950
3fbca06Thanks @jihnma! - Mark hook-based components as client modules and export all compound parts by name for Next.js App Router server pages. Existing compound APIs remain supported inside client components. -
#966
fd212f3Thanks @jihnma! - feat(cli): add component smoke tests withadd --with-tests -
#957
f417f8bThanks @jihnma! - Add Radix-backed Progress for determinate/indeterminate task progress and Slider for single/multiple values, keyboard input, native forms, RTL and vertical layouts. Both expose named and compound parts plus a short default composition. Tracks use neutral ink-and-border styling; slider thumbs preserve a 44px hit area and keyboard focus. Progress is not a measurement meter. -
#949
44a97c6Thanks @jihnma! - Pin registry downloads to the CLI release tag. Use —registry-ref to select a tag or commit, or —latest to explicitly resolve main to a commit. Missing release tags fail with explicit fallback guidance instead of silently fetching main.Record the registry ref, SHA-256 hash and CLI version for each installed file in beaket.ui.json, preserving existing baselines when local changes are skipped. Diff compares the recorded baseline, local source and target release, verifies baseline hashes, and separates local customizations from upstream changes. Exit codes are 0 for clean/local-only, 1 for mergeable updates, 2 for conflicts, and 3 for unknown baselines or errors. Comparing changes on both sides requires Git. Overwrite remains an explicit replacement with backups; automatic merging is deferred.
-
#955
aad0a34Thanks @jihnma! - Add the canonical danger variant to Button, Badge and Alert without removing destructive, error or caution. Alert also accepts info, success and accent as aliases for note, tip and important. Existing styles, icons, callout titles and defaults are unchanged. No new color scale or renamed consumer API. -
#963
dd9d9b4Thanks @jihnma! - feat(cli): merge recorded local component edits during add —overwrite
Patch Changes#
-
#956
dcf571dThanks @jihnma! - Document the copied-source update lifecycle and migration from native HTML. Explain version-pinned diffs, numbered backups, wrapper-first customization, theme ownership, named server-component imports, Select sentinels, native form submission, route matching and mobile table scrolling. Add a browser check for the documented Radix FormData behavior. -
#947
5ff6181Thanks @jihnma! - Type development warnings without requiring Node globals in browser projects, remove DataTable’s unused generic, and generate documented React requirements from the registry. Remove the deprecated baseUrl option from installation instructions. -
#959
91f7defThanks @jihnma! - Align Checkbox and Radio at 24px and give Switch distinct 16/20/24px heights with proportional thumbs. Preserve fixed chassis and inner-part press travel. Correct border-aware hit expanders to measure at least 44px rather than the previous 42px. -
#953
56188fbThanks @jihnma! - Make Radix-hidden backgrounds inert while Select and modal DropdownMenu overlays are open. This absorbs the aria-hidden-focus audit defect still present in @radix-ui/react-select 2.3.7 and @radix-ui/react-dropdown-menu 2.1.24. Native inert is required; existing inert regions are preserved. Background focusability is restored when Radix releases the last overlay’s hiding marker. No axe rule suppression is needed for these overlays. -
#906
0d51ce4Thanks @jihnma! - RemoveclassNameredeclarations from six component prop typesAvatarProps,CheckboxProps,RadioGroupProps,RadioItemProps,SwitchProps, andTextareaPropseach redeclaredclassName?: stringinside an interface already extending a props type that provides it. It reached no consumer and no docs table — the props generator excludesclassName. Types are unchanged.CheckboxProps,RadioGroupProps, andRadioItemPropsare now type aliases, matchingLabelProps.PaginationBasePropsandDataTablePropskeep theirs: neither extends a DOM props type, so the declaration is the only source. -
#906
0d51ce4Thanks @jihnma! -alert,badge, andswitchno longer depend onclass-variance-authorityEach used
cvafor a single flat variant map with no compound variants — a lookup keyed by one prop.alert.tsxalready spelled the same lookup twice as a plain object (variantIcons,variantTitles) directly beneath thecvacall. The three now use a plain object too, soui add alert|badge|switchinstalls one package fewer.buttonandcardkeepcva, where two axes and compound variants earn it.Rendered class strings are unchanged.
AlertPropsandSwitchPropsdrop theVariantProps<…>they extended, which restated literal unions the interfaces already declare. -
#947
5ff6181Thanks @jihnma! - Preserve local files in numbered backups before overwriting, label overwritten files explicitly, and recommend reviewing diffs before discarding customizations. Resolve transitive registry dependencies when adding a component. Theme updates now ask before replacing tokens, and re-running init preserves existing configuration and directs theme switches to the theme command. -
#961
8fac889Thanks @jihnma! - Check bundler aliases and Tailwind v4/plugin setup during init instead of reporting an unverified project as ready. Offer a backed-up, confirmed Vite 8 native paths edit for simple configs; preserve toolchain files under —yes and print manual instructions for unsupported or conflicting configurations.
3.1.0#
Minor Changes#
-
#901
4628023Thanks @jihnma! - Drop four dependencies and one migration path.LabelandSeparatorrender a plain<label>and<div role="separator">instead of wrapping@radix-ui/react-label/@radix-ui/react-separator. Same DOM, samedata-slot/data-orientation/aria-orientation, two fewer packages for a project that installs them. Radix Label’s one behavior beyond the element — suppressing text selection when a label is double-clicked — is gone with it.- The CLI drops
fs-extraandpicocolorsfornode:fs/promisesandnode:util.styleText, which raises its floor to Node 22.13 (now declared inengines). ui themeandui addno longer recognise a theme block written before the/* beaket:theme:start */markers shipped (April 2026, #309). An unmarked block is now left alone and the current theme is appended after it; re-runnpx @beaket/ui initif your CSS still carries one.
3.0.0#
Major Changes#
-
#890
22104a3Thanks @jihnma! - Breaking:RadioGroup.Item,<Component>Propsnames, andcloseWhenis goneThree breaking changes batched into one sign-off, because a major bump is a cost paid once (
docs/git-rules.md).RadioItem→RadioGroup.Item#Radio was already a true compound component — it shares state through Radix’s context — and only the packaging disagreed with
SelectandTabs.- import { RadioGroup, RadioItem } from "@/components/ui/radio"; + import { RadioGroup } from "@/components/ui/radio"; <RadioGroup defaultValue="a"> - <RadioItem value="a" /> + <RadioGroup.Item value="a" /> </RadioGroup>RadioItemPropskeeps its name and stays exported.data-slot="radio-item"is unchanged.Generic
Props→<Component>Props, exported everywhere#Propslands in your codebase, where it is un-greppable and forcesimport { Props as ButtonProps }. Four exported names change:- import { Button, type Props } from "@/components/ui/button"; + import { Button, type ButtonProps } from "@/components/ui/button";Was Now badge.tsxPropsBadgePropsbutton.tsxPropsButtonPropscheckbox.tsxPropsCheckboxPropsinput.tsxPropsInputPropsThe rest is free:
AvatarProps,DialogProps,SheetPropsandTextareaPropswere genericPropsthat nobody could import, andAlertProps,SelectTriggerProps,TooltipPropsandTooltipProviderPropswere named right but unexported. Exporting a props type is what lets you write a wrapper — the first thing anyone does with a copy-pasted component.closeWhenremoved from Dialog and Sheet#- <Dialog open={open} onOpenChange={setOpen} closeWhen={result?.ok}> + <Dialog open={open} onOpenChange={setOpen}>Close it from the state you already own:
useEffect(() => { if (result?.ok) setOpen(false); }, [result]).closeWhenwas the only reason Dialog and Sheet hand-rolled controlled/uncontrolled state that Radix’sRootalready implements. With it gone,openandonOpenChangepass straight to Radix, and the render-time ref write that served it disappears with it. The dev warning foropenwithoutonOpenChangeis unchanged.
Minor Changes#
-
#876
b8015bbThanks @jihnma! -Breadcrumb.LinkandNavigation.LinkacceptasChildBoth hardcoded a plain
<a href>, so in Next.js or React Router a consumer had to either lose client-side navigation or rewrite the component — which, under Open Code, means forking the file forever.asChildrenders the consumer’s own element instead:<Breadcrumb.Link asChild> <Link href="/docs">Docs</Link> </Breadcrumb.Link>Under
asChildthe component injects nothing: the child owns its tag, andNavigation.Linkskips its press-travel wrapper the way Button skips its spinner. Both components now list@radix-ui/react-slotin the registry. -
#887
005e77fThanks @jihnma! - DataTable defers its global filter, and the render-time ref writes are goneF6 — typing in the search field re-ran filtering and re-rendered the whole table in the same commit. The input is now bound to the immediate value and the table receives a
useDeferredValueof it: typing stays responsive and the expensive pass lags by design.F2 —
data-table.tsxcarried two hand-rolled “latest ref” writes during render (tableRef.current = table,onSelectionChangeRef.current = onSelectionChange) — a shape React tells you not to write and that React Compiler, which runs in your build, refuses to optimize past.useEffectEventreplaces both: it is the official answer for an Effect calling the freshest callback, and it closes over the freshesttableas well, so the second ref disappears with the first.textarea.tsxhad the same render-time write for its forwarded-ref merge. That is not an Effect callback, souseEffectEventdoes not apply; the ref is simply gone and the merge depends onrefdirectly.data-tablenow declares"react": ">=19.2.0"in the registry (useEffectEvent);addwarns if your project is below it.useDeferredValueis a React 18 API and needs no floor. -
#877
a955418Thanks @jihnma! -data-sloton every element DataTable rendersdata-table.tsxwas the only component in the registry with zerodata-slotattributes, so its entire DOM was unaddressable from outside — part of why it compensates withgetRowClassNameand a 20-prop interface.Hyrum’s Law says something becomes the contract whether we choose it or not.
data-slotis the blessed observable: stable, named, free at runtime, and explicitly not the class names, which are ours to rewrite on every redesign.24 of the 28 rendered elements now carry a
data-table-*slot —data-table,-toolbar,-search,-search-icon,-container,-table,-header,-header-row,-select-head,-head,-head-content,-sort-indicator,-sort-icon,-body,-row,-select-cell,-cell,-empty-row,-empty-cell,-empty,-footer,-summary.The other four are composed controls —
Input, twoCheckboxes andPagination— which keep their own slots rather than being overwritten; they are addressable through the named container around each.Attributes only. No API change.
-
#885
8a6c2bcThanks @jihnma! -Dialog.TriggerandSheet.TriggerpartsDialog and Sheet accepted the trigger as a
ReactNodeprop and exposed noTriggerpart, so the sugar was the API. A consumer who needed two triggers, or a conditional one, or a trigger that is not the first child, had nowhere to go — and aReactNodeprop is structurally invisible: nothing in the type saystriggermust be focusable.<Dialog> <Dialog.Trigger> <Button>Open</Button> </Dialog.Trigger> <Dialog.Trigger> <Button variant="ghost">Also open</Button> </Dialog.Trigger> <Dialog.Title>…</Dialog.Title> </Dialog>Radix’s context was already mounted here; the wrapper simply did not expose the part.
asChilddefaults totrue, matchingDialog.Close, so the natural nesting keeps the consumer’s own element.Additive:
triggerkeeps working unchanged and is now literally sugar that renders this part. ATriggerchild is placed as a sibling of the portal rather than as content inside it, so triggers and content each land where they are legal. -
#884
35d7666Thanks @jihnma! - Button’sloadingfalls back to the form’s pending stateReact 19’s
useFormStatusis documented as a design-system hook: a submit button nested in a<form>can read the form’s pending state directly, with no prop drilling and no wiring by the consumer. Until now every consumer wiredloadingby hand for the most common case there is.<form action={save}> <Button type="submit">Save</Button> {/* spins and disables on its own */} </form>loadingremains the override — supplied, it wins, includingloading={false}. The fallback applies only to atype="submit"button; an ordinary button is untouched, and underasChildthe component still injects nothing at all.No new registry dependency:
react-domships in lockstep withreact, and adding it to a component’sdependencieswould runnpm install react-domin your project. -
#883
8510babThanks @jihnma! -addwarns when your React is older than a component needsA registry component is copied into a project whose React version we do not control, and
registry.jsondeclared npm dependencies but never a React floor — so a component using a 19.2-only API would land in an 18.x project and fail at runtime with no warning.registry.jsonnow carries a top-level"react"floor, and a component needing more can carry its own.addreads the React you actually have (node_modules/react/package.json, falling back to the declared range read as its minimum) and says so when the floor is unmet:! tabs needs React >=19.2.0 — found 18.3.1. The files are still copied; they may fail at runtime until React is upgraded.A check, not an install. Putting
"react"in a component’sdependencieswould have runnpm install reactin your project — pulling React to latest as a side effect ofadd button— which is the opposite of declaring a minimum. The files are still written either way: you may be about to upgrade, and a copy-paste library has no business changing your React version. An unparseable floor, or a React version that cannot be determined, produces no warning rather than a guess. -
#879
f5f23ceThanks @jihnma! -Paginationis a flexible compound component:Pagination.Previous,.Item,.Ellipsis,.NextPagination was one function with configuration props, a discriminated union with
neverguards, and hardcoded<a href>s — so the consumer’s router was unusable and the page-number algorithm was entangled with rendering.The root now holds
page/totalPages/ navigation mode in context and the parts read it, each takingasChild:<Pagination page={page} totalPages={10} buildPageUrl={(p) => `/page/${p}`}> <Pagination.Previous /> <Pagination.Item page={1} /> <Pagination.Ellipsis /> <Pagination.Item page={10} asChild> <Link href="/page/10">10</Link> </Pagination.Item> <Pagination.Next /> </Pagination>Additive:
mode="link"/mode="button"keep working unchanged and are now sugar that lays the same parts out for you.PaginationBaseProps,PaginationLinkPropsandPaginationButtonPropsare exported alongside the newPaginationItemPropsandPaginationEllipsisProps. -
#880
75391daThanks @jihnma! -Alert.TitleandAlert.DescriptionpartsAlerttook its title as a content prop —title?: string— which forced the props type toOmit<…, "title">and could only ever be extended by another content prop:titleinvitestitleIcon, thentitleClassName, thenrenderTitle. Astringtitle also cannot be reordered, wrapped, conditionally rendered or styled.<Alert variant="warning"> <Alert.Title className="uppercase"> Deploy blocked <Badge>3</Badge> </Alert.Title> <Alert.Description>Two required checks have not reported yet.</Alert.Description> </Alert>Namespacing, not context — the parts share no state, so a provider would be pure machinery. The variant icon stays on the root, where the variant lives.
Additive:
titlekeeps working as the sugar over the parts, including the variant-name default ("Warning","Note", …), and bare children are still wrapped as the description. Parts must be direct children ofAlert. -
#876
b8015bbThanks @jihnma! -Navigationtakes a rootvalue;Navigation.Linkderives its own active stateThe consumer answered “is this the current page?” by hand on every single link. The root now holds that one answer and each link compares its own
value:<Navigation value={pathname}> <Navigation.Link href="/docs" value="/docs"> Docs </Navigation.Link> </Navigation>Additive: the root’s
valueis optional, an explicitactivestill overrides the derived state, and aNavigation.Linkused with no rootvaluebehaves exactly as before. The context accessor deliberately does not throw when there is no provider — throwing would require more of existing callers. -
#881
2f3064aThanks @jihnma! -DataTableis a flexible compound over the TanStack instanceDataTablePropsexposes 20 configuration props — among thememptyMessageandemptyState;onRowClick,onRowMouseEnter,onRowMouseLeave;getRowClassName. That is the mechanical result of answering layout questions with props instead of with children, and by Ousterhout’s measure it makes DataTable a shallow logic component: real complexity behind a wide interface, so the consumer pays twice — once to learn it, again when the 21st need is not on the list.The root now builds the TanStack
tableobject and hands it back; the parts read it:<DataTable columns={columns} data={data} searchable paginated> {(table) => ( <> <DataTable.Toolbar searchPlaceholder="Filter people…" /> <DataTable.Table> <DataTable.Head /> <DataTable.Body> {table.getRowModel().rows.map((row) => ( <DataTable.Row key={row.id} row={row} className={rowClass(row)} onMouseEnter={…} /> ))} </DataTable.Body> </DataTable.Table> <DataTable.Pagination /> </> )} </DataTable>emptyMessage/emptyStatebecomeDataTable.Empty;getRowClassName,onRowMouseEnterandonRowMouseLeavebecome ordinary props on your own<DataTable.Row>.Additive: all 20 props keep working and now lay out exactly these parts, so the sugar path and the composed path render the same DOM by construction. Removing any of the 20 is a separate breaking decision.
-
#875
77ea99fThanks @jihnma! -refnow typechecks on Badge, Blockquote, Button,Dialog.Header/.FooterandSheet.Header/.FooterThese seven prop declarations extended
React.HTMLAttributes/React.ButtonHTMLAttributes, which do not carryref. React 19 movedrefinto intrinsic element props, soReact.ComponentProps<"button">does. Passing arefto any of the seven failed to typecheck for no reason anyone intended; they now extendReact.ComponentProps<…>like the rest of the registry.Widened type only — nothing that compiled before stops compiling.
-
#886
352b6f5Thanks @jihnma! -Tabs.ContenttakeskeepMountedRadix unmounts inactive tab panels, so scroll position, uncommitted form input and any in-panel state are destroyed on every tab switch.
forceMountwas the only escape and it keeps the panel fully live — the opposite extreme.<Tabs.Content value="draft" keepMounted> <textarea /> {/* survives a trip to another tab */} </Tabs.Content>keepMountedis React 19.2’s<Activity mode="hidden">: state preserved, effects torn down, re-render deprioritized. Off by default — this adds a way out, it does not change what Tabs already does.Under
keepMountedthe panel is hidden withdata-[state=inactive]:hiddenrather than Radix’shiddenattribute, whichforceMountdisables — so an inactive panel leaves no empty box in the layout or the accessibility tree.Requires React >= 19.2, which the
tabsentry inregistry.jsonnow declares;addwarns if your project is below it.
Patch Changes#
-
#875
77ea99fThanks @jihnma! - Avatar drops the module-level hydration guardavatar.tsxcarried alet hydrated = falsemodule global and a post-mount render gate to work around a React 19 SSR hydration mismatch (#291): Radix’suseIsHydrated, built onuseSyncExternalStore, returnedtrueduring client hydration, so a cached image rendered<img>where the server had rendered the fallback<span>.Root cause is gone upstream.
@radix-ui/react-avatar1.2.6 no longer uses that mechanism — the image loading status is a plainuseState("idle"), identical on the server and on the first client render. The guard cost every Avatar auseState, auseEffect, and one blank frame before the image appeared.A new
SsrHydrationTeststory renders the avatar withrenderToString, hydrates it with a warmed image cache, and asserts React recovered from no errors — so a future Radix bump that reintroduces the mismatch fails loudly instead of silently. -
#875
77ea99fThanks @jihnma! - Dialog, Sheet, DropdownMenu, Table and Avatar attach their parts with oneObject.assignThese five files attached parts by post-hoc mutation (
Dialog.Title = DialogTitle) while the other six compound components used a singleObject.assign. One expression is the complete public surface, instead of a surface scattered down the file and dependent on statement order — and TypeScript’s expando-function support means a typo inDialog.Titel = …quietly adds a property rather than failing.Identical public shape. Nothing to change when re-copying.
2.8.0#
Minor Changes#
- #808
2e9f99aThanks @jihnma! - Install the shared type, spacing, border, and radius foundation with semantic and palette tokens so copied components render with their intended scale and shapes.
Patch Changes#
-
#812
3d6db4dThanks @jihnma! - Stop generating a$schemaURL that does not resolve in new configuration files. -
#810
aa9c1d8Thanks @jihnma! - Handle dependency installation failures without crashing the add command. -
#813
872eed2Thanks @jihnma! - Detect component aliases and Tailwind CSS entry files from referenced TypeScript configs. -
#814
73a7e07Thanks @jihnma! - Show dependency installation progress before the add command begins installing packages.
2.7.0#
Minor Changes#
-
#774
6dce543Thanks @jihnma! - Remove the non-functional--surface-brandand--shadow-size-activevalues from every built-in palette, reducing the authored theme contract from 32 to 30 values.--surface-brandis removed because no semantic token or shipped component referenced it. No replacement is needed; use a semantic background role such as--color-bg-emphasiswhen styling a brand-emphasis surface.--shadow-size-activeis removed because active pressables intentionally drop their shadow and translate by 1px; all semantic shadow roles derive from--shadow-size.
The dependency audit also identifies
--tone-8through--tone-10as reserved rather than functional. They remain for compatibility with the public 12-step neutral ramp and future deep-ink roles; the other 27 palette values are all reachable from the semantic layer.Existing CSS produced by an older
initremains valid because consumers own that CSS, but these two custom properties continue to have no effect. Runnpx @beaket/ui themeto refresh the configured theme, ornpx @beaket/ui theme --theme <preset>to switch presets and replace the managed block with the 30-value contract. Customized palettes can instead delete the two declarations manually. -
#781
09d2097Thanks @jihnma! - Add a dark Solace palette that followsprefers-color-scheme: dark, including complete generated documentation tokens and forced-dark Storybook support. -
#782
b509d5fThanks @jihnma! - Define and apply a system-wide accent hierarchy so keyboard focus, open ownership, hover intent, selection, persistent affordance, and content navigation no longer compete at equal weight.- Buttons and interactive cards are neutral at rest, reveal a thin accent edge on hover, and hold a grown edge only while they own an open overlay. Primary emphasis now comes entirely from its ink surface; the secondary button returns to neutral material.
- Checkbox, Radio, Switch, Navigation, Tabs, and Pagination drop their standing accent edges. Choice controls reveal action on hover, while navigation and tabs reserve accent for the selected lens.
- Writing fields retain their cap-off focus edge, Select retains its open-owner edge, and menu rows retain their inset active rule under an explicit channel-precedence policy.
- Add dense Storybook coverage plus native and Radix-backed interaction checks for simultaneous focus, open, active-row, and selection states.
Patch Changes#
-
#711
21f1cd4Thanks @renovate! - Migrate DataTable to the TanStack Table v9 feature and state APIs while preserving sorting, filtering, pagination, selection, and exported table types. -
#780
5261301Thanks @jihnma! - Re-cut the Solace semantic signals so small text, icons, borders, tints, and solid states remain distinguishable under simulated protanopia, deuteranopia, and tritanopia.- danger:
#af5340→#a44735 - warning:
#ce8042→#d18b3f - success:
#00896c→#3f8a55 - info:
#4c6bb6→#53628f - info-alt:
#008597→#005f72, with the knockout switched to the paper endpoint - accent remains
#2b5bff
The browser-backed audit evaluates all 15 role pairs across six semantic forms and four vision modes using the Machado 2009 full-severity transforms and OKLab distance floors. Storybook now includes a dedicated small-mark comparison and reinforces semantic status examples with readable labels and distinct icon shapes.
- danger:
-
#778
8d971e9Thanks @jihnma! - Rebalance the Solace light neutral ramp into a monotonic OKLab progression while preserving its warm-paper and cool-ink endpoints.The source hex values produce these OKLab lightness values and adjacent differences (rounded to three decimals):
Tone Before L Before ΔL After L After ΔL 0 0.963 — 0.963 — 1 0.966 -0.003 0.920 0.043 2 0.934 0.032 0.820 0.100 3 0.838 0.096 0.730 0.090 4 0.639 0.199 0.639 0.091 5 0.616 0.023 0.585 0.054 6 0.540 0.076 0.525 0.060 7 0.455 0.085 0.455 0.070 8 0.386 0.070 0.390 0.066 9 0.328 0.058 0.325 0.064 10 0.252 0.075 0.245 0.080 11 0.128 0.124 0.128 0.117 The automated palette contract permits 0.04–0.12 OKLab L across the full ramp and tightens the functional
tone-3throughtone-7steps to 0.05–0.10. This keeps the paper and deepest-ink ends flexible while preventing functional roles from collapsing or jumping abruptly. -
#773
51041c8Thanks @jihnma! - Enforce browser-resolved contrast guarantees for every built-in theme palette. Correct role hover/active mixtures, knockout choices, and dark neutral values exposed by the new policy. -
#777
eea28a5Thanks @jihnma! - Restore visible depth between the Solace light page, raised, and overlay surfaces while preserving its restrained warm-paper character and existing contrast guarantees.
2.6.0#
Minor Changes#
-
#683
02bd7e9Thanks @jihnma! - feat(cli): tell existing users when a component’s style has movedCopied components don’t auto-update, so a consumer on an older style had no way to know a component now differs from the registry —
addonly knew “a file is here” and asked to overwrite, learning nothing about whether their copy was actually out of sync.diffcommand —npx @beaket/ui difflists every installed component as matching the registry or differing from it;npx @beaket/ui diff <component>prints the line-by-line diff between your copy and the registry, then points you atadd <component> --overwrite(or hand-merging if you’ve customized it). It never writes — you own the code. A difference may be an upstream restyle or your own edits; without installed-version tracking the CLI can’t tell them apart, so it frames it as a difference and leaves the choice to you.- Smarter
add— an existing file is compared against the registry: files already matching are left untouched (no prompt), and only files that differ prompt to overwrite, with wording that says your copy is out of sync rather than just that a file exists. Skipped files point atdiffto see what changed.
2.5.0#
Minor Changes#
-
#681
3655067Thanks @jihnma! - Card is a surface, not a template.Reframed the Card around its material rather than an imposed structure. Its identity is now the drawn surface — square corners, a single-ink border, and the grey offset shade that marks a raised thing — and it imposes no header/body/footer: the root carries sensible padding and a column gap so you can pour any content straight in and it sits right. This is what pulls it away from reading like a Dialog, whose Cancel/confirm footer was the tell.
elevationprop (flat|shade|overlay, defaultshade) replaces the oldshadow?: boolean.shadeis the quiet grey offset that says “raised surface”;overlaylifts it ontobg-bg-overlaywith the heavier offset;flatsits flush.interactiveprop — when the whole card is a link it earns the accent edge in place of the grey shade: thin at rest, grown on hover, dropped onto the edge when pressed. Pair withasChildto render a real<a>(the one place a surface carries the vivid voice).Card.Section— an edge-to-edge helper that cancels the root padding so media and full-width rules reach the card’s edges.Header/Title/Description/Action/Content/Footerremain as optional, padding-free layout helpers — never the required anatomy.
Migration: replace
<Card shadow>with<Card elevation="shade">(now the default, so plain<Card>also lifts); a card with no shade is<Card elevation="flat">.
2.4.0#
Minor Changes#
-
#635
cff40cfThanks @jihnma! - Accent becomes the interaction voice.The
accentsignal — the one vivid ink each theme reserves for “what you can act on” — now actually carries interaction across the system, resolving the long-standing “two blues” (accent vs.signal-info) in Solace:- Primary button: the ink fill gains a 1px
accent-solidedge, withaccent-warmed hover/active surfaces (previously a plainborder-strongwith no hover shift). - Secondary button: now an
accenttint (accent-bg+accent-fg+accent-border) that deepens its border on hover, instead of a neutral grey surface. - Links & focus rings (
--color-fg-link,--color-border-focus): repointed fromsignal-infotosignal-accent, sosignal-inforeturns to meaning only “info”. Solace’s primary focus is now a single blue (edge + ring) rather than two.
Two new semantic tokens back the primary hover/active states —
--color-bg-emphasis-hoverand--color-bg-emphasis-active(accent-warmed mixes ofbg-emphasis) — bringing the semantic layer to 66 names.Accessibility: because
accentis now link/focus text (which needs 4.5:1 against the page), three palette accents were re-cut to clear that floor — Marigold (light), Porcelain (dark), and Tobacco (dark). The other themes already passed. - Primary button: the ink fill gains a 1px
-
#636
fede846Thanks @jihnma! - feat(theme): action shadow — a thin accent edge under what you can pressThe offset shadow splits into two voices. Surfaces (cards, overlays) keep the grey shade; pressables now carry the one vivid voice as a thin sharp edge.
- New semantic tokens
--shadow-offset-action/--shadow-offset-action-hover(68 shared names now: 62 color + 6 shadow) — drawn with--signal-accent - Button: rest = 1px accent edge → hover grows to 2px → active drops the button
onto the edge (
translate(1px,1px), shadow collapses); ghost/link/disabled stay flat, disabled keeps its dashed border - Solace palette thins
--shadow-size2px → 1px — softness of the paper, sharpness of the edge
- New semantic tokens
-
#647
8729283Thanks @jihnma! - feat(breadcrumb): a quiet trail in one ink, not a switcherA breadcrumb is a trail — a sentence read left to right — not the lens that navigation and tabs are, so it doesn’t join the fused-strip/glass-plate grammar. It stays in one ink: ancestor links now sit in muted ink (
text-fg-muted) instead of standing accent, and the current page keeps full ink (text-fg font-medium). Pointing at a step darkens it from muted to full ink (hover:text-fg) rather than dropping the accent to ink as before, and the always-on underline is retired (no-underline) — the darkening is the affordance. The one accent mark is the keyboard focus ring; the vivid voice is kept for where you act, not spent on a standing row of links. No pressable edge (a link is not a key), no new tokens. -
#649
3d36f73Thanks @jihnma! - feat(button): primary wears a light rim that sheds on engageThe primary button’s rest edge no longer doubles. Its border was the full accent (
border-accent-solid) sitting directly over the full accent shadow — the same blue on both, so the two 1px lines merged into one thick 2px band on the bottom and right. The border now takes the lighter accent tone (border-accent-border), so at rest the light rim and the full-accent shadow read as two tones — depth, not a doubled line — with the strong voice living in the shadow where it grows and drops.On engage the rim sheds:
hover:border-transparent(anddata-[state=open]mirrors it) drops the rim into the ink fill so the accent consolidates into the one growing shadow rather than piling into a thick accent band. The border is added to the shared transition (transition-[box-shadow,translate,border-color]) so the rim fades with the shadow instead of snapping — which also smooths the hover border-color change on the other bordered variants. No new tokens; other variants and the rest/hover/press/held-open shadow grammar are unchanged. -
#649
3d36f73Thanks @jihnma! - feat(button): remove themonopropThe
monoprop (monospace + wide tracking for CTA-style text) had no documented role in the Ink & Instrument vocabulary, was demonstrated only by an orphanMonoVariantscomposition the docs never rendered (plus a redundant single story), and was a Button-only flourish. Monospace lives on where it means something — code badges, numeric table cells — not as a per-button toggle.The
monoprop is dropped fromButtonalong with itsMonoandMonoVariantsstories. Consumers who want monospace CTA text can passclassName="font-mono tracking-wide". Since components are copy-paste, existing copies are unaffected. -
#649
3d36f73Thanks @jihnma! - feat(button): remove thestarkvariantstark(a strong-bordered button that inverted to solid ink on hover) had no documented role in the Ink & Instrument vocabulary — it appeared nowhere in the design docs, was used only as a demo trigger in stories, and overlapped withoutline(bordered neutral) andprimary(solid ink). In a system where every mark is meant to be deliberate, an unarticulated variant is a cut, not a keeper.The
variantunion drops"stark"; the demo triggers that used it (dialog, sheet, dropdown-menu stories) move tooutline. Since components are copy-paste, existing consumer copies are unaffected; new copies simply won’t include it. -
#645
fc298f9Thanks @jihnma! - feat(button): held-open triggers sustain the grown edgeA Button acting as an overlay trigger (dropdown menu, popover) now holds its hover state while the overlay is open — the accent edge stays grown instead of springing back to rest. The trigger reads as the active owner of the menu it opened while remaining pressable: a lifted state, not a drop.
The rule is variant-aware:
data-[state=open]:mirrors each variant’shover:, so edge-bearing variants grow and hold their edge, andghost/linkkeep their surface tint / underline with no edge and no press-travel. Radix setsdata-[state=open]only onasChildtriggers, so the styles are inert on ordinary buttons; modal (dialog/sheet) triggers sit behind the scrim, so the held edge is effectively visible only for non-modal triggers like DropdownMenu. -
#645
fc298f9Thanks @jihnma! - feat(dropdown-menu): accent marks the highlighted rowThe highlighted/active menu row is no longer an ink stamp (full
bg-emphasisslab). Instead the system’s one vivid voice — the accent — marks the row you’d activate: a faintaccent-bgwash plus a 2px accent left-rule (the engaged-edge weight, matching a hovered button’s grown edge), with the ink text left at full density and the shortcut inaccent-fg. Checkbox, radio, and sub-trigger rows adopt the same mark; the sub-trigger holds it while its submenu is open. Destructive rows swap accent → danger (danger-bgwash + danger rule).The menu panel and sub-menu panel now float on
shadow-offset-overlay(the darker overlay shade) instead of the surfaceshadow-offset— a menu sits above cards, not among them. -
#641
a13b651Thanks @jihnma! - feat(input, textarea): cap-off focus — retire the ring, the field engages the action edgeWriting fields are quiet at rest; focusing one is “cap-off”: the glowing focus ring is retired and a static accent edge (
shadow-offset-action) appears under the field while engaged — no hover growth, no active drop, no transition. Invalid fields swap the edge to the new--shadow-offset-action-dangertoken (semantic tokens 68 → 69). The caret becomes the pen (caret-accent-solid) and selection uses the accent tint (selection:bg-accent-bg). Read-only fields gain dignity: the frame retreats toborder-border-mutedwhile the value stays full-ink, and focusing one shows the grey surfaceshadow-offsetinstead of the action edge — readable, not writable. Keyboard-nav rings on pressables (buttons, checkboxes, …) are unchanged. -
#643
3dc0a0bThanks @jihnma! - feat(navigation): the lens — navigation joins the instrument familyNavigation was the last component on the legacy grey shadow ladder. It is now a fused hairline strip (cells share
border-border-mutedborders) carrying one static accent edge, and the current page is no longer stamped in ink — it sits under a glass lens plate: hairline top/left rim, ink bottom/right rim (ink gathers where every shadow in the system falls), filled with the faintest accent wash. The plate lies beneath the type, so the current label keeps full ink density. Pressing any other link travels its label 1px like an instrument key.Token changes (68 semantic names, was 69):
- New:
--color-accent-bg-subtle— accent-only faintest wash (8%), the lens fill - Removed:
--shadow-offset-hoverand--shadow-offset-active— the grey ladder had no users left; surfaces keepshadow-offset/-overlay, pressables keep the action edge
Vertical layouts swap the fusion axis:
flex-col [&>li+li]:ml-0 [&>li+li]:-mt-pxonNavigation.List. - New:
-
#637
e86c410Thanks @jihnma! - feat(pagination): one instrument — fused strip with a single accent edgePagination adopts the action shadow, but as one machine rather than a row of buttons: cells fuse into a single strip (shared borders, no gaps) carrying one static 1px accent edge.
- Strip: new
pagination-stripslot wraps all cells withshadow-offset-action - Press: the chassis stays still — the pressed key’s label travels 1px inside
the frame while the cell darkens (
active:bg-bg-active) - Current page: an ink-solid cell whose label stays held down 1px — the key locked at its landing point
- Ellipsis becomes a bordered blank key; focus outline lifts above neighboring cell borders
- Strip: new
-
#658
5b20afcThanks @jihnma! - Select trigger becomes a field that opens.The select trigger is surfaced like a form field (input’s paper, input’s border) but #646 gave it the full Button edge grammar — a standing accent edge, a hover that grows — so in a real form it was the one control that behaved like a button. But the dropdown “trigger” is loud at rest only because it’s literally a
<Button>; that rest-edge is Button-incidental, not trigger-essential.Select now stays quiet at rest among its field neighbors (no standing edge, no hover growth) and lifts the grown edge only while its menu is open — keeping the trigger-essence (
data-[state=open]:shadow-offset-action-hover) and the keyboard focus ring. It’s the one pressable that carries a ring but no rest edge. Invalid and disabled are unchanged; the resting form now reads as one quiet material. -
#646
4fee10cThanks @jihnma! - feat(select): held-open trigger + accent marks the highlighted rowThe Select trigger is the first form control that is also an overlay trigger — press it and a menu opens, exactly like a Dropdown. It now takes the same held-open pressable grammar as a Button rather than a writing field’s cap-off: a thin accent edge (
shadow-offset-action) at rest, grown on hover (shadow-offset-action-hover), held grown while the menu is open (data-[state=open]:— which Radix sets natively onSelectPrimitive.Trigger), dropped onto the edge on press (active:translate), and gone when disabled (disabled:shadow-none). Keyboard focus keeps the ring; invalid recolors the border and ring to danger while the pressable edge stays accent — role-agnostic, exactly as on a destructive Button. No new tokens.Select’s own menu joins the system too: highlighted rows drop the ink stamp (
bg-emphasisslab) for the accent mark introduced on DropdownMenu in #645 — anaccent-bgwash + a 2px accent left-rule (data-[highlighted]:shadow-[inset_2px_0_0_0_var(--color-accent-solid)]), ink text left at full density. The list panel now floats onshadow-offset-overlay(the darker overlay shade) instead of the surfaceshadow-offset. -
#638
372c55fThanks @jihnma! - feat(checkbox, switch, radio): instrument grammar — static accent edge on small controlsSmall controls adopt the action shadow as instruments: the chassis floats on a static 1px accent edge (
shadow-offset-action, no hover growth, no drop), and press physics belong to the inner key instead — the checkbox indicator and the switch thumb travel 1px under the press. Hover feedback is a surface tint (bg-bg-hoverunchecked,bg-bg-emphasis-hoverchecked); a checked radio gets no press affordance since it cannot be unchecked. Disabled removes the edge.Switch checked also moves from
bg-success-solidto ink (bg-bg-emphasis), matching checkbox/radio — state is carried by thumb position + ink, and the success role returns to meaning outcomes, not “on”. -
#630
fce6614Thanks @jihnma! - New two-layer color token system + Solace theme.- Semantic layer (
themes/semantic.css, new): the 64 names components use —--color-bg/-raised/-overlay/-input/-hover/-active/-disabled/-emphasis,--color-fg/-muted/-subtle/-disabled/-on-emphasis/-link,--color-border/-muted/-strong/-focus, six roles (danger,success,warning,info,info-alt,accent) × seven slots (-solid,-fg-on-solid,-solid-hover,-solid-active,-fg,-bg,-border), and--shadow-offset/-hover/-active/-overlay. Authored once, shared verbatim by every theme. - Palette layer: each theme now authors only 32 values —
--surface-0..2,--surface-brand,--tone-0..11, six--signal-*inks, six--signal-*-onknockouts, and shadow size/color. Porcelain, Tobacco, Marigold, and Eucalyptus are re-cut to the new contract (signal hue/chroma kept; lightness moved only as far as the 4.5:1 knockout and 3:1 page floors require). - New theme: Solace (light-only, now the default) — warm paper, cool ink, equal-weight signals, one vivid blue reserved for action.
- All components migrated to the semantic names; the old material names (
paper,ink,chrome,steel,frost,branch,signal-*,surface-N,shadow-offset-dark) are removed. The new--color-{role}-fg-on-solidknockouts close the amber-on-button legibility gap. init/themenow injectsemantic.css+ the chosen palette together;--theme solacesupported and default.- The
prefers-contrast: moreoverride was removed:--color-fgis now the ramp’s deepest ink, so maximum-contrast text is the default.
Migration for consumers who customized tokens: re-run
npx @beaket/ui themeto re-inject, then re-apply customizations against the 32 palette values (the semantic layer follows automatically). Class mapping highlights:bg-paper→bg-bg/bg-bg-raised/bg-bg-overlay/bg-bg-inputby surface role,text-ink→text-fg,text-steel→text-fg-muted,border-chrome→border-border,bg-branch/bg-ink→bg-bg-emphasis(+text-fg-on-emphasis),outline-signal-blue→outline-border-focus,bg-signal-red→bg-danger-solid(+text-danger-fg-on-solid),shadow-offset-dark→shadow-offset-overlay. - Semantic layer (
-
#644
6adaf80Thanks @jihnma! - feat(tabs): the lens — tabs join the navigation layer’s glassThe tray retires. Tabs.List is now a fused hairline strip under one static accent edge (
shadow-offset-action), and the selected tab sits under a glass lens plate instead of an ink stamp — hairline top/left rim, ink bottom/right rim, the faintest accent wash, plate beneath the type so the label keeps full ink density. Hover is a quiet surface tint; the switch itself is snappy (Radix activates a tab on press, so selecting it is instant by design). The unusedshadowprop on Tabs.List is removed. -
#650
49dfdcbThanks @jihnma! - Each theme’s accent gets its own taste — no longer five shades of the same purple.The
accentis a theme’s one vivid voice: it carries the secondary tint, links, focus ring, pressable edge, nav lens, and menu rule. Yet four of five palettes shipped the same default purple--signal-accent, so every theme but Solace read identically in its most characteristic moment. Each theme now answers in a hue drawn from its own world — light and dark both re-cut:- Porcelain → cobalt, the blue-and-white of 청화백자:
#1e40af/#7fa0f0. - Tobacco → a warm taupe from its cigar-box world, in place of purple:
#6c5240/#bda488. - Eucalyptus → a teal-blue — the trust of blue, kept a leaf’s-width green so it stays clear of the pure-blue themes and its own cyan
info-alt:#175a84/#4fb0d6. - Solace (electric blue) and Marigold (violet) are unchanged — Marigold is now the family’s one violet, not one of four.
Accessibility: because
accentis link/focus text (--color-fg-link), which needs 4.5:1 against the page, every new value was gated against its theme’stone-0in both light and dark before selection. A pale celadon and Marigold’s namesake gold were both rejected for failing that floor on light paper. Regenerateddocs/src/data/theme-tokens.jsonanddocs/public/theme-init.js. - Porcelain → cobalt, the blue-and-white of 청화백자:
Patch Changes#
-
#649
3d36f73Thanks @jihnma! - fix(button): ghost and link no longer leak the accent edgeGhost and link were meant to have no accent edge, but the base applied
shadow-offset-actionto every variant and theirshadow-noneoverride never won — twMerge can’t dedupe a custom shadow utility againstshadow-none, so both classes survived and the custom edge won the cascade. Ghost showed a blue 1px edge at rest (and grew it on hover); link wore a button edge instead of reading as a link.The accent edge now lives on the variants that carry it (via a shared
edgestring appended to primary/destructive/outline/secondary/success/stark/warning) rather than on the base, so ghost and link simply omit it — kept out, not overridden. Ghost is now fully quiet (ink text, grey hover fill, no edge); link reads as a link (accent text, hover underline, no edge). Edged variants are unchanged. No token or palette change. -
#649
3d36f73Thanks @jihnma! - fix(button): outline hovers on the accent edge, not a grey fillThe outline button was airy at rest but filled with a grey wash on hover (
hover:bg-bg-hover) on top of its accent edge growing — two signals at once, and the fill read as muddy against the paper. Outline now drops the grey fill on hover and held-open and leans on the accent edge it already grows (from the base grammar); the press keeps a faint grey settle (active:bg-bg-active) to confirm the drop. Ghost is unchanged: with no edge, its grey fill is its only hover signal. No token or palette change —bg-hoveris a shared, already-light token; this was a grammar fix, not a color one. -
#613
3287d19Thanks @jihnma! - fix(cli): add a 10s timeout to registry fetches and preserve the original errorfetchRegistryandfetchComponentcalledfetch()with noAbortController, so a slow or unreachable GitHub raw CDN hung the CLI until the OS-level TCP timeout fired (potentially minutes). Both catch blocks also discarded the underlying error, collapsing ENOTFOUND / ECONNREFUSED / proxy failures into the generic “Make sure the repository is public.” message.Each request now aborts after 10s and the thrown message includes the real cause (or “request timed out after 10s” when the timeout fires), so
npx @beaket/ui add buttonagainst a degraded network fails fast with an actionable reason. -
#656
a98d75bThanks @jihnma! - Switch now renders an invalid state.Switch was the only form control with no
aria-invalidstyling — a required switch marked invalid (e.g. an unaccepted terms toggle) showed no visual change, while its instrument siblings Checkbox and Radio both recolor. It now follows the same instrument grammar:aria-invalidrecolors the border and focus ring to danger while the accent edge stays (role-agnostic, exactly as on Checkbox/Radio and the Select trigger). -
#659
d4cfb2cThanks @jihnma! - Switch off-state now responds to hover.The switch was the one instrument with no hover feedback when off — its checked track warms on hover and its siblings Checkbox and Radio tint their surface, but the off-channel sat inert. It now darkens one token step on hover (
border-muted→border), completing the instrument grammar: hover tints the surface, press travels the key (the switch already travels its thumb, so no active tint is added).
2.3.1#
Patch Changes#
- #409
785955dThanks @jihnma! - AlignInputprops type with the rest of the library: extendsReact.ComponentProps<"input">instead ofReact.InputHTMLAttributes<HTMLInputElement>, matchingTextarea. Removes redundantclassNameandrefdeclarations (already inherited). No runtime behavior change. Closes #333.
2.3.0#
Minor Changes#
-
#339
28ca093Thanks @jihnma! - Re-tune prose body text color across all four themes to eliminate long-reading eye fatigue while preserving brutalist identity.Problem.
graphiteonfrost/paperwas running at ~18–20.4:1 contrast — above the research-backed 10–15:1 sweet spot (Bauer & Cavonius, Lin & Huang) and higher than every major design system (Apple 16.7:1, GitHub 15.5:1, Linear 16.2:1, Notion 10.6:1, Medium 14:1). Excess contrast induces halation and is especially punishing on dense Japanese kanji strokes.graphiteandinkwere also nearly identical (ΔE<1) so the two tokens had collapsed semantically.Changes per theme (light / dark):
- Porcelain: graphite
#030508 → #1e2229/#e6eaee → #c4cad4; ink#080b10 → #0a0d14/#dce0e6 → #e6eaee; iron#282b2f → #15191f/#b4bcc6 → #aab2bd; dark paper#06080c → #0d1117(reduces dark-mode halation + re-adaptation fatigue). - Eucalyptus: graphite
#0a1025 → #1e2638/#e8ecf4 → #c6cdde; ink[#162036](https://github.com/beaket/ui/issues/162036) → #0e1628/#dce2ec → #e8ecf4; iron[#243250](https://github.com/beaket/ui/issues/243250) → #151d30/#b0bace → #a6aec4; dark paper#060a14 → #0e1320. - Marigold: graphite
#0a0a0a → #1f1f1f/#f5f5f5 → #d8d8d8; ink[#121212](https://github.com/beaket/ui/issues/121212) → #0a0a0a/#ececec → #f5f5f5; iron[#262626](https://github.com/beaket/ui/issues/262626) → [#141414](https://github.com/beaket/ui/issues/141414)/#d0d0d0 → #c0c0c0; dark paper#0a0a0a → [#101010](https://github.com/beaket/ui/issues/101010)(conservative — preserves neon identity). Shadow references rebound (--shadow-offsetnow usesiron;-darkvariant usesink) to keep the “ink-black heavy shadow” semantic intact after the ink/graphite value swap. - Tobacco: graphite
[#111110](https://github.com/beaket/ui/issues/111110) → #26231e/#eceae0 → #cfcabc; ink#1a1a18 → #14130f/#e2e0d6 → #eceae0; iron#312f2c → #1c1a16/#c0bcb2 → #b4b0a6; dark paper#0c0b0a → #14130f.
Token semantics clarified (via values, not rename):
graphite= prose body (tuned for sustained reading, 13–14:1 light, ~12:1 dark).ink= UI primary / strong interactive (kept punchy — buttons,bg-inktooltips, strong borders).iron= structural dark accent / shadow (moved below graphite on the dark axis to preserve scale distance now that graphite has softened).
Other:
--color-border-strongrebound fromgraphitetoinkso softening prose text does not weaken the brutalist border language.- New
@media (prefers-contrast: more)block instyles.cssrestores the original near-blackgraphitefor users who request maximum contrast at the OS level (accessibility escape hatch).
- Porcelain: graphite
-
#338
15835aaThanks @jihnma! - Addresizableprop toTextareaso it can auto-grow with content while also letting the user drag the handle to make it taller. The manually dragged height becomes a floor — content can still push it larger but won’t shrink it below the user’s chosen size. Closes #332.
Patch Changes#
- #335
618f25bThanks @jihnma! - Fix CSS token duplication betweensrc/css-variables.cssandsrc/themes/porcelain.css. The two files had drifted out of sync (different hex values forgraphite,ink,signal-blue,signal-green, surface layers) andcss-variables.csslacked the dark-mode block. Removedsrc/css-variables.cssand madesrc/themes/porcelain.cssthe single source of truth —src/styles.cssnow imports it directly.
2.2.0#
Minor Changes#
-
#307
2603a9cThanks @jihnma! - Add ref prop support to Input component for direct DOM access (e.g. focus management) -
#309
4d95decThanks @jihnma! - Add theme token sync to CLI. Runningnpx @beaket/ui add -onow also updates outdated CSS design tokens. Newnpx @beaket/ui themecommand for standalone theme sync and switching.
2.1.2#
Patch Changes#
-
#294
46aa6ecThanks @jihnma! - fix: Avatar.Image hydration mismatch in React 19 SSRAdded hydration guard to
Avatar.Imagethat defers rendering until after mount. This prevents React hydration error #418 caused by@radix-ui/react-use-is-hydratedreturningtrueduring client hydration in React 19, which made cached images render<img>while the server rendered<span>(fallback). -
#296
7cc8d36Thanks @jihnma! - fix(porcelain): adjust warning (signal-amber) color from brownish #d88810 to orange-yellow #e09800 for better visual distinction
2.1.1#
Patch Changes#
- #287
5c50154Thanks @jihnma! - Brighten porcelain warning color from olive-toned #b58a00 to warm vintage amber #d89018. Add signal-amber-text token across all themes for accessible text/icon usage on light surfaces.
2.1.0#
Minor Changes#
-
#257
09debe5Thanks @jihnma! - Add dark mode variants for all themes (Porcelain, Tobacco, Marigold, Eucalyptus) with automatic OS detection via prefers-color-scheme and manual light/dark toggle in docs -
#260
2a240ceThanks @jihnma! - Add button mode to Pagination component and refactor DataTable to use itPagination now supports
mode="button"with anonPageChangecallback for client-side pagination, in addition to the existingmode="link"(default) withbuildPageUrlfor SSR-friendly navigation.DataTable’s inline pagination has been replaced with the shared Pagination component, gaining ellipsis support for large page counts and ensuring visual consistency.
-
#236
fa45480Thanks @jihnma! - Remove 5 composed components in favor of primitive-first philosophyRemoved: ConfirmationDialog, ErrorPage, SidebarLayout, BlankSlate, PageHeader
These components were fixed-layout compositions of existing primitives (Dialog, Button, Alert, etc.) that restricted user freedom. Users should compose their own layouts using the primitive components directly.
-
#256
98083c3Thanks @jihnma! - Redesign signal colors across all themes for distinct personalities. Default green shifted from teal (167°) to emerald (150°) for clearer success recognition. Tobacco signals now use earthy pigments (indigo ink, brick, forest, ochre, plum, verdigris). Marigold pushed to max saturation with WCAG green fix (2.1:1 → ~5:1). Eucalyptus moved from Tailwind defaults to formal cobalt/crimson/jade/brass palette. -
#249
168128dThanks @jihnma! - Add surface layer tokens (surface-0,surface-1,surface-2) for visual depth between page, cards, and elevated elements. Surface containers (Card, Dialog, Sheet, Alert, Table) usebg-surface-1; floating overlays (DropdownMenu, Select popup) usebg-surface-2. -
#255
7e14e13Thanks @jihnma! - Add four themes: Porcelain (cold precision, teal accent), Tobacco (warm pampas cream, terracotta), Marigold (loud poster-print signals, ink-black shadows), Eucalyptus (enterprise titanium blue-gray). Each transforms neutrals, signal colors, and shadow geometry. New--themeCLI flag. Interactive demo at/themes.
Patch Changes#
-
#239
7b6b49fThanks @jihnma! - Fix accessibility and consistency issues across 14 components- DataTable: add keyboard support (Enter/Space) on clickable rows, optimize selection useEffect
- Button: add data-slot and default type=“button”
- Alert: remove line-clamp-1 from title
- NavigationProgress: add aria-valuetext
- Sheet: add hideCloseButton prop for Dialog API parity
- Input, Textarea, Select: normalize focus indicators to focus-visible:outline
- Radio: align border weight with Checkbox (border-graphite)
- Tooltip: remove shadow-offset from dark surface
- Blockquote: use border-l-2 to match border-width-medium token
- Checkbox, Radio, Switch, Dialog/Sheet close: expand touch targets to 44px
- Select: add disabled:border-chrome to match other form controls
-
#252
9d4f3e0Thanks @jihnma! - fix: add missingshadowprop to Avatar for API consistency with Card and Table -
#250
e9c59f6Thanks @jihnma! - fix: add missinggap-2toCard.Footerso children are spaced correctly -
#266
04be083Thanks @jihnma! - Improve component prop documentation: extract compound component props (Card, Navigation, Tabs, Select, DropdownMenu), show enum literal values instead of “enum”, filter className noise, fix ReactNode types showing as “any”, and add inline option listings to JSDoc comments -
#269
8d8c31cThanks @jihnma! - Fix accessibility issues in DataTable, Pagination, and Input components -
#240
5beb672Thanks @jihnma! - Add focus-visible indicator to DataTable clickable rows for keyboard navigation -
#264
7add468Thanks @jihnma! - fix: resolve React 19 ref collision in Textarea and stabilize effect dependencies in DataTable, Dialog, Sheet -
#253
cd1de9aThanks @jihnma! - fix: increase Switchsmsize from 12×28px to 16×32px for better visibility -
#251
5d86371Thanks @jihnma! - fix: increase default Table cell padding from px-1.5 py-1 to px-4 py-2/py-2.5 for better readability
2.0.0#
Major Changes#
- #230
5dda5cfThanks @jihnma! - BREAKING: Migrate CSS variables to Tailwind v4@themeconvention with--color-*prefix- CSS tokens moved from
:rootto@themeblock with--color-*prefix - Components now use clean Tailwind utilities (
bg-paperinstead ofbg-[var(--paper)]) - Unified color token values with Beaket app (chrome, platinum, signal-blue)
- Added
--color-muted(#737373) for WCAG AA-compliant muted text (replaces aluminum for text) - Added
--color-signal-red-text(#b91c1c) for accessible red text on light backgrounds - Accessibility improvements across all components:
- BlankSlate: decorative icons marked
aria-hidden - Navigation: default
aria-labelon nav landmark - Button: spinner wrapped with
aria-live="polite" - Switch: disabled state uses border-dashed pattern instead of opacity
- DataTable: search input has
aria-label - Checkbox: indeterminate state with Minus icon and ARIA support
- BlankSlate: decorative icons marked
- CSS tokens moved from
Minor Changes#
- #232
18ebbb6Thanks @jihnma! - Add 7 new components extracted from Beaket app- Skeleton: Loading placeholder with pulse animation
- ViewToggle: Generic toggle button group for switching between views
- NavigationProgress: Indeterminate progress bar for page navigation
- PageHeader: Page header with title, count, and action slot
- ErrorPage: Full-page error display with code, message, and action
- SidebarLayout: Responsive layout with content and sidebar slots
- ConfirmationDialog: Pre-built confirmation dialog with warning and loading state
Patch Changes#
- #233
c71d810Thanks @jihnma! - Fix accessibility and UX issues across 9 components- DataTable: add
aria-sorton sortable headers andscope="col"for screen reader table navigation - ViewToggle: make
labelrequired for icon-only buttons (a11y) - Skeleton: add
role="status"andaria-labelfor screen readers - Select: fix focus ring to use
focus-visible:consistently - Badge: add missing
data-slot="badge" - Avatar: add dev warning when
altprop is missing on Avatar.Image - Radio: fix broken disabled indicator dot color using
group-disabledpattern - Navigation: add
transition-shadow,active:shadow-offset-active, suppress shadow/hover when active - Switch: fix disabled+checked state showing green, improve thumb contrast when disabled
- DataTable: add
1.9.1#
Patch Changes#
-
#194
8c73b0bThanks @jihnma! - Improve focus visibility for Switch and Tabs components by using outline-based focus indicators instead of subtle border color changes -
#192
6c2e1cdThanks @jihnma! - Replace hardcodedwhitewith palette tokens in Input, Table, and Badge components
1.9.0#
Minor Changes#
-
#113
4e8f921Thanks @jihnma! - Add support for installing multiple components at once:npx @beaket/ui add alert button label -
#109
028aa68Thanks @jihnma! - ### Breaking Changes- Table: Migrated to compound component pattern
- Before:
import { Table, TableBody, TableCell, ... } from "@beaket/ui" - After:
import { Table } from "@beaket/ui"and useTable.Body,Table.Cell, etc.
- Before:
New Features#
- Input: Added
prefixandsuffixprops for icon support - Sheet: Added
fullScreenprop for full-width mobile navigation - Button: Hover/active states now use CSS variables for easier customization
- Added
--signal-green-hover,--signal-green-active - Added
--signal-red-hover,--signal-red-active - Added
--signal-amber-hover,--signal-amber-active
- Added
- Table: Migrated to compound component pattern
Patch Changes#
- #111
df61ccdThanks @jihnma! - Refactor CSS variables to use single source of truth- Extract core CSS variables to
src/css-variables.css - CLI now imports from generated file instead of hardcoding
- Add
pnpm sync:cssscript to sync variables to CLI
- Extract core CSS variables to
1.8.0#
Minor Changes#
6460127: Add -y flag to init command for non-interactive mode
Patch Changes#
dca002d: Fix bun detection to use bun.lock instead of bun.lockb
1.7.0#
Minor Changes#
-
ed42b5d: Improve Switch and Textarea componentsSwitch:
- Flatter, more horizontal proportions (reduced height by ~40%)
- Uniform 2px padding on all sides
- Corrected thumb translate values for symmetric left/right states
Textarea:
- Add
autoResizeprop (default: true) for automatic height adjustment based on content - Unify focus styling with Input component (ring-2 instead of border-only)
1.6.0#
Minor Changes#
1b61c60: Auto-detect component path based on tsconfig alias configuration
Patch Changes#
6ffa8c6: Fix CLI init to include @theme block with shadow utilities for Tailwind CSS 4
1.5.1#
Patch Changes#
1d6083e: Fix consistent form control borders and navigation active text- Standardize border color to —graphite for Checkbox, Select, Textarea
- Remove meaningless hover border changes
- Add text-inverse utility for navigation active state
- Fix docs global styles conflicting with component styles
1.5.0#
Minor Changes#
-
2fa9067: Add offset shadow system and new component variants- Button: Add offset shadow states, warning variant, mono prop
- Badge: Add warning and code variants
- Table: Add shadow prop, TableSectionHeader component
- Card, Tabs: Add optional shadow prop
- Dialog, Sheet, Dropdown, Tooltip: Add offset shadows
-
df80cc1: Add Blockquote, Breadcrumb, and Navigation components- Blockquote: Styled quotation with author attribution support
- Breadcrumb: Compound component for navigation hierarchy
- Navigation: Primary site navigation with offset shadow styling
1.4.0#
Minor Changes#
-
1b26167: Add Avatar component for displaying user profile images with fallback support -
69a50f4: Add form components: Label, Textarea, Select, and Switch- Label: Form label with accessibility support via @radix-ui/react-label
- Textarea: Multi-line text input with validation states
- Select: Dropdown select with grouped options using compound pattern (Select.Trigger, Select.Content, Select.Item, etc.)
- Switch: Toggle switch with size variants (sm, md, lg) via @radix-ui/react-switch
-
0a6eb92: Add Phase 2 components: Tooltip, Separator, Card, Tabs, Sheet, and Alert- Tooltip: Popup that displays information on hover or focus
- Separator: Visual divider for horizontal or vertical separation
- Card: Container component with Header, Title, Description, Action, Content, and Footer sub-components
- Tabs: Tab navigation component for switching between content panels
- Sheet: Slide-out panel from any edge of the screen (left, right, top, bottom)
- Alert: Callout component with semantic variants (note, tip, important, warning, caution)
-
21e60c3: Add Table and DataTable components- Table: Semantic HTML table components (TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption)
- DataTable: TanStack Table-based component with sorting, filtering, pagination, and row selection
- Update Button to use design tokens
- Update CLAUDE.md with library architecture and design philosophy
-
2304fb0: Add Dialog component with compound pattern, controlled/uncontrolled modes, and Storybook tests -
4028bf6: Add DropdownMenu component with compound pattern, checkbox/radio items, submenus, and keyboard shortcuts support -
27f74ac: Add Pagination and BlankSlate components (Phase 3 migration)- Pagination: Server-side pagination with ellipsis support for SSR-friendly navigation
- BlankSlate: Empty state component with preset icons and custom icon support
Patch Changes#
86722d0: Make Tooltip self-contained by including TooltipProvider internally
1.3.0#
Minor Changes#
495bc3c: Add Input component with brutalist design2633dec: Add Radio component for single-choice selection
1.2.0#
Minor Changes#
11f945c: Add Badge component with 6 variants: default, secondary, success, error, info, outline
1.1.1#
Patch Changes#
6916e4e: Add usage field to registry.json for component documentation
1.1.0#
Minor Changes#
4901272: Add Checkbox component with Radix UI primitives
1.0.0#
Major Changes#
-
5dfc775: Simplify CLI and component structureBreaking changes:
beaket.jsonnow only requirescomponentspath (removedtailwind,aliases,paths.utils)- Components are now single files (e.g.,
button.tsxinstead ofbutton/button.tsx) cnutility is now inlined in each component
New
beaket.jsonformat:{ "components": "src/components/ui" }Migration:
- Update
beaket.jsonto new format - Add CSS variables manually (see docs)
- Re-add components with
--overwriteflag
Minor Changes#
12e1451: Add overwrite prompt for existing files in CLI add command- Add
--overwrite(-o) flag to force overwrite without prompting - Prompt user for confirmation when a file already exists
- Show skipped files with instructions to use
--overwrite - Improve progress display with checkmarks
- Add
0.1.8#
Patch Changes#
6ecf976: Update CLI package description
0.1.7#
Patch Changes#
e30519c: Add JSDoc comments to Button component props
0.1.6#
Patch Changes#
e453371: docs: add JSDoc comment to Button componente180d0f: docs: add JSDoc comment to Button props14c4706: docs: add JSDoc comment to Spinner component51771f8: Rename ButtonProps to Props internally26470e9: fix: trigger auto-changeset only on PR open and use PR title7b1fb93: chore: apply code formatting and cleanup
0.1.5#
Patch Changes#
04f0200: Test changeset for verification