Skip to content
Lenso UI

v3.0.3

RAC 1.17 with 90% fewer deps, expandable Table rows, Apache 2.0 license, useTheme hook, DOM polymorphic render-prop API, and bug fixes.

April 17, 2026

Patch release: React Aria Components 1.17 with 90% fewer dependencies, expandable Table rows, Apache 2.0 license, the useTheme hook for Vite and CRA apps, a DOM polymorphic utility for render-prop element swaps, and bug fixes.

Installation

Update to the latest version:

npm i @lenso/tokens@latest @lenso/ui@latest

Using AI assistants? Simply prompt "Hey Cursor, update HeroUI to the latest version" and your AI assistant will automatically compare versions and apply the necessary changes. Learn more about the HeroUI MCP Server.

What's New

React Aria Components 1.17

This release upgrades React Aria Components to v1.17.0. The highlight: dependency consolidation drops 90% of RAC's transitive dependencies, resulting in faster installs and builds. See the full RAC 1.17 release notes for details.

Expandable Table rows

Table now supports expandable rows for tree-style data. Set a treeColumn and render a chevron in those cells to expand and collapse child rows — perfect for file browsers, nested categories, and hierarchical data.

"use client";// HeroUI v3.2.6 expandable-rows adaptation (Apache-2.0).import { useState, type Key } from "react";import * as stylex from "@stylexjs/stylex";import { Icon } from "@iconify/react";import { Table } from "@lenso/ui";type Row = { key: string; title: string; type: string; date: string; children: Row[] };const file = (key: string, title: string, date: string, children: Row[] = []): Row => ({  key,  title,  date,  children,  type: children.length ? "Directory" : "File",});const rows = [  file("1", "Documents", "10/20/2025", [    file("2", "Project", "8/2/2025", [      file("3", "Weekly Report", "7/10/2025"),      file("4", "Budget", "8/20/2025"),    ]),  ]),  file("5", "Photos", "2/3/2026", [    file("6", "Image 1", "1/23/2026"),    file("7", "Image 2", "2/3/2026"),  ]),];const styles = stylex.create({  content: { minWidth: 520 },  title: { display: "flex", alignItems: "center", gap: 4 },  chevron: { transform: "rotate(90deg)" },  collapsed: { transform: { default: "none", ":dir(rtl)": "rotate(180deg)" } },});export function ExpandableRows() {  const [expanded, setExpanded] = useState<Set<Key>>(new Set(["1"]));  const renderRow = (item: Row): React.ReactNode => (    <Table.Row itemKey={item.key} textValue={item.title}>      <Table.Cell columnKey="name">        {({ hasChildItems, isExpanded, isTreeColumn }) => (          <span {...stylex.props(styles.title)}>            {hasChildItems && isTreeColumn && (              <Table.ExpandButton aria-label={`Toggle ${item.title}`}>                <Icon                  icon="gravity-ui:chevron-right"                  width={16}                  aria-hidden="true"                  {...stylex.props(isExpanded ? styles.chevron : styles.collapsed)}                />              </Table.ExpandButton>            )}            <span>{item.title}</span>          </span>        )}      </Table.Cell>      <Table.Cell>{item.type}</Table.Cell>      <Table.Cell>{item.date}</Table.Cell>      <Table.Collection items={item.children}>{renderRow}</Table.Collection>    </Table.Row>  );  return (    <Table>      <Table.ScrollContainer>        <Table.Content          aria-label="Files"          xstyle={styles.content}          treeColumn="name"          expandedKeys={expanded}          onExpandedChange={setExpanded}        >          <Table.Header>            <Table.Column columnKey="name">Name</Table.Column>            <Table.Column columnKey="type">Type</Table.Column>            <Table.Column columnKey="date">Date Modified</Table.Column>          </Table.Header>          <Table.Body items={rows}>{renderRow}</Table.Body>        </Table.Content>      </Table.ScrollContainer>    </Table>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

useTheme hook

For plain React with Vite or Create React App (no Next.js theme provider), import useTheme from @lenso/ui. It accepts any theme name ("light", "dark", "brutalism-light", etc.). Pass "system" to follow the OS preference. It persists the value in localStorage, and sets both data-theme and class on <html> to the resolved theme name.

"use client";
import { Button, useTheme } from "@lenso/ui";
export function ThemeSwitch() {  const { theme, setTheme } = useTheme("light");
  return (    <div className="flex gap-2">      <Button onPress={() => setTheme("light")}>        Light      </Button>      <Button onPress={() => setTheme("dark")}>        Dark      </Button>      <Button onPress={() => setTheme("system")}>        System      </Button>      <Button onPress={() => setTheme("brutalism-light")}>        Brutalism Light      </Button>      <span className="text-sm opacity-70">Current: {theme}</span>    </div>  );}

DOM polymorphic utility

The DOM polymorphic helper lets lightweight components that are not built on React Aria primitives change their host element through a render prop.

Example: rendering a Card component as <button />

<Card<"button"> render={(props) => <button {...props} />} />

License change

HeroUI is now licensed under the Apache License 2.0, replacing the previous MIT license. Apache 2.0 provides the same permissive freedoms while adding an explicit patent grant for additional legal protection. No action is required from existing users.

Bug Fixes

  • Tabs: scope secondary-variant styles so nested tab groups no longer inherit the parent variant (#6384)

Dependencies

  • React Aria Components: Updated from 1.16.0 to 1.17.0
  • @react-aria/utils: Updated from 3.33.1 to 3.34.0
  • @react-types/shared: Updated from 3.33.1 to 3.34.0
  • @internationalized/date: Updated from 3.12.0 to 3.12.1

Contributors

Thanks to everyone who contributed to this release!

HeroUI contributors