Skip to content
Lenso UI

v3.0.0-beta.6

6 new color components (ColorPicker, ColorArea, ColorSlider, ColorField, ColorSwatch, ColorSwatchPicker), toast improvements, and various style fixes.

February 6, 2026

This release introduces a comprehensive Color System with six new components for color selection and manipulation: ColorPicker, ColorArea, ColorSlider, ColorField, ColorSwatch, and ColorSwatchPicker. Also includes Separator variants and various style improvements.

HeroUI v3 Beta 6

Installation

Update to the latest version:

npm i @lenso/tokens@beta @lenso/ui@beta

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

Color System

We're excited to introduce a comprehensive Color System - a complete suite of components for color selection, manipulation, and display. These components are built on React Aria's color primitives and work together seamlessly.

Key features:

  • Full Color Space Support: Work with HSL, HSB, and RGB color spaces
  • Channel-based Editing: Manipulate individual color channels (hue, saturation, lightness, brightness, red, green, blue, alpha)
  • Accessible by Default: Full keyboard navigation and screen reader support
  • Composable Design: Mix and match components to build custom color pickers

New Components

This release introduces 6 new color components:

ColorPicker

The ColorPicker is a compound component that combines all color components into a complete color selection experience.

"use client";
import { ColorArea, ColorPicker, ColorSlider, ColorSwatch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({  area: { maxWidth: "100%" },  slider: { gap: 4, paddingInline: 4 },  output: { color: "var(--muted)" },});export function Basic() {  return (    <ColorPicker defaultValue="#0485F7">      <ColorPicker.Trigger>        <ColorSwatch size="lg" />        <span>Pick a color</span>      </ColorPicker.Trigger>      <ColorPicker.Popover>        <ColorArea          aria-label="Color area"          xstyle={styles.area}          colorSpace="hsb"          xChannel="saturation"          yChannel="brightness"        >          <ColorArea.Thumb />        </ColorArea>        <ColorSlider aria-label="Hue" channel="hue" xstyle={styles.slider} colorSpace="hsb">          <span>Hue</span>          <ColorSlider.Output xstyle={styles.output} />          <ColorSlider.Track>            <ColorSlider.Thumb />          </ColorSlider.Track>        </ColorSlider>      </ColorPicker.Popover>    </ColorPicker>  );}

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

ColorArea

A 2D gradient area for selecting two color channels at once, typically saturation and brightness.

"use client";
import { ColorArea } from "@lenso/ui";
export function ColorAreaBasic() {  return (    <ColorArea aria-label="Color area" defaultValue="rgb(116, 52, 255)">      <ColorArea.Thumb />    </ColorArea>  );}

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

ColorSlider

A slider for adjusting individual color channels like hue, saturation, lightness, or alpha.

"use client";
import { ColorSlider } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ slider: { width: "100%", maxWidth: 320 } });export function Basic() {  return (    <ColorSlider      aria-label="Hue"      channel="hue"      xstyle={styles.slider}      defaultValue="hsl(0, 100%, 50%)"    >      <span>Hue</span>      <ColorSlider.Output />      <ColorSlider.Track>        <ColorSlider.Thumb />      </ColorSlider.Track>    </ColorSlider>  );}

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

With different channels:

"use client";/** Adapted from HeroUI v3.2.6. Apache-2.0. */import { ColorSlider, ColorSwatch, parseColor } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { styles } from "../color-picker/source.stylex";export function Channels() {  const [color, setColor] = useState(parseColor("hsl(0, 100%, 50%)"));  return (    <div {...stylex.props(styles.column, styles.xs)}>      {(["hue", "saturation", "lightness"] as const).map((channel) => (        <ColorSlider key={channel} channel={channel} value={color} onChange={setColor}>          <ColorSlider.Label xstyle={styles.capitalize}>{channel}</ColorSlider.Label>          <ColorSlider.Output />          <ColorSlider.Track>            <ColorSlider.Thumb />          </ColorSlider.Track>        </ColorSlider>      ))}      <div {...stylex.props(styles.row2)}>        <ColorSwatch color={color} size="sm" />        <p {...stylex.props(styles.muted)}>          Current color: <code {...stylex.props(styles.mono)}>{color.toString("hsl")}</code>        </p>      </div>    </div>  );}

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

ColorField

A text input field for entering color values directly. Supports various color formats.

"use client";
import { ColorField, ColorSwatch, parseColor } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ field: { width: 280, maxWidth: "100%" } });export function Basic() {  const [color, setColor] = useState<ReturnType<typeof parseColor> | null>(() =>    parseColor("#0485F7"),  );  return (    <ColorField xstyle={styles.field} name="color" value={color} onChange={setColor}>      <ColorField.Label>Color</ColorField.Label>      <ColorField.Group>        <ColorField.Prefix>          <ColorSwatch {...(color ? { color } : {})} size="xs" />        </ColorField.Prefix>        <ColorField.Input />      </ColorField.Group>    </ColorField>  );}

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

ColorSwatch

A visual display of a color value with support for transparency patterns.

"use client";
import { ColorSwatch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ row: { display: "flex", alignItems: "center", gap: 12 } });export function ColorSwatchBasic() {  return (    <div {...stylex.props(styles.row)}>      <ColorSwatch aria-label="Blue" color="#0485F7" />      <ColorSwatch aria-label="Red" color="#EF4444" />      <ColorSwatch aria-label="Amber" color="#F59E0B" />      <ColorSwatch aria-label="Green" color="#10B981" />      <ColorSwatch aria-label="Fuchsia" color="#D946EF" />    </div>  );}

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

ColorSwatchPicker

A grid of color swatches for quick color selection from a predefined palette.

"use client";
import { ColorSwatchPicker } from "@lenso/ui";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];export function Basic() {  return (    <ColorSwatchPicker aria-label="Choose a color">      {colors.map((color) => (        <ColorSwatchPicker.Item key={color} color={color}>          <ColorSwatchPicker.Swatch />          <ColorSwatchPicker.Indicator />        </ColorSwatchPicker.Item>      ))}    </ColorSwatchPicker>  );}

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

Component Improvements

Toast Enhancements

The Toast component has been significantly improved with new features and better stability (#6151):

New Features:

  • Loading State: Added isLoading prop to show a spinner instead of the default indicator
  • Default Timeout: Toasts now auto-dismiss after 4 seconds by default (configurable via timeout prop)
  • Width Control: Added width prop to Toast.Provider for customizable toast width
  • Adaptive Height: Toasts now adapt their height based on content
  • Better Stacking: Fixed layout shifts when toasts stack using absolute positioning and height synchronization
  • Improved Close Handling: Deferred onClose callback to prevent toast transition deadlock
  • Front-most Close Button: Close button only appears on the front-most toast for cleaner UI
  • Enhanced Promise Support: Improved toast.promise() with better loading states and error handling

New Demos:

  • Promise & Loading states
  • Callbacks and timeout handling
"use client";
// Adapted from HeroUI v3.2.6 variants, Apache-2.0.import { HardDrive, Persons } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Toast } from "@lenso/ui";import { Notifications, styles, type ToastData } from "./_shared";
function Messages() {  const manager = Toast.useToastManager<ToastData>();  return (    <>      <div {...stylex.props(styles.frame)}>        <div {...stylex.props(styles.buttons)}>          <Button            size="sm"            variant="tertiary"            onClick={() => {              const id = manager.add({                title: "You have been invited to join a team",                description: "Bob sent you an invitation to join HeroUI team",                type: "default",                data: { indicator: <Persons />, actionStyle: "tertiary" },                actionProps: { children: "Dismiss", onClick: () => manager.close(id) },              });            }}          >            Default toast          </Button>          <Button            size="sm"            variant="secondary"            onClick={() => {              const id = manager.add({                title: "You have 2 credits left",                description: "Get a paid plan for more credits",                type: "accent",                actionProps: { children: "Upgrade", onClick: () => manager.close(id) },              });            }}          >            Accent toast          </Button>          <Button            xstyle={styles.successText}            size="sm"            variant="tertiary"            onClick={() => {              const id = manager.add({                title: "You have upgraded your plan",                description: "You can continue using HeroUI Chat",                type: "success",                data: { actionStyle: "success" },                actionProps: { children: "Billing", onClick: () => manager.close(id) },              });            }}          >            Success toast          </Button>          <Button            xstyle={styles.warningText}            size="sm"            variant="tertiary"            onClick={() => {              const id = manager.add({                title: "You have no credits left",                description: "Upgrade to a paid plan to continue",                type: "warning",                data: { actionStyle: "warning" },                actionProps: { children: "Upgrade", onClick: () => manager.close(id) },              });            }}          >            Warning toast          </Button>          <Button            size="sm"            variant="danger-soft"            onClick={() => {              const id = manager.add({                title: "Storage is full",                description:                  "Remove files to release space. Adding more text to demonstrate longer content display",                type: "danger",                data: { indicator: <HardDrive />, actionStyle: "danger" },                actionProps: { children: "Remove", onClick: () => manager.close(id) },              });            }}          >            Danger toast          </Button>        </div>      </div>      <Notifications />    </>  );}
export function Variants() {  return (    <Toast.Provider>      <Messages />    </Toast.Provider>  );}

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

Separator Variants

Added variants to the Separator component for different visual styles.

Chip Component - Label Slot

The Chip component now supports a Chip.Label subcomponent for better visual alignment. When removing start or end content (like icons), the label text was too close to the chip edges. Plain text children are automatically wrapped in <Chip.Label> for backward compatibility.

Usage:

import { Chip } from '@lenso/ui';
// Automatic wrapping (backward compatible)<Chip>Label text</Chip>
// Explicit label with custom styling<Chip>  <Chip.Label className="font-bold">Custom Label</Chip.Label></Chip>
// Mixing icons and labels<Chip>  <Icon icon="gravity-ui:check" />  <Chip.Label>With Icon</Chip.Label></Chip>

Style Fixes

  • Overlay Content: Fixed blur effect on overlay content (#6136)
  • Invalid Field: Converted ring to outline for invalid field states (#6184)
  • Link with Button: Fixed styling for Link components using button variants (#6138)
  • Toast Content: Fixed vertical alignment of toast content (#6147)
  • Safari SVG: Fixed SVG shifting issue in Safari (#6149)
  • Placeholder Color: Aligned placeholder color with input text (#6139)
  • Tooltip: Removed cursor style from tooltip trigger component
  • CSS Variables: Made calculated variables depend only on root variables (#6154)

Bug Fixes

  • Fixed page interactivity during view transitions (#6128)
  • Fixed markdown URL formatting (#6162)
  • Fixed incorrect link to combo box page (#6164)
  • Fixed autocomplete styles import order in index.css
  • Fixed hyphenated format for CSS classes (#6191)

⚠️ Breaking Changes

Toast Component - Container Renamed to Provider

The Toast.Container component has been renamed to Toast.Provider for better semantic clarity (#6151).

Before:

<Toast.Container placement="bottom" />

After:

<Toast.Provider placement="bottom" />

Additional Changes:

  • Default gap prop changed from 14 to 12 pixels
  • Default timeout is now 4000 (4 seconds) instead of requiring explicit timeout
  • Toast.Action has been renamed to Toast.ActionButton for consistency

CSS Class Naming Convention

CSS classes have been renamed to use hyphenated format for consistency (#6141). This follows BEM conventions more closely and improves compatibility with Tailwind CSS.

Important Note: The textarea class was initially renamed to text-area but was rolled back to textarea in PR #6191 due to conflicts with Tailwind's native textarea class. No changes are needed for TextArea component classes.

Component Class Name Changes

The following CSS class names have been updated. If you have custom CSS targeting these classes directly, update your selectors:

ComponentOld Class NameNew Class NameNotes
ComboBox.combobox.combo-boxAll related classes updated
.combobox__input-group.combo-box__input-group
.combobox__trigger.combo-box__trigger
.combobox__popover.combo-box__popover
.combobox--full-width.combo-box--full-width
ListBox.listbox.list-boxAll related classes updated
ListBoxItem.listbox-item.list-box-itemAll related classes updated
.listbox-item__indicator.list-box-item__indicator
.listbox-item--default.list-box-item--default
.listbox-item--danger.list-box-item--danger
ListBoxSection.listbox-section.list-box-sectionAll related classes updated
TextArea.textarea.textareaNo change - Rolled back to avoid Tailwind conflict

Migration Guide

Before:

/* Custom styles targeting old class names */.combobox {  /* styles */}
.listbox-item {  /* styles */}

After:

/* Update to new hyphenated class names */.combo-box {  /* styles */}
.list-box-item {  /* styles */}

JavaScript/TypeScript Updates:

If you're using these class names in JavaScript or TypeScript code:

// Before<div className="combobox" /><ListBoxItem className="listbox-item" />
// After<div className="combo-box" /><ListBoxItem className="list-box-item" />

Note: Component props and TypeScript types remain unchanged. Only CSS class names have been updated.

Removed CSS Variables

Several CSS variables have been removed as part of the surface color refactoring (#6204). These variables were either replaced with direct variable references or removed entirely.

Surface Color Variables

The following calculated surface color variables have been removed and replaced with direct variable references:

Removed:

  • --color-surface-secondary (was calculated via color-mix)
  • --color-surface-tertiary (was calculated via color-mix)

Replacement: These variables now directly reference the base variables defined in variables.css:

  • --color-surface-secondary → Uses var(--surface-secondary) directly
  • --color-surface-tertiary → Uses var(--surface-tertiary) directly

The base variables --surface-secondary and --surface-tertiary are now defined directly in variables.css instead of being calculated in theme.css.

On Surface Color Variables

All --color-on-surface-* variables have been removed entirely:

Removed:

  • --color-on-surface
  • --color-on-surface-foreground
  • --color-on-surface-hover
  • --color-on-surface-focus
  • --color-on-surface-secondary
  • --color-on-surface-secondary-foreground
  • --color-on-surface-secondary-hover
  • --color-on-surface-secondary-focus
  • --color-on-surface-tertiary
  • --color-on-surface-tertiary-foreground
  • --color-on-surface-tertiary-hover
  • --color-on-surface-tertiary-focus

Migration:

If you were using these variables, update your code to use the appropriate surface variables directly:

/* Before */.element {  background: var(--color-on-surface);  color: var(--color-on-surface-foreground);}
.element:hover {  background: var(--color-on-surface-hover);}
/* After */.element {  background: var(--surface-secondary);  color: var(--surface-secondary-foreground);}
.element:hover {  background: color-mix(in oklab, var(--surface-secondary) 92%, var(--surface-secondary-foreground) 8%);}

Or use the Tailwind utilities:

// Before<div className="bg-on-surface text-on-surface-foreground" />
// After<div className="bg-surface-secondary text-surface-secondary-foreground" />

Related PR: #6204

Contributors

Thanks to everyone who contributed to this release!

HeroUI contributors