Repository navigation
Releases: chassis-ui/react
Release list
@chassis-ui/react v0.5.1
Patch Changes
- 82fde1c: The list of a
Menu,MenuSubmenu,ContextMenu,ComboboxorAutocompleteis no taller than the room beside its trigger, and scrolls inside it. Since 0.5.0 it took its height from--cx-menu-max-heightalone, which isnonefor aMenu, so a menu taller than the room on either side of its trigger ran off the viewport, and the items past the edge could not be reached. The list now takes the smaller of--cx-menu-max-heightand the room react-aria finds for it, as before 0.5.0, and the theme's cap still wins where it is the smaller one: aComboboxlist stays at 320px. Where the room is the limit, the list getsoverflow-y: auto.
@chassis-ui/react v0.5.0
Minor Changes
-
7d76ef8: Breaking:
@chassis-ui/css0.11 is required (the peer range is>=0.11.0 <0.12.0), and a project
that compiles its Sass needs@chassis-ui/tokens0.10.1. No prop, export or class name of this
package changes; css 0.8 to 0.11 change how a page looks with no change of markup. Headings and the
title roles are set in Montserrat, the base colors of the default tokens are new, and buttons,
badges, chips, pagination links, progress bars and the other components that had one lose their
background gradient ($enable-background-gradients: truekeeps it). The font family of
components is namedui:.font-uiand--cx-font-family-ui, where.font-textand
--cx-font-family-textare deprecated..h1to.h6are deprecated for.font-h1to
.font-h6. An SVG icon in aLinkwithiconLinkis drawn at1emof the link text instead of
--cx-icon-size(24 px with the default tokens); a size class such as.icon-lgon the icon makes
it larger. Blockquotes are Regular Italic instead of Light Italic. The changelogs of
@chassis-ui/css0.8.0 to 0.11.0 have the full list.style.cssis built with css 0.11 and reads the custom properties of theuifamily, such as
--cx-font-family-ui,--cx-font-size-ui-smand--cx-font-weight-ui-strong, which css 0.7
doesn't declare. The default of--cx-divider-label-font-sizeisvar(--cx-font-size-ui-sm),
the size it had.
-
36a439b: Fixes to the carousel, the time field, range presets, list items and the scrollspy, and two values that no longer render a class.
Carousel:- A controlled carousel (
activeIndex) is fed fromonSlide. The docs and the development warning also offeredonSlid, which cannot do it: it fires once a slide has arrived, and a controlled carousel doesn't leave for one untilactiveIndexnames it. The warning now fires whenonSlideis missing, with or withoutonSlid. - When the parent of a controlled carousel doesn't follow a swipe, the track scrolls back to the active slide once the swipe has been at rest for half a second, which leaves a parent that follows late the time to. It stayed on the slide the user had swiped to, and the next step that was followed didn't scroll.
onSlidreports the slidesactiveIndexmoved between when the parent chose the slide itself, or answered a request with another one. It reported the last step the carousel had asked for.- Autoplay under
ends="stop"is over when it arrives at the last slide. It waited one more interval there, with the indicator filling andCarouselPlayPausereading Pause.
- A controlled carousel (
- Breaking: an empty
TimeFieldwithrequiredholds its form back from submitting, and its first segment takes focus.requiredwasaria-requiredalone andform.checkValidity()wastrue. A form that should submit anyway needsnoValidate. - Breaking: a preset of
RangeCalendarorDateRangePickerwith an unavailable day in its range is disabled, as the grid can't select a range across one. It was committed as given. - A
ListItemwhosecomponentorasChildelement is a component, such as a router link, stays mounted when itshrefcomes and goes. It was remounted, and lost its state and focus. ScrollspyanduseScrollspymark the last section once the scrolling area is at its end. A last section shorter than the area below the activation line never became the active one.- Breaking:
CardImagerenders no class for anorientationthat@chassis-ui/csshas none for, and warns in development:toporbottomat a breakpoint, and any value at2xl. It rendered a class with no rule, such asmd:card-image-top, which did nothing unless your own CSS defined it. - Breaking:
Skeletonrenders no width class for aspanthat isn't a whole number from 1 to 12,'auto'or a boolean, such as0,13or'50%', and warns in development. It rendered a class with no rule, such asw-13/12.
-
9b2c9d1:
DataGrid'spin="end"columns are pinned inside the layout engine, from the column widths and positions it computes, instead of being measured from the page and moved by atransformafterwards.- An end-pinned column that nothing pushes off the grid stays where the layout put it. In a grid whose columns fit, it was moved by the grid's width and drawn past the last column; after a column narrower than react-stately's 75px minimum, it was drawn past the edge.
- An end-pinned column is in the page at any scroll position. One further right than the grid's width plus about a third wasn't rendered until scrolled to.
- An end-pinned header is drawn over the headers that scroll under it. It was under every header to its left.
pinis read off the column that has it, so columns in a fragment, a wrapper component or thecolumnsrender function ofDataGridHeaderpin as written. A header with a fragment pinned the wrong column, and the render-function form didn't pin to the end or get either scroll cue.- Under RTL, the end-pinned column is pinned to the left edge, and the grid takes
datagrid-scrolled-startonce scrolled. - Breaking: each scroll-cue shadow is on the edge content scrolls under, the trailing edge of the start group and the leading edge of the end group. They were on the outer edges. The defaults of
--datagrid-pin-cue-shadow-startand--datagrid-pin-cue-shadow-endchange to match, with a mirrored pair under[dir="rtl"]. A value of your own for either property is applied as before, on the edge its horizontal offset gives; to match the new default, flip the sign of that offset, and give[dir="rtl"]the opposite sign. - In Firefox, a
pin="start"header stays at the edge however often the grid is scrolled. After a couple of horizontal scrolls it scrolled away with the other columns.
-
7dd8314: Fixes to form inputs, the date pickers and the combobox family.
- Breaking:
requiredis the native attribute where it wasaria-requiredalone, so a form checks it: on aNumberField's input, on the radios of a requiredRadioGroup, and on the checkboxes of a requiredCheckboxGroupwhile none is selected.form.checkValidity()wastruefor each of them when empty. A form that should submit anyway needsnoValidate. - A
Radiopasses itsformto its input. ChipInputinside aFloatingInputis named by the floating label. It kept its fallbackaria-labeland was named "Add value Skills".OtpInputfocuses the next empty box after a partial paste. Focus went to the last filled box, where the next digit typed replaced it.- Breaking:
InputAdornwithcomponent="button"istype="button"unlesstypeis given. A password reveal adorn submitted the form around it. - A
Checkbox,RadioorSwitchwith no label appliesclassName, on the.check-inputelement, wherestylealready went. - Breaking:
Chipwritesaria-pressedonly on a chip with the button role. A staticpressedchip had it on a<span>with no role, and a chip that is a link (href, or an<a>ascomponentor theasChildelement) on its<a>, which axe reports for both; the.activeclass still shows it. Give a link chiprole="button"for it to be announced as pressed, as withButton. Progresskeeps the caller'saria-labelledby.DatePickerandDateRangePickerdescribe their segments and calendar button by the help and feedback, which reached only the group around them, and the group keeps react-aria's description of the selected date.- The server HTML of a
Comboboxwith no options no longer describes its input by the no-results message, which exists only once hydrated. Autocompleteleaves focus on the field the user moved to when its list closes. It always focused the toggle.onClick,onKeyDownandonFocusgiven to it run beside the toggle's own handlers; they were replaced.- Breaking: a preset of
RangeCalendarorDateRangePickeris selected as the grid selects a range: its dates in order, kept insideminValue/maxValue, in the calendar of the current value and with that value's times. A preset with no day inside the limits is disabled.
- Breaking:
-
92e7307:
Form'svalidatedshows every field in the form as valid or invalid.- Breaking:
validatedno longer renders the.was-validatedclass.@chassis-ui/csshas no rule for it, so a validated form showed nothing: no field looked valid, and an invalid one looked invalid only after a submit it had held back. A stylesheet that targetsform.was-validatedneeds another selector. - Every field in a
Formwithvalidatedshows itself as valid or invalid by its own constraints (required,type,patternand the like), and again as its value changes: its class,aria-invalid, and itsvalidFeedbackorinvalidFeedback(errorMessageon a group). Setinvalid, tofalsetoo, orvalidon a field, and that prop decides how the field looks instead. This holds forTextInput,Textarea,SearchField,NumberField,Select,FileInput,ColorInput,RangeInput,Combobox,Autocomplete,OtpInput,ChipInput,Checkbox,Switch,RadioGroup,CheckboxGroup,TimeField,DatePickerandDateRangePicker. A control that isn't one of these, such as a plain<input className="form-input">, isn't affected.
- Breaking:
-
2a52f70: Fixes to form fields,
Navbar,Carouseland a few small components.Autocomplete: the search field is a query only. After a selection it held the option's text when the list opened again, what was typed went after it so nothing matched, and emptying it cleared the selection. It is now empty each time the list opens, and the selection changes only when an option is chosen.Autocomplete:value={null}is a controlled empty selection, so a parent can clear the toggle. It was read as novalue, and the selection went uncontrolled.- Breaking: a single
Selectwith aplaceholderand novalue,...
@chassis-ui/react v0.4.1
Patch Changes
-
6cf7c60:
size="sm"ofSpinnermakes the spinner smaller. It renderedspinner-border-smorspinner-grow-sm, which are not classes of@chassis-ui/css, so the prop had no effect in either build. It now rendersspinner-sm, the class that sizes a spinner of both variants. -
34837b5: A Tailwind build of
@chassis-ui/cssno longer loses the color and alignment classes of seven components. They built the class name from the prop at runtime (`bg-${color}`), which Tailwind's scanner cannot read, so the class rendered with no rule and nothing warned. Each prop now maps to whole class names:colorofProgressBarandSkeleton(bg-*), ofSpinner(fg-*), ofAlertIcon(icon-*) and ofLink(link-*);alignofPlaceholder(float-start,float-end), ofPagination(justify-content-*), and ofTable,StaticTableandDataGrid(align-top,align-middle,align-bottom).
The markup of every value the props accept is the same as before. A value outside a prop's type, which rendered a class named after it, now renders none.
Tailwind does not scan
node_modules, so a Tailwind build has to register this package for the whole class names to be found:@source "../node_modules/@chassis-ui/react/dist";in the project's stylesheet, with the path from that file. The regular build of@chassis-ui/cssneeds nothing: it ships every class.
@chassis-ui/react v0.4.0
Minor Changes
-
daf128c:
RowandColare removed with the flexbox grid of@chassis-ui/css0.7.GridandGridItem
take what the grid of css 0.7 adds: row counts as a class, the flow, end lines and the container
breakpoints. The layout components have noresponsiveprop: each of their props takes its own
value per breakpoint.Breaking:
@chassis-ui/css0.7 is required (the peer range is>=0.7.0 <0.8.0), and a project
that compiles its Sass needs@chassis-ui/tokens0.7. css 0.7 changes how a page looks with no
change of markup: the gap of a grid with nogapis 1rem, and 1.5rem frommd, where it went from
0.5rem to 3rem; the padding of aContaineris 1rem, and 1.5rem frommd, where it was 0.75rem;
subtle text is less faint, and the context colors of the dark theme are lighter. The changelog of
@chassis-ui/css0.7.0 has the full list.- Breaking:
Row,Col,RowPropsandColPropsare removed. They were deprecated in 0.3.0, and
css 0.7 has none of the classes they rendered. The Grid page of the docs maps each of their props
toGridandGridItem(Migrating from Row/Col). - Breaking: the
responsiveprop ofFlex,Stack,Grid,GridItem,Card,CardBody,
CardImageandSkeletonis removed. Each prop it held takes the breakpoints itself: one value
for every width, or an object withbasefor the narrowest width and a key per breakpoint.
<GridItem span="full" responsive={{ md: { span: 6 } }}>is
<GridItem span={{ base: 'full', md: 6 }}>,
<Flex direction="column" responsive={{ md: { direction: 'row', gap: 'md' } }}>is
<Flex direction={{ base: 'column', md: 'row' }} gap={{ md: 'md' }}>, and
<Stack direction="vertical" responsive={{ md: 'horizontal' }}>is
<Stack direction={{ base: 'vertical', md: 'horizontal' }}>. The classes they render are the
same. With TypeScript, aresponsiveleft behind is a type error; without it, the prop reaches
the element as an attribute and the breakpoint classes are gone. TheResponsivetype is
exported, and theGridLayoutandGridItemLayouttypes are removed.responsiveofTable,
StaticTableandDraweris another prop, and stays. - Breaking:
rowsofGrid, and of asubgridGridItem, renders thegrid-rows-{n}class for a
count from 1 to 6, and setsgrid-template-rowsinline for any other. It set--cx-grid-rows,
which css 0.7 removed, so on css 0.7 the prop of 0.3 does nothing. A grid with norowsno longer
has one template row: its rows are as tall as their content. A grid nested in a grid withrows
no longer takes that row count. - Breaking:
<Grid fill>with nominwraps. Its columns are at least 12rem wide in css 0.7, where
every child shared one row. Setminfor another width. - Breaking:
start={13}androwStart={7}have no class in css 0.7, which stops the start lines
at the last track. Place an item against the end edge withend:span={3} end={13}. GridItemtakesendandrowEnd, a line number or'auto'(col-end-{n},row-end-{n}), for
every width or per breakpoint.Gridtakesflow:'row','column','dense','row-dense'or'column-dense', the values
of CSSgrid-auto-flow, rendered as thegrid-flow-*classes.flow="column"fills therows
of a column before it starts the next.flowandrowstake a value per breakpoint, as
columnsandgapdo. TheGridFlowtype is exported.- The props of
GridandGridItemtake the container keys'@sm'to'@2xl'beside the
breakpoint keys:span={{ '@md': 4 }}renders@md:col-span-4, which follows
the width of the nearest query container (an ancestor with thecontains-inlineclass) where
mdfollows the viewport. A container key wins over a breakpoint key where both apply. The
ContainerBreakpointtype is exported. Gridtakescontained(thecontainedclass): its default gutter and column count follow the
query container too.Skeleton: aspanof12or'auto'at a breakpoint works. It rendersmd:w-100and
md:w-auto, which css 0.7 has and css 0.6 lacked.
- Breaking:
@chassis-ui/react v0.3.0
Minor Changes
-
c35c029: Add
Alert, chassis-css's alert dialog: a<dialog role="alertdialog">opened as a modal dialog
for a decision the flow can't continue without, such as confirming a destructive action. Compose
AlertIcon,AlertBodywithAlertTitle,AlertCodeandAlertText, andAlertFooterwith
the actions.AlertCancelis the least destructive action: it closes the alert and has focus
when the alert opens. Open state works likeModal's (visible,defaultVisible,
onVisibleChange,onClose), but a backdrop click and Escape don't close an alert unless
backdropandkeyboardallow them. The title names the alert and the code and text describe
it. Also a subpath:@chassis-ui/react/alert.Modal,DrawerandAlertnow focus an element withdata-autofocuswhen they open. The docs
saidautofocusworked, but React writes noautofocusattribute in a page rendered in the
browser, so the dialog itself got focus instead. A closingModal,DrawerorAlertalso no longer
takes focus back from a dialog opened from it, and after a chain of them focus returns to the
element that opened the first. -
01309f3:
ContextMenu: the region a context menu belongs to. A right-click (contextmenu, so Ctrl+click on
macOS too), a long press by a finger or a pen, or Shift+F10 and the context menu key on a focused
element inside it open the menu at that point, in place of the browser's own. The menu is a
MenuListamong the region's children, withMenu's items, headers, dividers, submenus and
itemsdata, portaled to the body or to the open<dialog>around the region, and positioned with
its start corner at the pointer, flipped above it when there is no room below. The first item takes
focus on open; Escape and a click on an item close the menu and return focus to the element that
had it; a press outside closes it too. The region is a polymorphic element (component,
asChild) and takesvisible/defaultVisible/onVisibleChange,onShow/onHide,autoClose
asMenu's, anddisabled, which leaves the region to the browser.MenuListno longer writesaria-labelledbywhen it is given anaria-labelof its own, which
that attribute would have outranked, nor when there is no trigger to point at. -
d60c013:
GridandGridItemare the grid of the library, on the CSS grid of@chassis-ui/css0.6.Row
andColare deprecated with the flexbox grid they render.Breaking:
@chassis-ui/css0.6 is required (the peer range is>=0.6.0 <0.7.0). The components
below render classes that 0.5 does not have, so on 0.5 aGridItemis one track wide and a
Skeletonwith aspanhas no width. In css 0.6 the gap of a grid with nogapis the gutter of
the breakpoint, from 0.5rem on a phone to 3rem on the widest screens, where it was 1.5rem.GridItemrenderscol-span-{n}andcol-start-{n}, where it renderedg-col-{n}and
g-start-{n}.span="full"spans the row (col-span-full): an item with nospanis one track
wide, so a column that stacks below a breakpoint isspan="full" responsive={{ md: { span: 6 } }}.
start="auto"returns an item to the flow at a wider breakpoint.rowSpanandrowStartplace
it on the rows (row-span-{n},row-start-{n}).responsivetakes all four.Gridtakesresponsive, asFlexdoes:columnsat a breakpoint rendersgrid-cols-{n}(1
to 12) andgaprendersgap-{token}. TheGridLayouttype is exported.- Breaking:
columnsfrom 1 to 12 renders thegrid-cols-{n}class onGrid, where it set
--cx-grid-columns. The grid has the same columns. A grid nested in it no longer inherits the
count, as it did through the custom property: aGridinside<Grid columns={1}>has its own
12 columns again. A count with no class (13 and up) still sets--cx-grid-columns. - Breaking: a
Spacingtoken ingaprenders thegap-{token}class onGridand on asubgrid
item, where it set--cx-grid-gapto the token's custom property. The gap is the same. A grid
nested in it no longer inherits it, since a class is not inherited as the custom property was:
give a nested grid, and asubgriditem, thegapof its own. A raw value (gap="1rem") still
sets--cx-grid-gap. Grid filltakesmin, the minimum column width (--cx-grid-min), below which the children
wrap. Its rawgapsets--cx-grid-gap, where it set--cx-gap.SkeletonandSkeletonLoadertake the samespan,responsiveandspansvalues and render
width utilities where they rendered the classes of the flexbox grid:w-{n}/12for a count,
w-100for 12,w-autofor'auto'andflex-fillfortrue. The widths are the same. At a
breakpoint, 12 and'auto'render{breakpoint}:w-100and{breakpoint}:w-auto, which
@chassis-ui/css0.6.0 does not have: inresponsive, use a count up to 11, ortrue.RowandColstill render.rowand.col-*and work as before. They are marked
@deprecatedand warn once in development.@chassis-ui/cssremoves its flexbox grid in 0.7,
and they go with it.
To move from
RowandCol:Before After <Row><Grid>, with noContainerneeded around it<Col span={4}><GridItem span={4}><Col span={12}><GridItem span="full"><Col span={12} responsive={{ md: { span: 6 } }}><GridItem span="full" responsive={{ md: { span: 6 } }}><Col span={4} offset={2}><GridItem span={4} start={3}>, exact for the first item of a row<Col order="first"><GridItem className="order-first"><Row>with bare<Col>s, equal columns<Grid fill>with plain children<Row cols={3}>with bare<Col>s<Grid columns={3}>with plain children<Row cols={1} responsive={{ md: { cols: 3 } }}><Grid columns={1} responsive={{ md: { columns: 3 } }}><Row gutter="md"><Grid gap="md">gutterX,gutterYclassName="column-gap-{token}",className="row-gap-{token}"<Col span="auto">, columns as wide as their content<Flex gap="md">with plain children -
074f61f:
DatePickerandDateRangePickertake a time:granularity(day,hour,minute,second)
adds the time segments to the field, and the value becomes aCalendarDateTime, or a
ZonedDateTimefrom a zonedvalue,defaultValueorplaceholderValue. AlsohourCycle,
hideTimeZone,placeholderValueandshouldForceLeadingZeros, as onTimeField. Picking a day
in the calendar keeps the time; an empty field takes the time and zone ofdefaultValue(single
selection only), elseplaceholderValue, else midnight with no zone, so a controlled zoned value
needs a zonedplaceholderValueto stay zoned once cleared. The hidden input ofnameholds the
date and time, and aZonedDateTime's zone.Both pickers now show a value outside
minValue/maxValue, on an unavailable date, or a range
that ends before it starts as invalid, withinvalidFeedback, asTimeFielddoes;validgives
way to it.invalidnow also reaches the segments asaria-invalid.CalendarandRangeCalendartakedefaultFocusedValue, the date they first show; the pickers
pass theirplaceholderValue. Their "today" is the value's time zone's for aZonedDateTime, as
react-aria's label for the day already was, rather than always the browser's.A field given
invalidFeedbackorvalidFeedbackis wrapped in its.form-fieldbefore the
feedback shows, not only once it does: moving the control into the wrapper remounted it, and a
TimeFieldtyped out of its range lost focus. The ", " between a date and its time renders as
plain text, like the space before AM/PM; a date's own literals keep their spacing. -
5d6e2e8: Add
Divider: the line chassis-css draws with its reboot's<hr>, and with.vrwhen
orientation="vertical". Children become a label in the line ("or" between two ways to sign in),
placed withlabelPlacement(start,center,end), which names the separator for screen
readers. An<hr>by default, a<div role="separator">when vertical or labelled;component
andasChildrender any other element as the separator. Also a subpath:@chassis-ui/react/divider.The label, the vertical line and the line on elements other than
<hr>are in
@chassis-ui/react/style.css. The color, thickness, opacity and margin of the line, and the color,
size and gap of the label, are custom properties (--cx-divider-color,--cx-divider-size, ...),
defaulting to chassis-css's values for<hr>and.vr; a divider inside a menu takes the menu's.Also:
@chassis-ui/react/style.cssnow opens with chassis-css's layer order, so it works imported
before or after chassis-css's stylesheet. And underasChild, an element's ownaria-labelkeeps
naming it where the component would name it witharia-labelledby. -
df988a6: Add
NavOverflow, chassis-css's nav overflow (the Priority+ pattern): wrap aNav, aNavbarNav...
@chassis-ui/react@0.2.0
Minor Changes
-
54d4178: Every polymorphic component now accepts
asChild, which makes the polymorphic API usable from
React Server Components (#23). Pass the target as a child element instead of a component
reference, and the component merges its classes, props and ref onto that element:import Link from 'next/link' import { Button } from '@chassis-ui/react/button' // In a Server Component <Button asChild variant="outline"> <Link href="/login">Log in</Link> </Button>
<Button component={Link} href="/login">failsnext buildin a Server Component under Next.js
16 with "Functions cannot be passed directly to Client Components". A component reference is a
function and can't cross the server→client boundary; it only worked before because older
next/linkexports weren't plain functions. An element crosses the boundary without trouble.
The rendered<a>gets the button's classes, andnext/linkstill handles the navigation
client-side.The child's own props win over the component's, except that class names are combined and event
handlers are chained (the component's runs first). Ifchildrenisn't exactly one element,
asChildlogs a dev warning and the component renders its default element.asChildtakes
precedence when both it andcomponentare passed.asChildis implemented once in the shared polymorphic wrapper, so it works on every component
with acomponentprop.SkeletonLoaderis the exception because it renders no element of its
own.componentitself is unchanged. -
3e3f7c1: Icons are now configurable, and the components that draw icons of their own no longer depend on
Icon.Breaking:
Iconno longer references/static/icons/chassis-icons.svgby default. With no
spriteset, it renders<use href="#name">, which works for a sprite embedded in the page. To
keep the old behavior, set the path once for the whole app:<IconProvider sprite="/static/icons/chassis-icons.svg">{children}</IconProvider>
New
IconProvider. It sets defaults for everyIconbelow it (sprite,className,font,
fontPrefixfor icon fonts generated with a prefix other thancx-) and configures the icons
this library's own components draw. Nested providers extend the one above them.Replaceable library icons.
Pagination,CarouselControlPrev/CarouselControlNext,
CarouselPlayPause,NavbarToggler, the check on a selected menu or combobox item, and a string
icononToast/Notificationnow ask for their icon by purpose:check,previous,next,
menu,playorpause. You can replace any of them with another icon name or with an element
from any icon set:import { Check, ChevronLeft, ChevronRight } from 'lucide-react' <IconProvider icons={{ check: <Check />, previous: <ChevronLeft />, next: <ChevronRight /> }}>
You can also render every icon name with your own icon component (
component={MyIcon}, set in a
'use client'module). A single instance can override its icon with the newpreviousIcon/
nextIcon(Pagination),icon(carousel prev/next controls, NavbarToggler), and
playIcon/pauseIcon(CarouselPlayPause) props. A custom icon gets the class the component
positions it by, includingdirectional-icon, so arrows still flip in right-to-left layouts. It
doesn't get.icon, whosefillwould paint over outline icon sets.Pagination's previous/next chevrons are now the outline style, matching the carousel's.
Fix:
Icon'ssizehad no visible effect when chassis-css was loaded. It set only the SVG's
width/heightattributes, which chassis-css's.iconsizing overrides, sosize={48}rendered
at 24px. It now sets--cx-icon-size, which also makes it work for font glyphs, and accepts CSS
lengths as well as pixel numbers (size="1.25rem"). -
6103315: Adds
StaticTable, a server-renderable table that uses no client JavaScript (#20). It takes the
same styling props asTable(bordered,borderless,hover,striped,sm,color,
align,responsive,stacked, pluscaption) and renders your own
<thead>/<tbody>/<tfoot>/<tr>/<th>/<td>markup as written. It has no sorting, selection
or keyboard grid navigation; those remainTable's job.Import it from
@chassis-ui/react/static-table. It's the only entry point without a
'use client'directive, so a React Server Component renders it as a real Server Component. In a
Next.js 16 app, a route rendering only aStaticTableships no client JS beyond Next's own
baseline. Links,next/linkand server-action forms in cells work as they do in any server markup.import { StaticTable } from '@chassis-ui/react/static-table' <StaticTable hover striped caption="Members"> <thead> <tr><th scope="col">Name</th></tr> </thead> <tbody> {members.map((m) => ( <tr key={m.id}><td data-cell="Name">{m.name}</td></tr> ))} </tbody> </StaticTable>
For
stackedtables, give each<td>adata-cellattribute with its label.Tablereads that
label from its column headers, but a static table has no column data to derive it from.
StaticTableis exported from the root entry too, for use inside Client Components. -
a92d1e9: Every component family is now published as its own entry point, so a page only ships the
components it actually imports (#22). The subpaths are named after the component folders:import { Button } from '@chassis-ui/react/button' import { TextInput } from '@chassis-ui/react/text-input' import { Modal, ModalBody, useModal } from '@chassis-ui/react/modal'
Until now the package was a single
dist/index.jsbehind one'use client'directive. Turbopack
(Next.js 16's default bundler) can't tree-shake unused exports out of a'use client'module, so a
page rendering oneButtonshipped every component, and the react-aria/react-stately hooks behind
them, to the browser: about 214 KB gzip of client JS on top of Next.js's own baseline, whichever
bundler was used. Importing the sameButtonfrom@chassis-ui/react/buttonships about 13 KB
under Turbopack and 11 KB under webpack. Each subpath carries its own'use client'directive, so it
works directly from a Server Component, just like the root entry.The root
@chassis-ui/reactentry is unchanged and still exports everything. Both import styles
resolve to the same shared chunks, so mixing them is safe:Modalfrom/modalanduseModalfrom
the root read the same context. Each family's hook is also exported from its subpath (useModal,
useDrawer,useToast,useNotification,usePagination).sideEffectsinpackage.jsonis now accurate. Only the entry files, the chunk that installs the
global focus-ring listener, andstyle.cssare marked as having side effects, so webpack and Vite
also drop unused components from a root import (about 16 KB for the same page under webpack). This
also fixes a latent build bug: the listener's side-effect import was being tree-shaken out of the
bundle, and it only survived becauseRangeCalendarhappened to import the same module.The
DataGrid*Propstypes are now exported from the root entry too. Before, they were only
reachable from the component's own module.
@chassis-ui/react@0.1.3
Patch Changes
-
0b72684: Fix
Radio,CheckboxandSwitchsilently discardingchildren, which left the control with
no accessible name.childrenwas never omitted from their props types (it comes in via
InputHTMLAttributes), so<Radio value="a">Option A</Radio>type-checked and rendered — and
then the implementation's{ ...rest, children: label }overwrote it with an undefinedlabel,
so react-aria had nothing to build a name from and the visible text was dropped. A WCAG 4.1.2
failure that no type error, runtime warning or snapshot would catch; it only shows up in an
accessibility check or a query by accessible name.childrenis now accepted as an alias forlabel, which is what react-aria calls the same thing
(AriaRadioProps.children) before this package renames it — so the shape React developers reach
for first names the control.labelstill wins when both are given.Switch's radio-backed
variant (type="radio") is fixed too: it spreads its rest props straight onto the<input>, so
children there did not merely vanish, they reached a void element and React threw.
@chassis-ui/react@0.1.2
Patch Changes
- 111ad96: Fix a guaranteed SSR hydration-mismatch warning on
Table's auto-injected select-all column
(selectionMode="multiple"). react-stately's@react-stately/tablederives that column's key
from a module-scoped'row-header-column-' + Math.random()constant computed once per JS
environment, so theid/data-keyattributes react-aria puts onTableSelectAllCell/
TableSelectionCellnever match between the server render and the browser's hydration pass. The
mismatch is confined to that internal bookkeeping attribute (no visible content, nothing else
references it), so both cells now render withsuppressHydrationWarning— the underlying
react-stately behavior is unchanged and not something this package can fix.
@chassis-ui/react@0.1.1
Patch Changes
-
02594b4: Fix
Paginationdropping keyboard focus every time the page changes.PaginationItemrendered the active item as a<span>, so activating a page unmounted the focused
<button>and mounted a different element in its place — focus fell to<body>and a keyboard user
had to tab in from the top of the document again (WCAG 2.4.3). The active item now keeps whatever
element it would render anyway (<button>, or<a>when givenhref), so the element identity is
stable across activation. The<span>wasn't inert either: smart mode passes anonClick, so it
picked uprole="button"andtabIndex={0}— a synthetic button in place of the real one.aria-current="page"also moved from the wrapping<li>onto the control itself, which is where
the WAI-ARIA pagination pattern puts it and where a screen reader conveys it when focus lands.
Styling is unchanged: chassis-css matches.pagination-link { &.active, .active > & }andactive
still lands on the<li>. -
02594b4: Adds
DataGrid, a virtualized alternative toTablefor datasets too large to mount all at once — only the rows scrolled into view are ever rendered, so a grid with thousands of rows costs the same to render as one with a dozen. Composed the same way asTable(DataGridHeader/DataGridColumn/DataGridBody/DataGridRow/DataGridCell), plusDataGridSelectAllCell/DataGridSelectionCellfor selection checkboxes.Beyond basic virtualized rows, columns, sorting, and selection, it also supports:
- Pinned columns —
pin="start"/pin="end"on aDataGridColumnkeeps it visible while the grid scrolls horizontally, with multiple pinned columns on the same side stacking in declaration order. - Variable row height —
rowHeight="auto"(paired withestimatedRowHeight) measures each row from its own rendered content instead of a single fixed height shared by every row. - Async/infinite loading —
onLoadMore/isLoading/loadingContentonDataGridBodyfetch and append more rows as the grid scrolls near its last one, for datasets too large to load up front. - Header/footer pinning — the header row stays pinned to the top of the grid's scrollable area as body rows scroll underneath it; a new
footerprop onDataGridrenders content (e.g. a totals row) pinned below the body, outside the grid's own keyboard-navigable structure.
This adds
react-aria-componentsas a runtime dependency —DataGridis built on itsVirtualizer, and it's the only component in the package that uses it. It's externalized from the bundle, so it resolves from your ownnode_moduleslikereact-ariaalready does. Note thatreact-aria-componentspinsreact-ariato an exact version, so an install may end up with a second copy ofreact-ariaalongside the one this package depends on; nothing here relies on the two being the same instance.See the
datagrid.mdxdocs page for the full set of examples and documented scope boundaries (e.g. no stacked variant, no zebra striping, no interactive column resize/reorder — all deliberate given the virtualized rendering model). - Pinned columns —
-
02594b4: Export every component's prop types from the package entry point.
ButtonProps,TableProps,MenuItemDef,Placement, the shared scales (ContextColor,
Sizing,Spacing, ...) and the public hooks' result types were all declared but never exported,
soimport type { ButtonProps } from '@chassis-ui/react'failed and consumers had to re-derive
them withComponentProps<typeof Button>. All 164 type names the component barrels already
export are now reachable from the package root.As a side effect,
TableBodyProps/TableHeaderPropsare no longer emitted under$1-suffixed
names indist/index.d.ts— now that this package exports its own, react-stately's same-named
types are the ones that get suffixed instead. -
02594b4: Fix
PopoverandTooltipdropping the trigger child'sref.Both re-render their child with
cloneElement, whose config replaces the child'srefoutright
rather than merging with it, so<Tooltip><Button ref={mine} /></Tooltip>never populatedmine.
Both now fork the caller's ref with their own.Tooltipadditionally spread its react-aria
triggerPropsover the child's props, silently dropping a handler the caller had put on their own
trigger (e.g.onFocus); both now merge them withmergePropsinstead. -
02594b4: Make every user-facing string the components render themselves translatable.
I18nProviderdrives month names, weekday names, segment order and the calendar system through
react-aria, but a handful of strings these components render themselves had no equivalent in
react-aria's dictionaries and were hardcoded English with no way to override them. Under
<I18nProvider locale="ar-SA">a user got Arabic month names interleaved with an English
"Previous years".Calendar,RangeCalendar,DatePickerandDateRangePickertake a newlabelsprop
(Partial<CalendarLabels>, merged over the English defaults) covering the year view's paging
arrows and live-region announcements, the month/year header buttons, the calendar trigger, and
the clear adornment. ADatePicker'slabelsalso reaches theCalendarit renders internally,
so it's set once. TheCalendarLabelstype is exported.Breadcrumbacceptsaria-labelfor its wrapping<nav>landmark, which previously hardcoded
"breadcrumb"on an element{...rest}never reached. This also lets two breadcrumb trails on
one page be told apart in a screen reader's landmark list.
-
02594b4: Fix six behavioural defects in focus handling, overlay callbacks, handler composition and form
field rendering.focusRedirectcould suppress a focus ring permanently. It saved the element's outline,
forced it off, and restored it on blur — but a second call before that blur captured the
already-suppressednoneas the value to restore. Reachable by clicking twice at the same
disabled end of anends="stop"Carousel. It now delegates the whole suppress/restore cycle
tosuppressFocusRing, which owns the re-entrancy guard, rather than reimplementing it without
one.Popoverstole focus back to its trigger on every close. Dismissing by clicking another
control moved focus off whatever the user had just clicked. Focus is now reclaimed only when the
popover still holds it, or when nothing does.Popover,TooltipandMenureported a hide on mount. The visibility effect ran its
"hidden" branch on the initial commit, soonHide(andMenu'sonHidden) fired for an overlay
that had never been shown, beforeonShowhad fired once. All of these report transitions now,
so none fires on mount — includingonShow/onShownfor an overlay mounted already-open.Carousellet a caller's handler replace its own.onKeyDown,onMouseEnterand
onMouseLeavewere overwritten by the props spread, so passing any of them silently switched off
arrow-key navigation or pause-on-hover. They are composed now, matching the rule
CarouselControlPrev/CarouselControlNext/CarouselPlayPausealready follow foronClick.ModalandDrawerdropped a caller'sonClickentirely. A<dialog>needsonClickfor
its own backdrop detection, and the caller's was spread alongside it. It is chained ahead of the
backdrop handling now, and fires for every click on the dialog.- A field set both
invalidandvalidrendered a duplicate DOM id. Both feedback nodes carry
the same id, leaving every control'saria-describedbypointing at an ambiguous target. At most
one renders now, invalid winning.
Two of these change behaviour beyond restoring the documented contract, hence a minor rather than a
patch:- An overlay mounted already-open (
<Popover visible>) no longer firesonShow/onShownon
mount. Wire up to the transition instead, or read the prop you already control. FormFieldgiven aclassNamebut nolabel/help/feedback now renders its.form-field
wrapper instead of returning the children bare — previously the class was silently dropped, so it
had nowhere to land. Fields with noclassNamestill render bare, unchanged.
-
02594b4: Fix published types and package metadata that were wrong for consumers.
usePagination'sprevRef/nextRefare typedRefObject<T | null>, matching the
useRef<T>(null)they actually come from. They were declared non-null, soprevRef.current.focus()
type-checked and then threw before mount.- The polymorphic components that pick their element from
hrefrather thancomponent—
Button,Chip,Avatar,NavbarBrand,PaginationItem— accept arefcovering the elements
they can actually render.<Button href="/x" ref={anchorRef}>was a type error even though the
runtime populated that ref with the<a>it rendered. - Dropped the
enginesfield. This is a browser library with no Node runtime requirement, and
engines.node: '>=24'warned on install for consumers on older Node and hard-failed under
engine-strict. It also drove the build's output target; the build now pinses2022explicitly,
alongside a newbrowserslistfield documenting the supported floor (Chrome 107+, Edge 107+,
Firefox 104+, Safari 16+). sideEffectsnow listsdist/paths. It named asrc/path no consumer resolves, so the whole
published bundle was flagged side-effect-free while actually carrying the global focus-ring
listener install.- The published bundle carries exactly one
'use client'directive; it previously carried two.
-
02594b4: Stop dev-time misuse warnings from firing in production, and fix arrow-key direction under RTL.
- Every
console.warn/console.errorin the package now goes throug...
- Every