Skip to content

Releases: chassis-ui/react

@chassis-ui/react v0.5.1

Choose a tag to compare

@github-actions github-actions released this 20 Oct 18:37

Patch Changes

  • 82fde1c: The list of a Menu, MenuSubmenu, ContextMenu, Combobox or Autocomplete is no taller than the room beside its trigger, and scrolls inside it. Since 0.5.0 it took its height from --cx-menu-max-height alone, which is none for a Menu, 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-height and 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: a Combobox list stays at 320px. Where the room is the limit, the list gets overflow-y: auto.

@chassis-ui/react v0.5.0

Choose a tag to compare

@github-actions github-actions released this 20 Oct 17:25

Minor Changes

  • 7d76ef8: Breaking: @chassis-ui/css 0.11 is required (the peer range is >=0.11.0 <0.12.0), and a project
    that compiles its Sass needs @chassis-ui/tokens 0.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: true keeps it). The font family of
    components is named ui: .font-ui and --cx-font-family-ui, where .font-text and
    --cx-font-family-text are deprecated. .h1 to .h6 are deprecated for .font-h1 to
    .font-h6. An SVG icon in a Link with iconLink is drawn at 1em of the link text instead of
    --cx-icon-size (24 px with the default tokens); a size class such as .icon-lg on the icon makes
    it larger. Blockquotes are Regular Italic instead of Light Italic. The changelogs of
    @chassis-ui/css 0.8.0 to 0.11.0 have the full list.

    • style.css is built with css 0.11 and reads the custom properties of the ui family, such as
      --cx-font-family-ui, --cx-font-size-ui-sm and --cx-font-weight-ui-strong, which css 0.7
      doesn't declare. The default of --cx-divider-label-font-size is var(--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 from onSlide. The docs and the development warning also offered onSlid, which cannot do it: it fires once a slide has arrived, and a controlled carousel doesn't leave for one until activeIndex names it. The warning now fires when onSlide is missing, with or without onSlid.
      • 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.
      • onSlid reports the slides activeIndex moved 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 and CarouselPlayPause reading Pause.
    • Breaking: an empty TimeField with required holds its form back from submitting, and its first segment takes focus. required was aria-required alone and form.checkValidity() was true. A form that should submit anyway needs noValidate.
    • Breaking: a preset of RangeCalendar or DateRangePicker with an unavailable day in its range is disabled, as the grid can't select a range across one. It was committed as given.
    • A ListItem whose component or asChild element is a component, such as a router link, stays mounted when its href comes and goes. It was remounted, and lost its state and focus.
    • Scrollspy and useScrollspy mark 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: CardImage renders no class for an orientation that @chassis-ui/css has none for, and warns in development: top or bottom at a breakpoint, and any value at 2xl. It rendered a class with no rule, such as md:card-image-top, which did nothing unless your own CSS defined it.
    • Breaking: Skeleton renders no width class for a span that isn't a whole number from 1 to 12, 'auto' or a boolean, such as 0, 13 or '50%', and warns in development. It rendered a class with no rule, such as w-13/12.
  • 9b2c9d1: DataGrid's pin="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 a transform afterwards.

    • 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.
    • pin is read off the column that has it, so columns in a fragment, a wrapper component or the columns render function of DataGridHeader pin 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-start once 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-start and --datagrid-pin-cue-shadow-end change 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: required is the native attribute where it was aria-required alone, so a form checks it: on a NumberField's input, on the radios of a required RadioGroup, and on the checkboxes of a required CheckboxGroup while none is selected. form.checkValidity() was true for each of them when empty. A form that should submit anyway needs noValidate.
    • A Radio passes its form to its input.
    • ChipInput inside a FloatingInput is named by the floating label. It kept its fallback aria-label and was named "Add value Skills".
    • OtpInput focuses the next empty box after a partial paste. Focus went to the last filled box, where the next digit typed replaced it.
    • Breaking: InputAdorn with component="button" is type="button" unless type is given. A password reveal adorn submitted the form around it.
    • A Checkbox, Radio or Switch with no label applies className, on the .check-input element, where style already went.
    • Breaking: Chip writes aria-pressed only on a chip with the button role. A static pressed chip had it on a <span> with no role, and a chip that is a link (href, or an <a> as component or the asChild element) on its <a>, which axe reports for both; the .active class still shows it. Give a link chip role="button" for it to be announced as pressed, as with Button.
    • Progress keeps the caller's aria-labelledby.
    • DatePicker and DateRangePicker describe 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 Combobox with no options no longer describes its input by the no-results message, which exists only once hydrated.
    • Autocomplete leaves focus on the field the user moved to when its list closes. It always focused the toggle. onClick, onKeyDown and onFocus given to it run beside the toggle's own handlers; they were replaced.
    • Breaking: a preset of RangeCalendar or DateRangePicker is selected as the grid selects a range: its dates in order, kept inside minValue/maxValue, in the calendar of the current value and with that value's times. A preset with no day inside the limits is disabled.
  • 92e7307: Form's validated shows every field in the form as valid or invalid.

    • Breaking: validated no longer renders the .was-validated class. @chassis-ui/css has 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 targets form.was-validated needs another selector.
    • Every field in a Form with validated shows itself as valid or invalid by its own constraints (required, type, pattern and the like), and again as its value changes: its class, aria-invalid, and its validFeedback or invalidFeedback (errorMessage on a group). Set invalid, to false too, or valid on a field, and that prop decides how the field looks instead. This holds for TextInput, Textarea, SearchField, NumberField, Select, FileInput, ColorInput, RangeInput, Combobox, Autocomplete, OtpInput, ChipInput, Checkbox, Switch, RadioGroup, CheckboxGroup, TimeField, DatePicker and DateRangePicker. A control that isn't one of these, such as a plain <input className="form-input">, isn't affected.
  • 2a52f70: Fixes to form fields, Navbar, Carousel and 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 no value, and the selection went uncontrolled.
    • Breaking: a single Select with a placeholder and no value,...
Read more

@chassis-ui/react v0.4.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 12:06

Patch Changes

  • 6cf7c60: size="sm" of Spinner makes the spinner smaller. It rendered spinner-border-sm or spinner-grow-sm, which are not classes of @chassis-ui/css, so the prop had no effect in either build. It now renders spinner-sm, the class that sizes a spinner of both variants.

  • 34837b5: A Tailwind build of @chassis-ui/css no 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:

    • color of ProgressBar and Skeleton (bg-*), of Spinner (fg-*), of AlertIcon (icon-*) and of Link (link-*);
    • align of Placeholder (float-start, float-end), of Pagination (justify-content-*), and of Table, StaticTable and DataGrid (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/css needs nothing: it ships every class.

@chassis-ui/react v0.4.0

Choose a tag to compare

@github-actions github-actions released this 07 Oct 21:31

Minor Changes

  • daf128c: Row and Col are removed with the flexbox grid of @chassis-ui/css 0.7. Grid and GridItem
    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 no responsive prop: each of their props takes its own
    value per breakpoint.

    Breaking: @chassis-ui/css 0.7 is required (the peer range is >=0.7.0 <0.8.0), and a project
    that compiles its Sass needs @chassis-ui/tokens 0.7. css 0.7 changes how a page looks with no
    change of markup: the gap of a grid with no gap is 1rem, and 1.5rem from md, where it went from
    0.5rem to 3rem; the padding of a Container is 1rem, and 1.5rem from md, 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/css 0.7.0 has the full list.

    • Breaking: Row, Col, RowProps and ColProps are 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
      to Grid and GridItem (Migrating from Row/Col).
    • Breaking: the responsive prop of Flex, Stack, Grid, GridItem, Card, CardBody,
      CardImage and Skeleton is removed. Each prop it held takes the breakpoints itself: one value
      for every width, or an object with base for 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, a responsive left behind is a type error; without it, the prop reaches
      the element as an attribute and the breakpoint classes are gone. The Responsive type is
      exported, and the GridLayout and GridItemLayout types are removed. responsive of Table,
      StaticTable and Drawer is another prop, and stays.
    • Breaking: rows of Grid, and of a subgrid GridItem, renders the grid-rows-{n} class for a
      count from 1 to 6, and sets grid-template-rows inline 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 no rows no longer
      has one template row: its rows are as tall as their content. A grid nested in a grid with rows
      no longer takes that row count.
    • Breaking: <Grid fill> with no min wraps. Its columns are at least 12rem wide in css 0.7, where
      every child shared one row. Set min for another width.
    • Breaking: start={13} and rowStart={7} have no class in css 0.7, which stops the start lines
      at the last track. Place an item against the end edge with end: span={3} end={13}.
    • GridItem takes end and rowEnd, a line number or 'auto' (col-end-{n}, row-end-{n}), for
      every width or per breakpoint.
    • Grid takes flow: 'row', 'column', 'dense', 'row-dense' or 'column-dense', the values
      of CSS grid-auto-flow, rendered as the grid-flow-* classes. flow="column" fills the rows
      of a column before it starts the next. flow and rows take a value per breakpoint, as
      columns and gap do. The GridFlow type is exported.
    • The props of Grid and GridItem take 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 the contains-inline class) where
      md follows the viewport. A container key wins over a breakpoint key where both apply. The
      ContainerBreakpoint type is exported.
    • Grid takes contained (the contained class): its default gutter and column count follow the
      query container too.
    • Skeleton: a span of 12 or 'auto' at a breakpoint works. It renders md:w-100 and
      md:w-auto, which css 0.7 has and css 0.6 lacked.

@chassis-ui/react v0.3.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 22:37

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, AlertBody with AlertTitle, AlertCode and AlertText, and AlertFooter with
    the actions. AlertCancel is the least destructive action: it closes the alert and has focus
    when the alert opens. Open state works like Modal's (visible, defaultVisible,
    onVisibleChange, onClose), but a backdrop click and Escape don't close an alert unless
    backdrop and keyboard allow them. The title names the alert and the code and text describe
    it. Also a subpath: @chassis-ui/react/alert.

    Modal, Drawer and Alert now focus an element with data-autofocus when they open. The docs
    said autofocus worked, but React writes no autofocus attribute in a page rendered in the
    browser, so the dialog itself got focus instead. A closing Modal, Drawer or Alert also 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
    MenuList among the region's children, with Menu's items, headers, dividers, submenus and
    items data, 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 takes visible/defaultVisible/onVisibleChange, onShow/onHide, autoClose
    as Menu's, and disabled, which leaves the region to the browser.

    MenuList no longer writes aria-labelledby when it is given an aria-label of its own, which
    that attribute would have outranked, nor when there is no trigger to point at.

  • d60c013: Grid and GridItem are the grid of the library, on the CSS grid of @chassis-ui/css 0.6. Row
    and Col are deprecated with the flexbox grid they render.

    Breaking: @chassis-ui/css 0.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 a GridItem is one track wide and a
    Skeleton with a span has no width. In css 0.6 the gap of a grid with no gap is the gutter of
    the breakpoint, from 0.5rem on a phone to 3rem on the widest screens, where it was 1.5rem.

    • GridItem renders col-span-{n} and col-start-{n}, where it rendered g-col-{n} and
      g-start-{n}. span="full" spans the row (col-span-full): an item with no span is one track
      wide, so a column that stacks below a breakpoint is span="full" responsive={{ md: { span: 6 } }}.
      start="auto" returns an item to the flow at a wider breakpoint. rowSpan and rowStart place
      it on the rows (row-span-{n}, row-start-{n}). responsive takes all four.
    • Grid takes responsive, as Flex does: columns at a breakpoint renders grid-cols-{n} (1
      to 12) and gap renders gap-{token}. The GridLayout type is exported.
    • Breaking: columns from 1 to 12 renders the grid-cols-{n} class on Grid, 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: a Grid inside <Grid columns={1}> has its own
      12 columns again. A count with no class (13 and up) still sets --cx-grid-columns.
    • Breaking: a Spacing token in gap renders the gap-{token} class on Grid and on a subgrid
      item, where it set --cx-grid-gap to 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 a subgrid item, the gap of its own. A raw value (gap="1rem") still
      sets --cx-grid-gap.
    • Grid fill takes min, the minimum column width (--cx-grid-min), below which the children
      wrap. Its raw gap sets --cx-grid-gap, where it set --cx-gap.
    • Skeleton and SkeletonLoader take the same span, responsive and spans values and render
      width utilities where they rendered the classes of the flexbox grid: w-{n}/12 for a count,
      w-100 for 12, w-auto for 'auto' and flex-fill for true. The widths are the same. At a
      breakpoint, 12 and 'auto' render {breakpoint}:w-100 and {breakpoint}:w-auto, which
      @chassis-ui/css 0.6.0 does not have: in responsive, use a count up to 11, or true.
    • Row and Col still render .row and .col-* and work as before. They are marked
      @deprecated and warn once in development. @chassis-ui/css removes its flexbox grid in 0.7,
      and they go with it.

    To move from Row and Col:

    Before After
    <Row> <Grid>, with no Container needed 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, gutterY className="column-gap-{token}", className="row-gap-{token}"
    <Col span="auto">, columns as wide as their content <Flex gap="md"> with plain children
  • 074f61f: DatePicker and DateRangePicker take a time: granularity (day, hour, minute, second)
    adds the time segments to the field, and the value becomes a CalendarDateTime, or a
    ZonedDateTime from a zoned value, defaultValue or placeholderValue. Also hourCycle,
    hideTimeZone, placeholderValue and shouldForceLeadingZeros, as on TimeField. Picking a day
    in the calendar keeps the time; an empty field takes the time and zone of defaultValue (single
    selection only), else placeholderValue, else midnight with no zone, so a controlled zoned value
    needs a zoned placeholderValue to stay zoned once cleared. The hidden input of name holds the
    date and time, and a ZonedDateTime'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, with invalidFeedback, as TimeField does; valid gives
    way to it. invalid now also reaches the segments as aria-invalid.

    Calendar and RangeCalendar take defaultFocusedValue, the date they first show; the pickers
    pass their placeholderValue. Their "today" is the value's time zone's for a ZonedDateTime, as
    react-aria's label for the day already was, rather than always the browser's.

    A field given invalidFeedback or validFeedback is wrapped in its .form-field before the
    feedback shows, not only once it does: moving the control into the wrapper remounted it, and a
    TimeField typed 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 .vr when
    orientation="vertical". Children become a label in the line ("or" between two ways to sign in),
    placed with labelPlacement (start, center, end), which names the separator for screen
    readers. An <hr> by default, a <div role="separator"> when vertical or labelled; component
    and asChild render 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.css now opens with chassis-css's layer order, so it works imported
    before or after chassis-css's stylesheet. And under asChild, an element's own aria-label keeps
    naming it where the component would name it with aria-labelledby.

  • df988a6: Add NavOverflow, chassis-css's nav overflow (the Priority+ pattern): wrap a Nav, a NavbarNav...

Read more

@chassis-ui/react@0.2.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 19:55
4d241a5

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"> fails next build in 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/link exports weren't plain functions. An element crosses the boundary without trouble.
    The rendered <a> gets the button's classes, and next/link still 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). If children isn't exactly one element,
    asChild logs a dev warning and the component renders its default element. asChild takes
    precedence when both it and component are passed.

    asChild is implemented once in the shared polymorphic wrapper, so it works on every component
    with a component prop. SkeletonLoader is the exception because it renders no element of its
    own. component itself is unchanged.

  • 3e3f7c1: Icons are now configurable, and the components that draw icons of their own no longer depend on
    Icon.

    Breaking: Icon no longer references /static/icons/chassis-icons.svg by default. With no
    sprite set, 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 every Icon below it (sprite, className, font,
    fontPrefix for icon fonts generated with a prefix other than cx-) 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
    icon on Toast/Notification now ask for their icon by purpose: check, previous, next,
    menu, play or pause. 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 new previousIcon/
    nextIcon (Pagination), icon (carousel prev/next controls, NavbarToggler), and
    playIcon/pauseIcon (CarouselPlayPause) props. A custom icon gets the class the component
    positions it by, including directional-icon, so arrows still flip in right-to-left layouts. It
    doesn't get .icon, whose fill would paint over outline icon sets.

    Pagination's previous/next chevrons are now the outline style, matching the carousel's.

    Fix: Icon's size had no visible effect when chassis-css was loaded. It set only the SVG's
    width/height attributes, which chassis-css's .icon sizing overrides, so size={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 as Table (bordered, borderless, hover, striped, sm, color,
    align, responsive, stacked, plus caption) and renders your own
    <thead>/<tbody>/<tfoot>/<tr>/<th>/<td> markup as written. It has no sorting, selection
    or keyboard grid navigation; those remain Table'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 a StaticTable ships no client JS beyond Next's own
    baseline. Links, next/link and 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 stacked tables, give each <td> a data-cell attribute with its label. Table reads that
    label from its column headers, but a static table has no column data to derive it from.
    StaticTable is 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.js behind 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 one Button shipped 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 same Button from @chassis-ui/react/button ships 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/react entry is unchanged and still exports everything. Both import styles
    resolve to the same shared chunks, so mixing them is safe: Modal from /modal and useModal from
    the root read the same context. Each family's hook is also exported from its subpath (useModal,
    useDrawer, useToast, useNotification, usePagination).

    sideEffects in package.json is now accurate. Only the entry files, the chunk that installs the
    global focus-ring listener, and style.css are 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 because RangeCalendar happened to import the same module.

    The DataGrid*Props types 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

Choose a tag to compare

@github-actions github-actions released this 22 Sep 15:28
737c350

Patch Changes

  • 0b72684: Fix Radio, Checkbox and Switch silently discarding children, which left the control with
    no accessible name. children was 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 undefined label,
    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.

    children is now accepted as an alias for label, 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. label still 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

Choose a tag to compare

@github-actions github-actions released this 19 Sep 23:57
6b29e73

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/table derives that column's key
    from a module-scoped 'row-header-column-' + Math.random() constant computed once per JS
    environment, so the id/data-key attributes react-aria puts on TableSelectAllCell/
    TableSelectionCell never 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 with suppressHydrationWarning — the underlying
    react-stately behavior is unchanged and not something this package can fix.

@chassis-ui/react@0.1.1

Choose a tag to compare

Patch Changes

  • 02594b4: Fix Pagination dropping keyboard focus every time the page changes.

    PaginationItem rendered 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 given href), so the element identity is
    stable across activation. The <span> wasn't inert either: smart mode passes an onClick, so it
    picked up role="button" and tabIndex={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 > & } and active
    still lands on the <li>.

  • 02594b4: Adds DataGrid, a virtualized alternative to Table for 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 as Table (DataGridHeader/DataGridColumn/DataGridBody/DataGridRow/DataGridCell), plus DataGridSelectAllCell/DataGridSelectionCell for selection checkboxes.

    Beyond basic virtualized rows, columns, sorting, and selection, it also supports:

    • Pinned columns — pin="start"/pin="end" on a DataGridColumn keeps 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 with estimatedRowHeight) measures each row from its own rendered content instead of a single fixed height shared by every row.
    • Async/infinite loading — onLoadMore/isLoading/loadingContent on DataGridBody fetch 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 footer prop on DataGrid renders content (e.g. a totals row) pinned below the body, outside the grid's own keyboard-navigable structure.

    This adds react-aria-components as a runtime dependency — DataGrid is built on its Virtualizer, and it's the only component in the package that uses it. It's externalized from the bundle, so it resolves from your own node_modules like react-aria already does. Note that react-aria-components pins react-aria to an exact version, so an install may end up with a second copy of react-aria alongside the one this package depends on; nothing here relies on the two being the same instance.

    See the datagrid.mdx docs 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).

  • 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,
    so import type { ButtonProps } from '@chassis-ui/react' failed and consumers had to re-derive
    them with ComponentProps<typeof Button>. All 164 type names the component barrels already
    export are now reachable from the package root.

    As a side effect, TableBodyProps/TableHeaderProps are no longer emitted under $1-suffixed
    names in dist/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 Popover and Tooltip dropping the trigger child's ref.

    Both re-render their child with cloneElement, whose config replaces the child's ref outright
    rather than merging with it, so <Tooltip><Button ref={mine} /></Tooltip> never populated mine.
    Both now fork the caller's ref with their own. Tooltip additionally spread its react-aria
    triggerProps over the child's props, silently dropping a handler the caller had put on their own
    trigger (e.g. onFocus); both now merge them with mergeProps instead.

  • 02594b4: Make every user-facing string the components render themselves translatable.

    I18nProvider drives 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, DatePicker and DateRangePicker take a new labels prop
      (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. A DatePicker's labels also reaches the Calendar it renders internally,
      so it's set once. The CalendarLabels type is exported.
    • Breadcrumb accepts aria-label for 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.

    • focusRedirect could 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-suppressed none as the value to restore. Reachable by clicking twice at the same
      disabled end of an ends="stop" Carousel. It now delegates the whole suppress/restore cycle
      to suppressFocusRing, which owns the re-entrancy guard, rather than reimplementing it without
      one.
    • Popover stole 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, Tooltip and Menu reported a hide on mount. The visibility effect ran its
      "hidden" branch on the initial commit, so onHide (and Menu's onHidden) fired for an overlay
      that had never been shown, before onShow had fired once. All of these report transitions now,
      so none fires on mount — including onShow/onShown for an overlay mounted already-open.
    • Carousel let a caller's handler replace its own. onKeyDown, onMouseEnter and
      onMouseLeave were 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/CarouselPlayPause already follow for onClick.
    • Modal and Drawer dropped a caller's onClick entirely. A <dialog> needs onClick for
      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 invalid and valid rendered a duplicate DOM id. Both feedback nodes carry
      the same id, leaving every control's aria-describedby pointing 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 fires onShow/onShown on
      mount. Wire up to the transition instead, or read the prop you already control.
    • FormField given a className but no label/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 no className still render bare, unchanged.
  • 02594b4: Fix published types and package metadata that were wrong for consumers.

    • usePagination's prevRef/nextRef are typed RefObject<T | null>, matching the
      useRef<T>(null) they actually come from. They were declared non-null, so prevRef.current.focus()
      type-checked and then threw before mount.
    • The polymorphic components that pick their element from href rather than component —
      Button, Chip, Avatar, NavbarBrand, PaginationItem — accept a ref covering 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 engines field. 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 pins es2022 explicitly,
      alongside a new browserslist field documenting the supported floor (Chrome 107+, Edge 107+,
      Firefox 104+, Safari 16+).
    • sideEffects now lists dist/ paths. It named a src/ 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.error in the package now goes throug...
Read more