v3.0.5
Text renamed to Typography (breaking), color tokens refactored to unprefixed source variables, Checkbox + Radio border fixes, and a new CLI docs page.
Patch release: Text is renamed to Typography to resolve a tailwind-merge collision that silently dropped variant classes. Derived color tokens (hover, soft, border-secondary, …) moved from @theme inline aliases into variables.css as unprefixed source tokens, so component CSS can reference them directly. Plus Checkbox and Radio border alignment fixes, an accent-soft-foreground hover refinement for Calendar, and a new CLI docs page.
⚠️ Breaking change: Text → Typography. The BEM block changes from text to typography (e.g. text--body-sm → typography--body-sm).
Installation
Update to the latest version:
npm i @lenso/tokens@latest @lenso/ui@latestUsing 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.
⚠️ Breaking Changes
Text → Typography
The component shipped in v3.0.4 used text-* BEM modifiers, which collide with Tailwind's text-* utility family. tailwind-variants runs tailwind-merge on every variant composition and dedupes text--body-sm, text--color-muted, text--weight-normal to a single class — silently dropping the others.
Renaming the block to typography permanently fixes the collision (#6505, closes #6497).
Before:
import {Text} from "@lenso/ui";
<Text type="body-sm" color="muted" weight="medium"> Hello world</Text>;After:
import {Typography} from "@lenso/ui";
<Typography type="body-sm" color="muted" weight="medium"> Hello world</Typography>;Sub-components rename the same way:
| Before | After |
|---|---|
Text | Typography |
Text.Heading | Typography.Heading |
Text.Paragraph | Typography.Paragraph |
Text.Code | Typography.Code |
Text.Prose | Typography.Prose |
CSS BEM classes change too: .text → .typography, .text--body-sm → .typography--body-sm, etc. See the Typography docs for the full class reference.
"use client";// Adapted from HeroUI v3.2.6 (Apache-2.0).import { Typography } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const scale = [ { label: "h1", meta: "36px / 600 / 1.11 / tight", sample: "Build better interfaces", type: "h1" }, { label: "h2", meta: "30px / 600 / 1.17 / tight", sample: "Built for the intelligence age", type: "h2", }, { label: "h3", meta: "24px / 600 / 1.25 / tight", sample: "Pricing on your terms", type: "h3" }, { label: "h4", meta: "20px / 600 / 1.33 / tight", sample: "Apply to the startup program", type: "h4", }, { label: "h5", meta: "18px / 600 / 1.39 / tight", sample: "Card titles", type: "h5" }, { label: "h6", meta: "16px / 600 / 1.50 / tight", sample: "Smaller feature headers", type: "h6" }, { label: "body", meta: "16px / 400 / 1.75", sample: "Primary body text used across documentation, marketing copy, and descriptions.", type: "body", }, { label: "body-sm", meta: "14px / 400 / 1.50", sample: "Secondary body, table cells, navigation, and sidebar items.", type: "body-sm", }, { label: "body-xs", meta: "12px / 400 / 1.25", sample: "Captions, badges, helper text, and fine print.", type: "body-xs", }, { label: "code", meta: "14px / mono", sample: "pnpm add @lenso/ui", type: "code" },] as const;const styles = stylex.create({ column: { display: "flex", width: "100%", flexDirection: "column" }, row: { display: "grid", gridTemplateColumns: "160px 1fr", alignItems: "center", gap: 32, paddingBlock: 20, borderBlockEndWidth: { default: 1, ":last-child": 0 }, borderBlockEndStyle: "solid", borderBlockEndColor: "var(--border)", }, caption: { display: "flex", flexShrink: 0, flexDirection: "column", gap: 2 }, label: { fontSize: 14, lineHeight: "20px", fontWeight: 600, color: "var(--foreground)" }, meta: { fontSize: 12, lineHeight: "16px", whiteSpace: "nowrap", color: "var(--muted)" },});export function TypographyScale() { return ( <div {...stylex.props(styles.column)}> {scale.map((row) => ( <div key={row.label} {...stylex.props(styles.row)}> <div {...stylex.props(styles.caption)}> <span {...stylex.props(styles.label)}>{row.label}</span> <span {...stylex.props(styles.meta)}>{row.meta}</span> </div> <Typography type={row.type}>{row.sample}</Typography> </div> ))} </div> );}export default TypographyScale;Local adaptation source above. Derived from HeroUI v3.2.6 source.
Style Refactor
Unprefixed source color tokens
All derived color tokens — *-hover, *-soft, *-soft-foreground, border-secondary, etc. — moved from @theme inline aliases in theme.css into variables.css as unprefixed source variables, and 24 component CSS files now reference source tokens directly (#6499).
1. color-mix formulas moved from theme.css to variables.css
Before — the calculation lived inside the @theme inline block:
/* packages/styles/themes/shared/theme.css */--color-accent-hover: color-mix(in oklab, var(--accent) 90%, var(--accent-foreground) 10%);--color-accent-soft: var(--accent-soft, color-mix(in oklab, var(--accent) 15%, transparent));--color-accent-soft-foreground: var(--accent-soft-foreground, var(--accent));--color-border-secondary: color-mix(in oklab, var(--surface) 78%, var(--surface-foreground) 22%);After — theme.css is pure aliases; the formulas live in variables.css:
/* packages/styles/themes/default/variables.css */--accent-hover: color-mix(in oklab, var(--accent) 90%, var(--accent-foreground) 10%);--accent-soft: color-mix(in oklab, var(--accent) 15%, transparent);--accent-soft-foreground: var(--accent);--border-secondary: color-mix(in oklab, var(--surface) 78%, var(--surface-foreground) 22%);
/* packages/styles/themes/shared/theme.css */--color-accent-hover: var(--accent-hover);--color-accent-soft: var(--accent-soft);--color-accent-soft-foreground: var(--accent-soft-foreground);--color-border-secondary: var(--border-secondary);2. Component CSS uses source tokens directly
Before — var(--color-*) references everywhere:
/* packages/styles/components/button.css */.button--primary { --button-bg: var(--color-accent); --button-bg-hover: var(--color-accent-hover); --button-fg: var(--color-accent-foreground);}
.button--secondary { --button-bg: var(--color-default); --button-bg-hover: var(--color-default-hover); --button-fg: var(--color-accent-soft-foreground);}After — unprefixed source tokens:
/* packages/styles/components/button.css */.button--primary { --button-bg: var(--accent); --button-bg-hover: var(--accent-hover); --button-fg: var(--accent-foreground);}
.button--secondary { --button-bg: var(--default); --button-bg-hover: var(--default-hover); --button-fg: var(--accent-soft-foreground);}The @theme inline block still exposes every --color-* Tailwind utility as an alias, so user-land class usage (bg-accent, text-accent, …) is unaffected.
3. Theme builder consolidated
Three derived-token helpers (getAccentDerivedVariables, getSemanticDerivedVariables, getFieldDerivedVariables) collapsed into a single getDerivedColorVariables() that emits the full unprefixed set, including the darkenForSoftForeground logic that keeps soft-foreground text readable on light accent themes.
Style Fixes
- Calendar / Range Calendar: default day-cell hover now uses
accent-soft-foregroundinstead ofaccent, so hovered cells stay legible on light accent themes. The docs search tag selected state was aligned the same way (#6500). - Checkbox:
.checkbox__controlnow carries the sameborder,border-field-border, and[border-width:var(--border-width-field)]base styling that.radio__controlalready used, withborder-coloradded to the transition list (#6521). - Radio:
.radio__controldefault border now usesborder-field-borderinstead of the generic Tailwindbordercolor, matching Input, Select, TextArea, and NumberField (#6522).
Docs
CLI page
New CLI docs page covers installation, init, install, upgrade, uninstall, list, doctor, and env with example outputs (#6498).
Dependencies
Bumped across packages (#6529):
react/react-dom:19.2.3→19.2.6@types/react:19.2.7→19.2.14next(docs):16.1.1→16.2.6- CI actions:
actions/checkout@v6,actions/setup-node@v6,actions/cache@v5,pnpm/action-setup@v6
Links
Contributors
Thanks to everyone who contributed to this release!
HeroUI contributors