Beta 1 [v3] (#5872)

* refactor: compound and named pattern (#5850)

* refactor(component): compound and named pattern

* refactor(component): tabs component naming consistency

* docs(changelog): v3.0.0-alpha-36

* fix(docs): correct broken storybook links (#5854)

* feat(components): checkbox (#5829)

* fix(component): support compound pattern in server component

* fix(component): keep accordion and chip in sync with compound pattern

* fix(component): type of forward refs

* fix(calendar): temporary workaround for ref error

* fix(storybook): imports

* feat(checkbox): initial draft

* refactor(styles): remove orientation

* refactor: migration from dot notation to separted components to support RSC

* refactor: migrate to React 19 ref pattern - remove forwardRef wrappers and explicit ref declarations from all components

* chore(checkbox): revise checkbox story

* feat(checkbox): checkmark & indeterminate

* chore(styles): checkmark & indeterminate styles

* refactor: provider context removed as it is not longer needed on react 19

* feat: made the migration smoother by still supporting the "dot" exports but adjusting the main compound component

* fix: compound patter, radio group api, ref on react 19

* refactor(switch): split switch and switch-group into separate components following radio/radio-group pattern

* chore(changelog): update v3.0.0-alpha.35 release notes and date

* fix(page): update version label to reflect RSC support

* chore(changelog): update examples to use new component names CardRoot and TabsRoot

* refactor(checkbox): adopt latest component structure

* chore: remove radio stories

* refactor(checkbox): adopt new api and revised stories

* refactor(checkbox-group): adopt new api and add stories

* chore(checkbox-group): revise examples

* chore(checkbox): separate checkbox group css

* chore(styles): revise checkbox styels

* fix(styles): add border for invalid

* feat(checkbox): add invalid story

* chore: sync changes from v3 redesign

* refactor(checkbox): remove slots

* refactor(checkbox): apply compound and named pattern

* feat(docs): checkbox page

* refactor(checkbox): remove use client

* chore(docs): revise based on the latest revamp

---------

Co-authored-by: Tianen Pang <32772271+tianenpang@users.noreply.github.com>
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>

* V3 Redesign (#5851)

* fix(docs): update Link component to use Link.Root for consistency with new component structure

* feat: light theme in progress, button, spearator & accordion adjusted to new default theme

* feat: button and chip done

* feat: enhance Skeleton component with new Grid and Single Shimmer previews; update card and disclosure styles for consistency

* feat: update Kbd component styles and add Abbr support in stories

* feat: avatar and chip updated

* fix(accordion): adjust separator styling for consistency in accordion items

* refactor: close button adjsuted

* feat(link): add underline variants and offset options to Link component documentation and implementation

* feat(link): enhance link component with hover state and transition effects for icon opacity

* refactor: surfaces variables are now calculated by default, card, button, disclosures updated

* refactor: update styles for various components to improve consistency and reduce motion effects

* feat(storybook): add welcome story and update story order; feat(styles): include variants in package config and copy script

* refactor: update accordion, card, and kbd components to enhance styling and introduce new variants

* refactor(link): update hover underline effect to use opacity transition instead of scale transformation

* refactor(styles): update background color variables and remove gradient backgrounds from layout components

* refactor(storybook): enhance color stories by adding calculated color variants and removing deprecated soft colors

* refactor(styles): introduce background quaternary color and update shadow variables for popover and tooltip components

* feat: alert component created

* refactor(alert-demo, x-profile-demo): simplify button usage and enhance layout alignment

* feat: input otp

* feat: inpuit otp docs

* refactor(switch): enhance styling with improved transition timings and add box-shadow for active states

* feat: home page design in progress

* feat: add Surface component with context-based on-surface styling

- Add Surface component with four variants (default, secondary, tertiary, quaternary)
- Implement SurfaceContext to provide variant to child components
- Update Input, TextArea, InputOTP, and RadioGroup to automatically detect Surface context
- Components now apply on-surface styling when wrapped in Surface component
- Add OnSurface stories for all affected components
- Create comprehensive Surface documentation with examples
- Add OnSurface demo examples to all component documentation pages
- Update CSS classes to support on-surface modifier variants

* feat: add Surface component documentation and update meta.json

* feat(card): implement form handling in WithForm story using Form, Input, and Label components

* feat(card): refactor WithForm to use Form component for improved form handling and submission

* refactor(accordion): remove background class from stories and enhance AccordionRoot with SurfaceContext for elevated variant

* refactor(accordion): update variant from elevated to surface across components and documentation

* feat: add checkbox-group component and checkbox enhancements

* feat(demo): add UIComponentsDemo to the DemoComponents

* refactor: migrate Component.Root to Component in docs and demos

- Update all component documentation files to use Component instead of Component.Root
- Update all demo files to use simplified component syntax
- Update API reference headers from Component.Root Props to Component Props
- Exclude changelog files from migration as requested

This change promotes the simpler and more elegant Component syntax while
maintaining backward compatibility (both patterns are still supported).

* style: update global styles and card component

* feat(storybook): enhance color stories with new "On Surface" color variants and remove obsolete field color states

* feat(demos): update chip component props from 'type' to 'color' and add "use client" directive in multiple demo files

* feat(demos): add "use client" directive to basic button demo

* refactor: theme variables update background color in dark mode

* feat(demos): add "use client" directive to multiple demo files

* feat(demos): add FieldError component to TextfieldDemo for improved validation feedback

* fix(demo): update avatar images and names in avatar-group-demo for consistency

* feat: listbox component preview created and documented

* feat: selecte component created

* feat: export React Aria components from rac module

* feat: slider component created, documentation styles and content updated. Home page completed

* fix(demo): correct spelling of "credits" in AlertDemo component

* refactor(docs): simplify component usage by using compound pattern (#5870)

* fix(component): checkbox build errors

* refactor(docs): simplify component usage by using compound pattern

* fix(component): checkbox build errors

* chore(docs): correct pattern description

* feat(docs): enhance component preview with intersection observer for lazy loading

* feat(docs): enhance code block styling and functionality with isolated rendering option

* chore(root): upgrade package manager

* fix(docs): update image sources and enhance styling across various components

---------

Co-authored-by: Tianen Pang <32772271+tianenpang@users.noreply.github.com>

* fix(docs): update Twitter card image and adjust styles in route component for improved readability

* refactor(docs): updated color documentation

* fix(docs): update icon import in home page and remove unused client directive from Iconify component

* chore(docs): update documentation to reflect beta status and adjust storybook links for components

* feat: fix alert responsive

* feat(docs): replace img tags with next/image component for improved performance and consistency in home page and demo components

* fix: improve Lighthouse performance scores

- Replace picture/source elements with Next.js Image component
- Add fetchPriority='high' and loading='eager' to LCP images
- Fix viewport: remove userScalable: false and increase maximumScale to 5
- Add robots metadata with index: true for proper SEO
- Update sitemap URL in robots.txt

* fix: explicitly set X-Robots-Tag header to ensure proper indexing

- Add headers function in next.config.ts to set X-Robots-Tag: index, follow
- This ensures the header is explicitly set and prevents noindex issues

* fix(docs): update Link component styling in home page for improved visibility

* feat: card demos adjusted

* fix(docs): update introduction images for consistency and improved presentation

* fix(docs): update image sources in introduction and README for consistency

* fix(docs): update card component to include icon property for enhanced documentation

* fix(docs): update icon property for ListBox, Select, Slider, and MCP Server components to 'new' for improved documentation clarity

* fix(docs): update card demo layout to use flex-wrap for improved responsiveness

* feat(docs): implement changelog filtering in search API response

* feat: beta-1 changelog (draft)

* fix(docs): update version label in home page to reflect major redesign

* feat(docs): update changelog for v3-0-0-beta-1 with new components and improved API details

* fix(docs): correct formatting in changelog for v3-0-0-beta-1

* chore(docs): minor changes on v3 beta doc (#5879)

* chore(docs): update release date

* chore(docs): point to v3 component doc instead of v2

* chore(docs): add updated icon

* fix(input): typo

* refactor(styles): reduce motion (#5873)

* refactor(styles): reduce motion

* refactor(styles): reduce motion

* docs(handbook): disabling animations in animation section

* style(component): extend motion-reduce and adjust motion priority

* docs(handbook): disabling animations in animation section

* chore(docs): update design principles and changelog for v3-0-0-beta-1

* feat(docs): enhance HomePage and DocsImage components with static rendering and improved image handling

* fix(docs): update background colors for input, textarea, skeleton components and tabs in showcase

---------

Co-authored-by: Tianen Pang <32772271+tianenpang@users.noreply.github.com>
Co-authored-by: WK <wingkwong.code@gmail.com>
This commit is contained in:
Junior Garcia
2025-11-06 11:08:05 -03:00
committed by GitHub
parent 9542aae53c
commit 3c3f82432a
503 changed files with 19547 additions and 5208 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ You communicate technical concepts clearly and can assist both human developers
```css
/* Good */
@apply bg-panel rounded-lg border p-4 shadow-md;
@apply bg-surface rounded-lg border p-4 shadow-md;
```
2. **Keep custom CSS properties**: Don't convert properties without Tailwind utilities
+1 -1
View File
@@ -343,7 +343,7 @@ Tailwind CSS v4 embraces CSS custom properties for theming and dynamic values:
```css
.component {
/* Good: Use @apply for Tailwind utilities */
@apply bg-panel rounded-lg border p-4 shadow-md;
@apply bg-surface rounded-lg border p-4 shadow-md;
/* Keep custom CSS for non-utility properties */
cursor: var(--cursor-interactive);
+1
View File
@@ -1,5 +1,6 @@
{
"npm.packageManager": "pnpm",
"css.lint.emptyRules": "ignore",
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore",
"typescript.tsdk": "node_modules/typescript/lib",
+21 -7
View File
@@ -12,6 +12,24 @@ icon: clock-arrow-rotate-left
## Latest Release
### v3.0.0-beta.1
**November 6, 2025**
<VideoPlayer
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/videos/v3-beta-1.mp4"
poster="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/videos/v3-beta-1-poster.png"
height={430}
/>
This release introduces a comprehensive redesign of HeroUI v3, merging v2's beauty and animations with v3's simplicity. All components have been redesigned, 8 new components added ([Alert](/docs/compoenents/alert), [Checkbox](/docs/components/checkbox), [InputOTP](/docs/components/input-otp), [ListBox](/docs/components/listbox), [Select](/docs/components/select), [Slider](/docs/components/slider), [Surface](/docs/components/surface)), and the design system has been completely overhauled with better color tokens, shadows, and architecture. Includes breaking changes to design system variables, component APIs, and flexible component patterns.
[Read full release notes →](/docs/changelog/v3-0-0-beta-1)
---
## Previous Releases
### v3.0.0-alpha.35
**October 21, 2025**
@@ -37,10 +55,6 @@ All compound components now require `.Root` suffix: `Accordion`, `Avatar`, `Card
[Read full release notes →](/docs/changelog/v3-0-0-alpha-35)
---
## Previous Releases
### v3.0.0-alpha.34
**October 15, 2025**
@@ -94,9 +108,9 @@ Card component redesigned with [new variants](/docs/components/card), added [Clo
HeroUI v3 follows a regular release cycle:
- **Alpha releases**: Weekly to bi-weekly during active development - In progress
- **Beta releases**: Monthly stabilization releases (once first beta release is made)
- **Stable releases**: Quarterly major versions (once first stable release is made)
- **Alpha releases**: Weekly to bi-weekly during active development
- **Beta releases**: Monthly stabilization releases - In progress
- **Stable releases**: Quarterly major versions (Q4 2025 target)
## Contributing
+1 -1
View File
@@ -2,5 +2,5 @@
"title": "Changelog",
"description": "HeroUI v3 Release Notes",
"root": false,
"pages": ["changelog", "v3-0-0-alpha-35"]
"pages": ["changelog", "v3-0-0-beta-1"]
}
@@ -271,15 +271,15 @@ import {
**Tabs Example:**
```tsx
import { TabsRoot, TabListWrapper, TabList, Tab, TabIndicator, TabPanel } from "@heroui/react"
import { TabsRoot, TabListContainer, TabList, Tab, TabIndicator, TabPanel } from "@heroui/react"
<TabsRoot>
<TabListWrapper>
<TabListContainer>
<TabList>
<Tab id="tab1">Tab 1<TabIndicator /></Tab>
<Tab id="tab2">Tab 2<TabIndicator /></Tab>
</TabList>
</TabListWrapper>
</TabListContainer>
<TabPanel id="tab1">Panel 1</TabPanel>
<TabPanel id="tab2">Panel 2</TabPanel>
</TabsRoot>
@@ -0,0 +1,866 @@
---
title: v3.0.0-beta.1
description: Major redesign with new design system, 8 new components, and improved developer experience.
---
<div className="flex items-center gap-3 mb-6">
<span className="text-sm text-muted">November 6, 2025</span>
</div>
This release introduces a comprehensive redesign of HeroUI v3, merging v2's beauty and animations with v3's simplicity. All components redesigned, 8 new components, and improved design system with better color tokens, shadows, and architecture.
## Installation
Update to the latest version:
<Tabs items={["npm", "pnpm", "yarn", "bun"]}>
<Tab value="npm">
```bash
npm i @heroui/styles@beta @heroui/react@beta
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add @heroui/styles@beta @heroui/react@beta
```
</Tab>
<Tab value="yarn">
```bash
yarn add @heroui/styles@beta @heroui/react@beta
```
</Tab>
<Tab value="bun">
```bash
bun add @heroui/styles@beta @heroui/react@beta
```
</Tab>
</Tabs>
<Callout type="info">
**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](/docs/ui-for-agents/mcp-server).
</Callout>
## What's New
### New Design System
We've spent weeks crafting a new design system that merges the soul of HeroUI v2 with the simplicity of v3. Every component has been redesigned with attention to detail, smooth animations, and improved developer experience. The new design system is available in our [Figma Kit V3](https://www.figma.com/community/file/1546526812159103429/heroui-figma-kit-v3).
<VideoPlayer
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/videos/v3-beta-1.mp4"
poster="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/videos/v3-beta-1-poster.png"
height={430}
/>
The redesign brings:
- New color system that brings v3's vision to life and stands out for its uniqueness
- Refined shadow system for better depth perception
- New variables and tokens for better customization
- Automatic `isOnSurface` support for form-based components
- Enhanced border and spacing tokens
- Better contrast and accessibility
- Consistent component patterns across web and native
### New Components
This release introduces **8 new** essential components:
- **[Alert](#alert)**: Display important messages and notifications with status indicators.
- **[Checkbox & CheckboxGroup](#checkbox-checkboxgroup)**: Select multiple items from a list.
- **[InputOTP](#inputotp)**: One-time password input for authentication flows.
- **[Listbox](#listbox)**: Display a list of options and allow single or multiple selection.
- **[Select](#select)**: Dropdown selection component built on top of Listbox.
- **[Slider](#slider)**: Select a value from a range with custom marks and labels.
- **[Surface](#surface)**: Base surface component for creating elevated containers.
<span id="alert"></span>
### Alert
<ComponentPreview
name="alert-basic"
/>
<span id="checkbox-checkboxgroup"></span>
### Checkbox & CheckboxGroup
<ComponentPreview
name="checkbox-basic"
/>
<ComponentPreview
name="checkbox-group-basic"
/>
<span id="inputotp"></span>
### InputOTP
<ComponentPreview
name="input-otp-basic"
/>
<span id="listbox"></span>
### Listbox
<ComponentPreview
name="listbox-default"
/>
<span id="select"></span>
### Select
<ComponentPreview
name="select-default"
/>
<span id="slider"></span>
### Slider
<ComponentPreview
name="slider-default"
/>
<span id="surface"></span>
### Surface
<ComponentPreview
name="surface-variants"
/>
### Improved Component APIs
Several components have been refined with better APIs:
- **Link**: Added `underline` and `underlineOffset` props for better customization
<ComponentPreview
name="link-basic"
/>
- **Card**: Improved variants and styling system
<ComponentPreview
name="card-with-images"
/>
- **Chip**: Enhanced with size variants and improved color system
<ComponentPreview
name="chip-basic"
/>
- **Switch**: Redesigned from the ground up with improved visual design and animations
<span id="switch"></span>
<ComponentPreview
name="switch-basic"
/>
- **RadioGroup**: Redesigned from the ground up with better API and styling
<span id="radiogroup"></span>
<ComponentPreview
name="radio-group-basic"
/>
### Flexible Component Patterns
HeroUI now supports flexible component syntax. Use compound patterns with or without `.Root`, or named exports - all three patterns work identically.
**Available patterns:**
```tsx
import { Avatar } from "@heroui/react"
// 1. Compound pattern (no .Root needed) - recommended
<Avatar>
<Avatar.Image src="/avatar.jpg" alt="User" />
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar>
// 2. Compound pattern with .Root - still supported
<Avatar.Root>
<Avatar.Image src="/avatar.jpg" alt="User" />
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar.Root>
// 3. Named exports
import { AvatarRoot, AvatarImage, AvatarFallback } from "@heroui/react"
<AvatarRoot>
<AvatarImage src="/avatar.jpg" alt="User" />
<AvatarFallback>JD</AvatarFallback>
</AvatarRoot>
```
**Simple components** like Button work the same way:
```tsx
import { Button } from "@heroui/react"
// No .Root needed
<Button>Label</Button>
// Or with .Root
<Button.Root>Label</Button.Root>
// Or named export
import { ButtonRoot } from "@heroui/react"
<ButtonRoot>Label</ButtonRoot>
```
**You can mix compound and named exports** in the same component:
```tsx
import { Avatar, AvatarFallback } from "@heroui/react"
<Avatar>
<Avatar.Image src="/avatar.jpg" alt="User" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
```
This provides:
- **Simpler API**: Main components no longer require `.Root` suffix
- **Flexibility**: Choose between compound pattern, compound with `.Root`, or named exports
- **Backward Compatibility**: The `.Root` pattern still works
- **Naming Consistency**: Standardized naming (e.g., "Container" instead of "Wrapper")
### Global Animation Control
HeroUI now supports easy global animation control through the `data-reduce-motion` attribute. Simply add `data-reduce-motion="true"` to your `<html>` or `<body>` tag to disable all animations across your application.
```html
<!DOCTYPE html>
<html data-reduce-motion="true">
<!-- All HeroUI animations will be disabled -->
</html>
```
HeroUI automatically respects user motion preferences using the `prefers-reduced-motion` media query and extends Tailwind's `motion-reduce:` variant to support both system preferences and manual control via the data attribute. This provides flexible control over animations while maintaining accessibility best practices.
Learn more about animations and motion preferences in the [Animation documentation](/docs/handbook/animation).
## ⚠️ Breaking Changes
### Design System Variables
#### Panel → Surface & Overlay
The `--panel` variable has been replaced with `--surface` and `--overlay` to better distinguish between non-overlay components (cards, accordions) and floating components (tooltips, popovers, modals).
**Before:**
```css
--panel: var(--white);
--panel-foreground: var(--foreground);
--shadow-panel: 0 0 1px 0 rgba(0, 0, 0, 0.3) inset, 0 2px 8px 0 rgba(0, 0, 0, 0.08);
```
**After:**
```css
--surface: var(--white);
--surface-foreground: var(--foreground);
--overlay: var(--white);
--overlay-foreground: var(--foreground);
--shadow-surface: 0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.06);
--shadow-overlay: 0 4px 16px 0 rgba(24, 24, 27, 0.08), 0 8px 24px 0 rgba(24, 24, 27, 0.09);
```
**Migration:**
- Replace `bg-panel` with `bg-surface` for non-overlay components
- Replace `bg-panel` with `bg-overlay` for floating components
- Replace `shadow-panel` with `shadow-surface` or `shadow-overlay`
- Replace `--color-panel` with `--color-surface` or `--color-overlay`
#### Surface Levels Simplified
The `--surface-1`, `--surface-2`, and `--surface-3` variables have been removed. Surface levels are now automatically calculated from `--surface` using `color-mix`, so you only need to declare the base surface color.
**Before (manual declaration):**
```css
--surface-1: var(--background);
--surface-2: var(--color-neutral-100);
--surface-3: var(--color-neutral-200);
```
**After (auto-calculated):**
```css
/* You only declare the base surface */
--surface: var(--white);
--surface-foreground: var(--foreground);
/* HeroUI automatically calculates these using color-mix */
--color-surface-secondary: color-mix(in oklab, var(--surface) 94%, var(--surface-foreground) 6%);
--color-surface-tertiary: color-mix(in oklab, var(--surface) 92%, var(--surface-foreground) 8%);
--color-surface-quaternary: color-mix(in oklab, var(--surface) 86%, var(--surface-foreground) 14%);
```
**Customization:**
You can override the default calculations using Tailwind's `@theme` directive:
```css
@theme inline {
--color-surface-secondary: color-mix(in oklab, var(--surface) 96%, var(--surface-foreground) 4%);
--color-surface-tertiary: color-mix(in oklab, var(--surface) 94%, var(--surface-foreground) 6%);
--color-surface-quaternary: color-mix(in oklab, var(--surface) 90%, var(--surface-foreground) 10%);
}
```
**Migration:**
- Replace `bg-surface-1` with `bg-surface` (base surface)
- Replace `bg-surface-2` with `bg-surface-secondary` (auto-calculated)
- Replace `bg-surface-3` with `bg-surface-tertiary` (auto-calculated)
The same auto-calculation pattern applies to:
- **Background shades**: Calculated from `--background` → `background-secondary`, `background-tertiary`, `background-quaternary`
- **Soft colors**: Calculated from status colors → `accent-soft`, `danger-soft`, `warning-soft`, `success-soft`
#### Border Width Default Changed
The default border width has changed from `1px` to `0px`. Borders are now opt-in rather than default.
**Before:**
```css
--border-width: 1px;
```
**After:**
```css
--border-width: 0px; /* no border by default */
```
**Migration:**
- If you rely on default borders, explicitly set `border-width` in your custom styles
- Form fields now use `transparent` borders by default
#### Border Color Default Changed
The default border color opacity has changed from `15%` to `0%` (transparent).
**Before:**
```css
--border: oklch(0 0 0 / 15%);
```
**After:**
```css
--border: oklch(0 0 0 / 0%);
```
**Field Border Default:**
```css
--field-border: transparent; /* no border by default on form fields */
```
#### Shadow System Updates
The shadow system has been completely redesigned with separate shadows for surfaces and overlays.
**Before:**
```css
--panel-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.3) inset, 0 2px 8px 0 rgba(0, 0, 0, 0.08);
--field-shadow: 0 0 0 0 rgba(255, 255, 255, 0.1) inset, 0 1px 2px 0 rgba(0, 0, 0, 0.05);
```
**After (Light):**
```css
--surface-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.06);
--overlay-shadow: 0 4px 16px 0 rgba(24, 24, 27, 0.08), 0 8px 24px 0 rgba(24, 24, 27, 0.09);
--field-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.06);
```
**After (Dark):**
```css
--surface-shadow: 0 0 0 0 transparent inset; /* No shadow on dark mode */
--overlay-shadow: 0 0 0 0 transparent inset; /* No shadow on dark mode */
--field-shadow: 0 0 0 0 transparent inset; /* Transparent shadow to allow ring utilities to work */
```
#### Accent Color Updates
The accent color has been updated for better contrast and visual appeal.
**Before:**
```css
--accent: var(--color-neutral-950);
--accent-foreground: var(--snow);
```
**After:**
```css
--accent: oklch(0.6204 0.195 253.83);
--accent-foreground: var(--snow);
```
#### Status Color Refinements
Success, warning, and danger colors have been refined for better consistency and contrast.
**Success:**
- **Before:** `oklch(0.5503 0.1244 153.56)`
- **After:** `oklch(0.7329 0.1935 150.81)`
- Foreground changed from `var(--snow)` to `var(--eclipse)` in light mode
**Warning:**
- **Before:** `oklch(0.7186 0.1521 64.85)`
- **After:** `oklch(0.7819 0.1585 72.33)` (light), `oklch(0.8203 0.1388 76.34)` (dark)
**Danger:**
- **Before:** `oklch(0.6259 0.1908 29.19)`
- **After:** `oklch(0.6532 0.2328 25.74)` (light), `oklch(0.594 0.1967 24.63)` (dark)
### Component API Changes
#### Chip Component
The Chip component's `type` prop has been renamed to `color`, and a new `size` prop has been added. A new `soft` variant has been introduced.
**Before:**
```tsx
import { Chip } from "@heroui/react";
<Chip type="danger" variant="secondary">Label</Chip>
```
**After:**
```tsx
import { Chip } from "@heroui/react";
<Chip color="danger" variant="soft" size="md">Label</Chip>
```
**Migration:**
- Replace `type` prop with `color` prop
- Use `size` prop (`sm`, `md`, `lg`) to control chip size
- The `soft` variant provides a subtle appearance for less prominent chips
#### Link Component
The Link component now supports `underline` and `underlineOffset` props, and includes `asChild` support.
**Before:**
```tsx
import { Link } from "@heroui/react";
<Link href="#">Link text</Link>
```
**After:**
```tsx
import { Link } from "@heroui/react";
<Link href="#" underline="hover" underlineOffset={4}>Link text</Link>
```
**New Props:**
- `underline`: `"none" | "hover" | "always"` - Controls underline visibility
- `underlineOffset`: `number` - Controls underline offset from text
#### Type Reference Syntax
Due to the dual pattern implementation, type references through the namespace syntax are no longer supported. Use object-style syntax or named type imports instead.
**Before (no longer works):**
```tsx
type AvatarProps = Avatar.RootProps
```
**After (Option 1 - Object-style syntax):**
```tsx
type AvatarProps = Avatar["RootProps"]
```
**After (Option 2 - Named type imports, recommended):**
```tsx
import type { AvatarRootProps } from "@heroui/react"
type AvatarProps = AvatarRootProps
```
This change affects all compound components when accessing prop types.
#### Tabs Component Renaming
The Tabs component's wrapper element has been renamed for consistency:
- **Compound property**: `Tabs.ListWrapper` → `Tabs.ListContainer`
- **Named export**: `TabListWrapper` → `TabListContainer`
- **CSS class**: `.tabs__list-wrapper` → `.tabs__list-container`
- **Data attribute**: `data-slot="tabs-list-wrapper"` → `data-slot="tabs-list-container"`
**Migration:**
Find and replace all instances of `TabListWrapper` with `TabListContainer`:
```bash
# Component usage
TabListWrapper → TabListContainer
Tabs.ListWrapper → Tabs.ListContainer
# CSS selectors (if using custom styles)
.tabs__list-wrapper → .tabs__list-container
[data-slot="tabs-list-wrapper"] → [data-slot="tabs-list-container"]
```
#### Removed Variables
The following variables have been removed:
- `--panel` → Use `--surface` or `--overlay`
- `--panel-foreground` → Use `--surface-foreground` or `--overlay-foreground`
- `--surface-1`, `--surface-2`, `--surface-3` → Use background shades or surface levels
- `--accent-soft` → Use `--color-accent-soft` (now calculated)
- `--radius-panel` and `--radius-panel-inner` → Use standard radius values
## Design System Updates
### New Color System
#### Surface vs Overlay Concept
The design system now distinguishes between two types of elevated components:
- **Surface**: Used for non-overlay components like cards, accordions, and disclosure groups that sit on the page
- **Overlay**: Used for floating components like tooltips, popovers, modals, and menus that appear above the page
This distinction provides:
- Better visual hierarchy
- Appropriate shadow depths
- Improved dark mode contrast
- Clearer component semantics
#### Auto-Calculated Color System
HeroUI now automatically calculates shade levels and soft color variants using CSS `color-mix`. You only need to declare the base colors, and HeroUI handles the rest.
**Background Shade Levels**
Background shades are automatically calculated from `--background`:
```css
/* You only declare the base */
--background: oklch(0.9702 0 0);
--foreground: var(--eclipse);
/* HeroUI automatically calculates these */
--color-background-secondary: color-mix(in oklab, var(--color-background) 96%, var(--color-foreground) 4%);
--color-background-tertiary: color-mix(in oklab, var(--color-background) 92%, var(--color-foreground) 8%);
--color-background-quaternary: color-mix(in oklab, var(--color-background) 86%, var(--color-foreground) 14%);
```
**Surface Levels**
Surface levels are automatically calculated from `--surface`:
```css
/* You only declare the base */
--surface: var(--white);
--surface-foreground: var(--foreground);
/* HeroUI automatically calculates these */
--color-surface-secondary: color-mix(in oklab, var(--surface) 94%, var(--surface-foreground) 6%);
--color-surface-tertiary: color-mix(in oklab, var(--surface) 92%, var(--surface-foreground) 8%);
--color-surface-quaternary: color-mix(in oklab, var(--surface) 86%, var(--surface-foreground) 14%);
```
**Soft Color Variants**
Soft color variants are automatically calculated from status colors:
```css
/* You declare the base status colors */
--accent: oklch(0.6204 0.195 253.83);
--danger: oklch(0.6532 0.2328 25.74);
--warning: oklch(0.7819 0.1585 72.33);
--success: oklch(0.7329 0.1935 150.81);
/* HeroUI automatically calculates these at 15% opacity */
--color-accent-soft: color-mix(in oklab, var(--color-accent) 15%, transparent);
--color-danger-soft: color-mix(in oklab, var(--color-danger) 15%, transparent);
--color-warning-soft: color-mix(in oklab, var(--color-warning) 15%, transparent);
--color-success-soft: color-mix(in oklab, var(--color-success) 15%, transparent);
```
Each soft variant includes hover states (20% opacity) and foreground colors for proper contrast.
**Customization:**
You can override any auto-calculated values using Tailwind's `@theme` directive:
```css
@theme inline {
/* Adjust surface levels */
--color-surface-secondary: color-mix(in oklab, var(--surface) 96%, var(--surface-foreground) 4%);
/* Adjust soft colors */
--color-accent-soft: color-mix(in oklab, var(--color-accent) 20%, transparent);
}
```
This auto-calculation system reduces the number of variables you need to manage while providing full customization when needed.
### Shadow System
The shadow system has been redesigned to provide:
- Separate shadows for surfaces and overlays
- Better depth perception
- Dark mode support (transparent shadows)
- Consistent field shadows
Shadows automatically adapt to light and dark modes, providing appropriate depth cues for each theme.
### Focus System
The focus color now uses the accent color for consistency:
```css
--focus: var(--accent);
```
This ensures focus indicators align with your brand colors while maintaining accessibility.
### Typography Tokens
Several typography-related variables have been removed in favor of using Tailwind's typography utilities directly. The design system now focuses on color and spacing tokens, letting Tailwind handle typography.
## Migration Guide
### Step 1: Update Design System Variables
Replace old panel variables with surface/overlay:
```css
/* Before */
.my-card {
background: var(--panel);
box-shadow: var(--shadow-panel);
}
/* After */
.my-card {
background: var(--surface);
box-shadow: var(--shadow-surface);
}
.my-tooltip {
background: var(--overlay);
box-shadow: var(--shadow-overlay);
}
```
### Step 2: Update Surface Levels
Surface levels are now automatically calculated from `--surface`, so you don't need to manually declare them. Simply use the new utility classes:
```css
/* Before */
.bg-surface-1 → .bg-surface (base surface)
.bg-surface-2 → .bg-surface-secondary (auto-calculated)
.bg-surface-3 → .bg-surface-tertiary (auto-calculated)
/* You can also use background shades */
.bg-surface-2 → .bg-background-secondary (auto-calculated from --background)
.bg-surface-3 → .bg-background-tertiary (auto-calculated from --background)
```
**Note:** Surface levels (`surface-secondary`, `surface-tertiary`, etc.) are automatically calculated based on your `--surface` color. No manual CSS variables needed unless you want to customize the calculations.
### Step 3: Update Component Props
Update Chip and Link components:
```tsx
// Chip: type → color, add size if needed
<Chip type="danger" /> → <Chip color="danger" size="md" />
// Link: Add underline props if customizing underlines
<Link href="#">Text</Link> // Still works, underline props are optional
```
### Step 4: Simplify Component Patterns (Optional)
If you adopted the `.Root` suffix from v3.0.0-alpha.35, you can now simplify your code by removing it:
**Before (v3.0.0-alpha.35):**
```tsx
<Avatar.Root>
<Avatar.Image src="..." alt="..." />
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar.Root>
```
**After (simpler):**
```tsx
<Avatar>
<Avatar.Image src="..." alt="..." />
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar>
```
**Note:** The `.Root` syntax still works if you prefer it.
### Step 5: Update Type References
If you're using namespace syntax for types, switch to object-style syntax or named imports:
**Before:**
```tsx
type ButtonProps = Button.RootProps
```
**After (Option 1 - Object-style):**
```tsx
type ButtonProps = Button["RootProps"]
```
**After (Option 2 - Named imports, recommended):**
```tsx
import type { ButtonRootProps } from "@heroui/react"
type ButtonProps = ButtonRootProps
```
### Step 6: Update Tabs Component
Replace `TabListWrapper` with `TabListContainer`:
**Before:**
```tsx
import { Tabs } from "@heroui/react"
<Tabs.Root>
<Tabs.ListWrapper>
<Tabs.List>
<Tabs.Tab id="home">Home<Tabs.Indicator /></Tabs.Tab>
</Tabs.List>
</Tabs.ListWrapper>
<Tabs.Panel id="home">Content</Tabs.Panel>
</Tabs.Root>
```
**After:**
```tsx
import { Tabs } from "@heroui/react"
<Tabs>
<Tabs.ListContainer>
<Tabs.List>
<Tabs.Tab id="home">Home<Tabs.Indicator /></Tabs.Tab>
</Tabs.List>
</Tabs.ListContainer>
<Tabs.Panel id="home">Content</Tabs.Panel>
</Tabs>
```
### Step 7: Handle Border Changes
If your custom styles rely on default borders:
```css
/* Add explicit borders where needed */
.my-component {
border-width: 1px;
border-color: var(--color-border);
}
```
### Step 8: Update Status Colors
If you've customized status colors, review the new values and adjust your custom theme if needed:
```css
/* Check if your custom status colors need updates */
--success: oklch(0.7329 0.1935 150.81); /* New value */
--warning: oklch(0.7819 0.1585 72.33); /* New value */
--danger: oklch(0.6532 0.2328 25.74); /* New value */
```
### Automated Migration
For large codebases, you can use find-and-replace:
```bash
# Panel → Surface
--panel → --surface
bg-panel → bg-surface
shadow-panel → shadow-surface
# Panel → Overlay (for floating components)
--panel → --overlay (where appropriate)
bg-panel → bg-overlay (for tooltips, popovers, etc.)
shadow-panel → shadow-overlay (for floating components)
# Chip type prop
type=" → color="
# Surface levels
bg-surface-1 → bg-surface
bg-surface-2 → bg-surface-secondary
bg-surface-3 → bg-surface-tertiary
# Tabs component
TabListWrapper → TabListContainer
Tabs.ListWrapper → Tabs.ListContainer
# Type references
Component.RootProps → Component["RootProps"] or use named imports
```
## Component Updates
### Card Component
Card component has been refined with improved variants and better semantic structure. The component now uses the new surface system for consistent styling.
### Accordion Component
Accordion now uses the surface system for better visual consistency with other components.
### Form Components
Form components (Input, TextField, TextArea) have been updated to use the new field border system (transparent by default) for a cleaner look while maintaining accessibility.
### Component Pattern Updates
All components now support flexible patterns. Components that support the dual pattern include:
- **Simple components**: Button, Link, Spinner, Chip, Kbd
- **Compound components**: Accordion, Avatar, Card, Disclosure, Fieldset, Popover, RadioGroup, Switch, Tabs, Tooltip
You can use any of the three patterns (compound without `.Root`, compound with `.Root`, or named exports) with all these components.
## HeroUI Pro
HeroUI Pro is being reshaped from the ground up on top of the new design system. The new Pro version will feature:
- New components built on top of HeroUI v3
- Tailwind CSS v4 native support
- CSS native animations
- Enhanced customization options
We'll share more updates soon.
## Roadmap
We're working towards a stable release in **Q4** this year (2025). This beta release brings us significantly closer to that goal with:
- Comprehensive component set
- Refined design system
- Improved developer experience
- Better performance
## Community
The reception on the native side has been phenomenal. Thank you for supporting us as we build HeroUI v3! Your feedback helps us improve every day.
See what the community is saying: [HeroUI Native Reception](https://x.com/hero_ui/status/1985721976220966926)
## Links
- [Component Documentation](https://v3.heroui.com/docs/components)
- [Design System - Figma Kit V3](https://www.figma.com/community/file/1546526812159103429/heroui-figma-kit-v3)
- [HeroUI Native](https://link.heroui.com/native)
- [GitHub Repository](https://github.com/heroui-inc/heroui)
- [GitHub PR #5872](https://github.com/heroui-inc/heroui/pull/5872)
## Contributors
Thanks to everyone who contributed to this release, helping us create a design system that's both beautiful and practical!
@@ -6,7 +6,7 @@ links:
rac: Disclosure
source: accordion/accordion.tsx
styles: accordion.css
storybook: accordion
storybook: Components/Navigation/Accordion
figma: true
---
@@ -31,7 +31,7 @@ Import the Accordion component and access all parts using dot notation.
import { Accordion } from '@heroui/react';
export default () => (
<Accordion.Root>
<Accordion>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
@@ -42,7 +42,7 @@ export default () => (
<Accordion.Body/>
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
</Accordion>
)
```
@@ -120,7 +120,7 @@ const items = [
export function CustomStyles() {
return (
<Accordion.Root className="bg-surface-1 w-full max-w-md rounded-2xl" variant="outline">
<Accordion className="bg-surface-secondary w-full max-w-md rounded-2xl" variant="surface">
{items.map((item, index) => (
<Accordion.Item
key={index}
@@ -131,7 +131,7 @@ export function CustomStyles() {
)}
>
<Accordion.Heading>
<Accordion.Trigger className="hover:bg-surface-2 group flex items-center gap-2">
<Accordion.Trigger className="hover:bg-surface-tertiary group flex items-center gap-2">
{item.iconUrl ? (
<img
alt={item.title}
@@ -153,7 +153,7 @@ export function CustomStyles() {
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion.Root>
</Accordion>
);
}`} />
@@ -211,7 +211,7 @@ The component supports both CSS pseudo-classes and data attributes for flexibili
## API Reference
### Accordion.Root Props
### Accordion Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
@@ -220,7 +220,7 @@ The component supports both CSS pseudo-classes and data attributes for flexibili
| `expandedKeys` | `Iterable<Key>` | - | The controlled expanded keys |
| `onExpandedChange` | `(keys: Set<Key>) => void` | - | Handler called when expanded keys change |
| `isDisabled` | `boolean` | `false` | Whether the entire accordion is disabled |
| `variant` | `"default" \| "outline"` | `"default"` | The visual variant of the accordion |
| `variant` | `"default" \| "surface"` | `"default"` | The visual variant of the accordion |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The accordion items |
+153
View File
@@ -0,0 +1,153 @@
---
title: Alert
description: Display important messages and notifications to users with status indicators
icon: new
links:
source: alert/alert.tsx
styles: alert.css
storybook: Components/Feedback/Alert
figma: true
---
## Import
```tsx
import { Alert } from '@heroui/react';
```
### Usage
<ComponentPreview
name="alert-basic"
/>
### Anatomy
Import the Alert component and access all parts using dot notation.
```tsx
import { Alert } from '@heroui/react';
export default () => (
<Alert>
<Alert.Indicator />
<Alert.Content>
<Alert.Title />
<Alert.Description />
</Alert.Content>
</Alert>
)
```
<RelatedComponents component="alert" />
## Styling
### Passing Tailwind CSS classes
```tsx
import { Alert } from "@heroui/react";
function CustomAlert() {
return (
<Alert className="border-2 border-blue-500 rounded-xl" status="accent">
<Alert.Indicator className="text-blue-600" />
<Alert.Content className="gap-1">
<Alert.Title className="font-bold text-lg">Custom Alert</Alert.Title>
<Alert.Description className="text-sm opacity-80">
This alert has custom styling applied
</Alert.Description>
</Alert.Content>
</Alert>
);
}
```
### Customizing the component classes
To customize the Alert component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.alert {
@apply rounded-2xl shadow-lg;
}
.alert__title {
@apply font-bold text-lg;
}
.alert--danger {
@apply border-l-4 border-red-600;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Alert component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/alert.css)):
#### Base Classes
- `.alert` - Base alert container
- `.alert__indicator` - Icon/indicator container
- `.alert__content` - Content wrapper for title and description
- `.alert__title` - Alert title text
- `.alert__description` - Alert description text
#### Status Variant Classes
- `.alert--default` - Default gray status
- `.alert--accent` - Accent blue status
- `.alert--success` - Success green status
- `.alert--warning` - Warning yellow/orange status
- `.alert--danger` - Danger red status
### Interactive States
The Alert component is primarily informational and doesn't have interactive states on the base component. However, it can contain interactive elements like buttons or close buttons.
## API Reference
### Alert Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | The visual status of the alert |
| `asChild` | `boolean` | `false` | Merge props onto the child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert content |
### Alert.Indicator Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `asChild` | `boolean` | `false` | Merge props onto the child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator icon (defaults to status icon) |
### Alert.Content Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `asChild` | `boolean` | `false` | Merge props onto the child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content (typically Title and Description) |
### Alert.Title Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `asChild` | `boolean` | `false` | Merge props onto the child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert title text |
### Alert.Description Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `asChild` | `boolean` | `false` | Merge props onto the child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert description text |
+14 -7
View File
@@ -1,12 +1,11 @@
---
title: Avatar
description: Display user profile images with customizable fallback content
icon: updated
links:
radix: avatar
source: avatar/avatar.tsx
styles: avatar.css
storybook: avatar
storybook: Components/Media/Avatar
figma: true
---
@@ -28,10 +27,10 @@ Import the Avatar component and access all parts using dot notation.
import { Avatar } from '@heroui/react';
export default () => (
<Avatar.Root>
<Avatar>
<Avatar.Image/>
<Avatar.Fallback/>
</Avatar.Root>
</Avatar>
)
```
@@ -43,6 +42,10 @@ export default () => (
<ComponentPreview name="avatar-colors" />
### Variants
<ComponentPreview name="avatar-variants" />
### Fallback Content
<ComponentPreview name="avatar-fallback" />
@@ -64,10 +67,10 @@ import { Avatar } from '@heroui/react';
function CustomAvatar() {
return (
<Avatar.Root className="size-20">
<Avatar className="size-20">
<Avatar.Image src="..." alt="..." />
<Avatar.Fallback>XL</Avatar.Fallback>
</Avatar.Root>
</Avatar>
);
}
```
@@ -105,6 +108,9 @@ The Avatar component uses these CSS classes ([View source styles](https://github
- `.avatar--md` - Medium avatar (default, no additional styles)
- `.avatar--lg` - Large avatar (size-12)
#### Variant Modifiers
- `.avatar--soft` - Soft variant with lighter background
#### Color Modifiers
- `.avatar__fallback--default` - Default text color
- `.avatar__fallback--accent` - Accent text color
@@ -114,12 +120,13 @@ The Avatar component uses these CSS classes ([View source styles](https://github
## API Reference
### Avatar.Root Props
### Avatar Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Avatar size |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | `'default'` | Fallback color theme |
| `variant` | `'default' \| 'soft'` | `'default'` | Visual style variant |
| `className` | `string` | - | Additional CSS classes |
| `asChild` | `boolean` | `false` | Render as child element |
+1 -1
View File
@@ -5,7 +5,7 @@ links:
rac: Button
source: button/button.tsx
styles: button.css
storybook: button
storybook: Components/Buttons/Button
figma: true
---
+27 -24
View File
@@ -5,7 +5,7 @@ icon: updated
links:
source: card/card.tsx
styles: card.css
storybook: card
storybook: Components/Layout/Card
---
## Import
@@ -28,23 +28,31 @@ Import the Card component and access all parts using dot notation.
import { Card } from "@heroui/react";
export default () => (
<Card.Root>
<Card>
<Card.Header>
<Card.Title />
<Card.Description />
</Card.Header>
<Card.Content />
<Card.Footer />
</Card.Root>
</Card>
);
```
### Variants
Cards come in semantic variants that describe their prominence level rather than specific visual styles. This allows themes to interpret them differently:
<ComponentPreview
name="card-variants"
/>
- **`transparent`** - Minimal prominence, transparent background (great for nested cards)
- **`default`** - Standard card for most use cases (surface-secondary)
- **`secondary`** - Medium prominence to draw moderate attention (surface-tertiary)
- **`tertiary`** - Higher prominence for important content (surface-quaternary)
- **`quaternary`** - Highest prominence for critical content
### Horizontal Layout
<ComponentPreview
@@ -57,16 +65,10 @@ export default () => (
name="card-with-avatar"
/>
### With Image
### With Images
<ComponentPreview
name="card-with-image"
/>
### With Background Image
<ComponentPreview
name="card-with-background-image"
name="card-with-images"
/>
### With Form
@@ -79,18 +81,18 @@ export default () => (
```tsx
// Semantic markup
<Card.Root role="article" aria-labelledby="card-title">
<Card role="article" aria-labelledby="card-title">
<Card.Header>
<Card.Title id="card-title">Article Title</Card.Title>
</Card.Header>
</Card.Root>
</Card>
// Interactive cards
<Card.Root asChild>
<Card asChild>
<a href="/details" aria-label="View product details">
<Card.Title>Product Name</Card.Title>
</a>
</Card.Root>
</Card>
```
## Styling
@@ -98,7 +100,7 @@ export default () => (
### Component Customization
```tsx
<Card.Root className="border-2 border-blue-500 bg-gradient-to-r from-blue-50 to-purple-50">
<Card className="border-2 border-blue-500 bg-gradient-to-r from-blue-50 to-purple-50">
<Card.Header>
<Card.Title className="text-blue-900">Custom Styled Card</Card.Title>
<Card.Description className="text-blue-700">Custom colors applied</Card.Description>
@@ -106,14 +108,14 @@ export default () => (
<Card.Content>
<p className="text-blue-800">Content with custom styling</p>
</Card.Content>
</Card.Root>
</Card>
```
### CSS Variable Overrides
```css
/* Override specific variants */
.card--variant-elevated {
.card--secondary {
@apply bg-gradient-to-br from-blue-50 to-purple-50;
}
@@ -139,18 +141,19 @@ Card uses BEM naming for predictable styling, ([View source styles](https://gith
#### Variant Classes
- `.card--variant-flat` - Transparent background, no border
- `.card--variant-outlined` - Border with subtle background (default)
- `.card--variant-elevated` - Slightly elevated with medium background
- `.card--variant-filled` - Prominently filled with stronger background
- `.card--transparent` - Minimal prominence, transparent background (maps to `transparent` variant)
- `.card--default` - Standard appearance with surface-secondary (default)
- `.card--secondary` - Medium prominence with surface-tertiary (maps to `secondary` variant)
- `.card--tertiary` - Higher prominence with surface-quaternary (maps to `tertiary` variant)
- `.card--quaternary` - Highest prominence for critical content (maps to `quaternary` variant)
## API Reference
### Card.Root
### Card
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------------------- | ------------ | -------------------------- |
| `variant` | `"flat" \| "outlined" \| "elevated" \| "filled"` | `"outlined"` | Visual variant of the card |
| `variant` | `"transparent" \| "default" \| "secondary" \| "tertiary" \| "quaternary"` | `"default"` | Semantic variant indicating prominence level |
| `asChild` | `boolean` | `false` | Render as a child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Card content |
@@ -0,0 +1,168 @@
---
title: CheckboxGroup
description: A checkbox group component for managing multiple checkbox selections
icon: new
links:
rac: CheckboxGroup
source: checkbox-group/checkbox-group.tsx
styles: checkbox-group.css
storybook: Components/Forms/CheckboxGroup
---
## Import
```tsx
import { CheckboxGroup, Checkbox, Label, Description } from '@heroui/react';
```
### Usage
<ComponentPreview
name="checkbox-group-basic"
/>
### Anatomy
Import the CheckboxGroup component and access all parts using dot notation.
```tsx
import {CheckboxGroup, Checkbox, Label, Description, FieldError} from '@heroui/react';
export default () => (
<CheckboxGroup name="interests">
<Label />
<Description /> {/* Optional */}
<Checkbox value="option1">
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
<Checkbox.Content>
<Label />
<Description /> {/* Optional */}
</Checkbox.Content>
</Checkbox>
<FieldError /> {/* Optional */}
</CheckboxGroup>
);
```
### On Surface
When used inside a [Surface](/docs/components/surface) component, CheckboxGroup automatically applies on-surface styling.
<ComponentPreview
name="checkbox-group-on-surface"
/>
### With Custom Indicator
<ComponentPreview
name="checkbox-group-with-custom-indicator"
/>
### Indeterminate
<ComponentPreview
name="checkbox-group-indeterminate"
/>
### Controlled
<ComponentPreview
name="checkbox-group-controlled"
/>
### Validation
<ComponentPreview
name="checkbox-group-validation"
/>
### Disabled
<ComponentPreview
name="checkbox-group-disabled"
/>
### Features and Add-ons Example
<ComponentPreview
name="checkbox-group-features-and-addons"
/>
## Styling
### Passing Tailwind CSS classes
You can customize the CheckboxGroup component:
```tsx
import { CheckboxGroup, Checkbox, Label } from '@heroui/react';
function CustomCheckboxGroup() {
return (
<CheckboxGroup className="gap-4" name="custom">
<Checkbox value="option1">
<Checkbox.Control className="border-2 border-purple-500 data-[selected=true]:bg-purple-500">
<Checkbox.Indicator className="text-white" />
</Checkbox.Control>
<Checkbox.Content>
<Label>Option 1</Label>
</Checkbox.Content>
</Checkbox>
</CheckboxGroup>
);
}
```
### Customizing the component classes
To customize the CheckboxGroup component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.checkbox-group {
@apply flex flex-col gap-2;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The CheckboxGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox-group.css)):
- `.checkbox-group` - Base checkbox group container
## API Reference
### CheckboxGroup Props
Inherits from [React Aria CheckboxGroup](https://react-spectrum.adobe.com/react-aria/CheckboxGroup.html).
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | `string[]` | - | The current selected values (controlled) |
| `defaultValue` | `string[]` | - | The default selected values (uncontrolled) |
| `onChange` | `(value: string[]) => void` | - | Handler called when the selected values change |
| `isDisabled` | `boolean` | `false` | Whether the checkbox group is disabled |
| `isRequired` | `boolean` | `false` | Whether the checkbox group is required |
| `isReadOnly` | `boolean` | `false` | Whether the checkbox group is read only |
| `isInvalid` | `boolean` | `false` | Whether the checkbox group is in an invalid state |
| `name` | `string` | - | The name of the checkbox group, used when submitting an HTML form |
| `children` | `React.ReactNode \| (values: CheckboxGroupRenderProps) => React.ReactNode` | - | Checkbox group content or render prop |
### CheckboxGroupRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
|------|------|-------------|
| `value` | `string[]` | The currently selected values |
| `isDisabled` | `boolean` | Whether the checkbox group is disabled |
| `isReadOnly` | `boolean` | Whether the checkbox group is read only |
| `isInvalid` | `boolean` | Whether the checkbox group is in an invalid state |
| `isRequired` | `boolean` | Whether the checkbox group is required |
@@ -0,0 +1,236 @@
---
title: Checkbox
description: Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.
icon: new
links:
rac: Checkbox
source: checkbox/checkbox.tsx
styles: checkbox.css
storybook: Components/Forms/Checkbox
figma: true
---
## Import
```tsx
import { Checkbox, Label } from '@heroui/react';
```
### Usage
<ComponentPreview
name="checkbox-basic"
/>
### Anatomy
Import the Checkbox component and access all parts using dot notation.
```tsx
import { Checkbox, Label, Description } from '@heroui/react';
export default () => (
<Checkbox name="terms">
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
<Checkbox.Content>
<Label />
<Description /> {/* Optional */}
</Checkbox.Content>
</Checkbox>
);
```
### Disabled
<ComponentPreview
name="checkbox-disabled"
/>
### Default Selected
<ComponentPreview
name="checkbox-default-selected"
/>
### Controlled
<ComponentPreview
name="checkbox-controlled"
/>
### Indeterminate
<ComponentPreview
name="checkbox-indeterminate"
/>
### With Label
<ComponentPreview
name="checkbox-with-label"
/>
### With Description
<ComponentPreview
name="checkbox-with-description"
/>
### Render Props
<ComponentPreview
name="checkbox-render-props"
/>
### Form Integration
<ComponentPreview
name="checkbox-form"
/>
### Custom Styles
<ComponentPreview
name="checkbox-custom-styles"
/>
### Invalid
<ComponentPreview
name="checkbox-invalid"
/>
### Custom Indicator
<ComponentPreview
name="checkbox-custom-indicator"
/>
### Full Rounded
<ComponentPreview
name="checkbox-full-rounded"
/>
## Styling
### Passing Tailwind CSS classes
You can customize individual Checkbox components:
```tsx
import { Checkbox, Label } from '@heroui/react';
function CustomCheckbox() {
return (
<Checkbox name="custom">
<Checkbox.Control className="border-2 border-purple-500 data-[selected=true]:bg-purple-500">
<Checkbox.Indicator className="text-white" />
</Checkbox.Control>
<Checkbox.Content>
<Label>Custom Checkbox</Label>
</Checkbox.Content>
</Checkbox>
);
}
```
### Customizing the component classes
To customize the Checkbox component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.checkbox {
@apply inline-flex gap-3 items-center;
}
.checkbox__control {
@apply size-5 border-2 border-gray-400 rounded data-[selected=true]:bg-blue-500 data-[selected=true]:border-blue-500;
/* Animated background indicator */
&::before {
@apply bg-accent pointer-events-none absolute inset-0 z-0 origin-center scale-50 rounded-md opacity-0 content-[''];
transition:
scale 200ms linear,
opacity 200ms linear,
background-color 200ms ease-out;
}
/* Show indicator when selected */
&[data-selected="true"]::before {
@apply scale-100 opacity-100;
}
}
.checkbox__indicator {
@apply text-white;
}
.checkbox__content {
@apply flex flex-col gap-1;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Checkbox component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox.css)):
- `.checkbox` - Base checkbox container
- `.checkbox__control` - Checkbox control box
- `.checkbox__indicator` - Checkbox checkmark indicator
- `.checkbox__content` - Optional content container
### Interactive States
The checkbox supports both CSS pseudo-classes and data attributes for flexibility:
- **Selected**: `[data-selected="true"]` or `[aria-checked="true"]` (shows checkmark and background color change)
- **Indeterminate**: `[data-indeterminate="true"]` (shows indeterminate state with dash)
- **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` (shows error state with danger colors)
- **Hover**: `:hover` or `[data-hovered="true"]`
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring)
- **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events)
- **Pressed**: `:active` or `[data-pressed="true"]`
## API Reference
### Checkbox Props
Inherits from [React Aria Checkbox](https://react-spectrum.adobe.com/react-aria/Checkbox.html).
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isSelected` | `boolean` | `false` | Whether the checkbox is checked |
| `defaultSelected` | `boolean` | `false` | Whether the checkbox is checked by default (uncontrolled) |
| `isIndeterminate` | `boolean` | `false` | Whether the checkbox is in an indeterminate state |
| `isDisabled` | `boolean` | `false` | Whether the checkbox is disabled |
| `isInvalid` | `boolean` | `false` | Whether the checkbox is invalid |
| `isReadOnly` | `boolean` | `false` | Whether the checkbox is read only |
| `isOnSurface` | `boolean` | `false` | Whether the checkbox is displayed on a surface (affects styling) |
| `name` | `string` | - | The name of the input element, used when submitting an HTML form |
| `value` | `string` | - | The value of the input element, used when submitting an HTML form |
| `onChange` | `(isSelected: boolean) => void` | - | Handler called when the checkbox value changes |
| `children` | `React.ReactNode \| (values: CheckboxRenderProps) => React.ReactNode` | - | Checkbox content or render prop |
### CheckboxRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
|------|------|-------------|
| `isSelected` | `boolean` | Whether the checkbox is currently checked |
| `isIndeterminate` | `boolean` | Whether the checkbox is in an indeterminate state |
| `isHovered` | `boolean` | Whether the checkbox is hovered |
| `isPressed` | `boolean` | Whether the checkbox is currently pressed |
| `isFocused` | `boolean` | Whether the checkbox is focused |
| `isFocusVisible` | `boolean` | Whether the checkbox is keyboard focused |
| `isDisabled` | `boolean` | Whether the checkbox is disabled |
| `isReadOnly` | `boolean` | Whether the checkbox is read only |
+29 -16
View File
@@ -4,7 +4,7 @@ description: Small informational badges for displaying labels, statuses, and cat
links:
source: chip/chip.tsx
styles: chip.css
storybook: chip
storybook: Components/DataDisplay/Chip
figma: true
---
@@ -80,29 +80,41 @@ The Chip component uses these CSS classes ([View source styles](https://github.c
#### Base Classes
- `.chip` - Base chip styles
#### Type Classes
- `.chip--accent` - Accent type variant
- `.chip--danger` - Danger type variant
- `.chip--default` - Default type variant
- `.chip--success` - Success type variant
- `.chip--warning` - Warning type variant
#### Color Classes
- `.chip--accent` - Accent color variant
- `.chip--danger` - Danger color variant
- `.chip--default` - Default color variant
- `.chip--success` - Success color variant
- `.chip--warning` - Warning color variant
#### Variant Classes
- `.chip--primary` - Primary variant with filled background
- `.chip--secondary` - Secondary variant with border
- `.chip--tertiary` - Tertiary variant with transparent background
- `.chip--soft` - Soft variant with lighter background
#### Size Classes
- `.chip--sm` - Small size
- `.chip--md` - Medium size (default)
- `.chip--lg` - Large size
#### Compound Variant Classes
- `.chip--primary.chip--accent` - Primary accent combination
- `.chip--primary.chip--success` - Primary success combination
- `.chip--primary.chip--warning` - Primary warning combination
- `.chip--primary.chip--danger` - Primary danger combination
### Interactive States
Chips support combining variant and color classes (e.g., `.chip--secondary.chip--accent`). The following combinations have default styles defined:
The component supports focus visibility:
**Primary Variants:**
- `.chip--primary.chip--accent` - Primary accent combination with filled background
- `.chip--primary.chip--success` - Primary success combination with filled background
- `.chip--primary.chip--warning` - Primary warning combination with filled background
- `.chip--primary.chip--danger` - Primary danger combination with filled background
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]`
**Soft Variants:**
- `.chip--accent.chip--soft` - Soft accent combination with lighter background
- `.chip--success.chip--soft` - Soft success combination with lighter background
- `.chip--warning.chip--soft` - Soft warning combination with lighter background
- `.chip--danger.chip--soft` - Soft danger combination with lighter background
**Note:** You can apply custom styles to any variant-color combination (e.g., `.chip--secondary.chip--accent`, `.chip--tertiary.chip--success`) using the `@layer components` directive in your CSS.
## API Reference
@@ -112,5 +124,6 @@ The component supports focus visibility:
|------|------|---------|-------------|
| `children` | `React.ReactNode` | - | Content to display inside the chip |
| `className` | `string` | - | Additional CSS classes |
| `type` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Type variant of the chip |
| `variant` | `"primary" \| "secondary" \| "tertiary"` | `"secondary"` | Visual style variant |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Color variant of the chip |
| `variant` | `"primary" \| "secondary" \| "tertiary" \| "soft"` | `"secondary"` | Visual style variant |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the chip |
@@ -5,7 +5,7 @@ links:
rac: Button
source: close-button/close-button.tsx
styles: close-button.css
storybook: close-button
storybook: Components/Buttons/CloseButton
---
## Import
@@ -5,7 +5,6 @@ links:
rac: TextField
source: description/description.tsx
styles: description.css
storybook: description
---
## Import
@@ -1,12 +1,11 @@
---
title: DisclosureGroup
description: Container that manages multiple Disclosure items with coordinated expanded states
icon: updated
links:
rac: DisclosureGroup
source: disclosure-group/disclosure-group.tsx
styles: disclosure-group.css
storybook: disclosure-group
storybook: Components/Navigation/DisclosureGroup
---
## Import
@@ -31,14 +30,14 @@ import {DisclosureGroup, Disclosure} from '@heroui/react';
export default () => (
<DisclosureGroup>
<Disclosure.Root id="item1">
<Disclosure id="item1">
<Disclosure.Heading>
<Disclosure.Trigger>
<Disclosure.Indicator />
</Disclosure.Trigger>
</Disclosure.Heading>
<Disclosure.Content />
</Disclosure.Root>
</Disclosure>
</DisclosureGroup>
)
```
@@ -1,12 +1,11 @@
---
title: Disclosure
description: A disclosure is a collapsible section with a header containing a heading and a trigger button, and a panel that wraps the content.
icon: updated
links:
rac: Disclosure
source: disclosure/disclosure.tsx
styles: disclosure.css
storybook: disclosure
storybook: Components/Navigation/Disclosure
figma: true
---
@@ -30,14 +29,14 @@ Import the Disclosure component and access all parts using dot notation.
import { Disclosure } from '@heroui/react';
export default () => (
<Disclosure.Root>
<Disclosure>
<Disclosure.Heading>
<Disclosure.Trigger>
<Disclosure.Indicator />
</Disclosure.Trigger>
</Disclosure.Heading>
<Disclosure.Content/>
</Disclosure.Root>
</Disclosure>
)
```
@@ -52,7 +51,7 @@ import { Disclosure } from '@heroui/react';
function CustomDisclosure() {
return (
<Disclosure.Root className="border rounded-lg p-4">
<Disclosure className="border rounded-lg p-4">
<Disclosure.Heading>
<Disclosure.Trigger className="text-lg font-semibold">
Click to expand
@@ -64,7 +63,7 @@ function CustomDisclosure() {
Hidden content
</Disclosure.Body>
</Disclosure.Content>
</Disclosure.Root>
</Disclosure>
);
}
```
@@ -119,7 +118,7 @@ The component supports both CSS pseudo-classes and data attributes for flexibili
## API Reference
### Disclosure.Root Props
### Disclosure Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
@@ -5,7 +5,6 @@ links:
rac: FieldError
source: field-error/field-error.tsx
styles: field-error.css
storybook: field-error
---
## Import
+13 -7
View File
@@ -5,7 +5,7 @@ icon: updated
links:
source: fieldset/fieldset.tsx
styles: fieldset.css
storybook: fieldset
storybook: Components/Forms/Fieldset
---
## Import
@@ -18,6 +18,12 @@ import { Fieldset } from '@heroui/react';
<ComponentPreview name="fieldset-basic" />
### On Surface
When used inside a [Surface](/docs/components/surface) component, form controls within Fieldset automatically apply on-surface styling.
<ComponentPreview name="fieldset-on-surface" />
### Anatomy
Import the Fieldset component and access all parts using dot notation.
@@ -26,7 +32,7 @@ Import the Fieldset component and access all parts using dot notation.
import { Fieldset } from '@heroui/react';
export default () => (
<Fieldset.Root>
<Fieldset>
<Fieldset.Legend />
<Fieldset.Group>
{/* form fields go here */}
@@ -34,7 +40,7 @@ export default () => (
<Fieldset.Actions>
{/* action buttons go here */}
</Fieldset.Actions>
</Fieldset.Root>
</Fieldset>
)
```
@@ -51,7 +57,7 @@ import { Fieldset, TextField, Label, Input } from '@heroui/react';
function CustomFieldset() {
return (
<Fieldset.Root className="rounded-xl border border-border bg-surface-2 p-6 shadow-sm">
<Fieldset className="rounded-xl border border-border bg-surface p-6 shadow-sm">
<Fieldset.Legend className="text-lg font-semibold">Team members</Fieldset.Legend>
<Fieldset.Group className="grid gap-4 md:grid-cols-2">
<TextField>
@@ -66,7 +72,7 @@ function CustomFieldset() {
<Fieldset.Actions className="justify-end gap-3">
{/* Action buttons */}
</Fieldset.Actions>
</Fieldset.Root>
</Fieldset>
);
}
```
@@ -78,7 +84,7 @@ Use the `@layer components` directive to target Fieldset BEM-style classes.
```css
@layer components {
.fieldset {
@apply gap-5 rounded-xl border border-border/60 bg-surface-2 p-6 shadow-field;
@apply gap-5 rounded-xl border border-border/60 bg-surface p-6 shadow-field;
}
.fieldset__legend {
@@ -106,7 +112,7 @@ The Fieldset compound component exposes these CSS selectors:
## API Reference
### Fieldset.Root Props
### Fieldset Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
+1 -1
View File
@@ -45,7 +45,7 @@ import {Form, TextField, Label, Input, FieldError, Button} from '@heroui/react';
function CustomForm() {
return (
<Form className="w-full max-w-md space-y-4 rounded-lg border border-border bg-surface-2 p-6">
<Form className="w-full max-w-md space-y-4 rounded-lg border border-border bg-surface p-6">
<TextField>
<Label className="text-sm font-medium">Email</Label>
<Input className="rounded-full border-border/60" placeholder="Enter your email" />
@@ -0,0 +1,281 @@
---
title: InputOTP
description: A one-time password input component for verification codes and secure authentication
icon: new
links:
source: input-otp/input-otp.tsx
styles: input-otp.css
storybook: Components/Forms/InputOTP
figma: true
---
## Import
```tsx
import { InputOTP } from '@heroui/react';
```
### Usage
<ComponentPreview name="input-otp-basic" />
### Anatomy
Import the InputOTP component and access all parts using dot notation.
```tsx
import { InputOTP } from '@heroui/react';
export default () => (
<InputOTP maxLength={6}>
<InputOTP.Group>
<InputOTP.Slot index={0} />
<InputOTP.Slot index={1} />
{/* ...rest of the slots */}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
<InputOTP.Slot index={3} />
{/* ...rest of the slots */}
</InputOTP.Group>
</InputOTP>
)
```
> **InputOTP** is built on top of [input-otp](https://github.com/guilhermerodz/input-otp) by [@guilherme_rodz](https://twitter.com/guilherme_rodz), providing a flexible and accessible foundation for OTP input components.
### Four Digits
<ComponentPreview name="input-otp-four-digits" />
### Disabled State
<ComponentPreview name="input-otp-disabled" />
### With Pattern
Use the `pattern` prop to restrict input to specific characters. HeroUI exports common patterns like `REGEXP_ONLY_CHARS` and `REGEXP_ONLY_DIGITS`.
<ComponentPreview name="input-otp-with-pattern" />
### Controlled
Control the value to synchronize with state, clear the input, or implement custom validation.
<ComponentPreview name="input-otp-controlled" />
### With Validation
Use `isInvalid` together with validation messages to surface errors.
<ComponentPreview name="input-otp-with-validation" />
### On Complete
Use the `onComplete` callback to trigger actions when all slots are filled.
<ComponentPreview name="input-otp-on-complete" />
### Form Example
A complete two-factor authentication form with validation and submission.
<ComponentPreview name="input-otp-form-example" />
### On Surface
When used inside a [Surface](/docs/components/surface) component, InputOTP automatically applies on-surface styling.
<ComponentPreview name="input-otp-on-surface" />
<RelatedComponents component="inputotp" />
## Styling
### Passing Tailwind CSS classes
```tsx
import {InputOTP, Label} from '@heroui/react';
function CustomInputOTP() {
return (
<div className="flex flex-col gap-2">
<Label className="text-sm font-semibold">Enter verification code</Label>
<InputOTP
className="gap-3"
containerClassName="gap-4"
maxLength={6}
>
<InputOTP.Group className="gap-3">
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={0}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={1}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={2}
/>
</InputOTP.Group>
<InputOTP.Separator className="bg-border h-1 w-2 rounded-full" />
<InputOTP.Group className="gap-3">
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={3}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={4}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={5}
/>
</InputOTP.Group>
</InputOTP>
</div>
);
}
```
### Customizing the component classes
To customize the InputOTP component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.input-otp {
@apply gap-3;
}
.input-otp__slot {
@apply size-12 rounded-xl border-2 font-bold;
}
.input-otp__slot[data-active="true"] {
@apply border-primary-500 ring-2 ring-primary-200;
}
.input-otp__separator {
@apply w-2 h-1 bg-border-strong rounded-full;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The InputOTP component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/input-otp.css)):
#### Base Classes
- `.input-otp` - Base container
- `.input-otp__container` - Inner container from input-otp library
- `.input-otp__group` - Group of slots
- `.input-otp__slot` - Individual input slot
- `.input-otp__slot-value` - The character inside a slot
- `.input-otp__caret` - Blinking caret indicator
- `.input-otp__separator` - Visual separator between groups
#### State Classes
- `.input-otp__slot[data-active="true"]` - Currently active slot
- `.input-otp__slot[data-filled="true"]` - Slot with a character
- `.input-otp__slot[data-disabled="true"]` - Disabled slot
- `.input-otp__slot[data-invalid="true"]` - Invalid slot
- `.input-otp__container[data-disabled="true"]` - Disabled container
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- **Hover**: `:hover` or `[data-hovered="true"]` on slot
- **Active**: `[data-active="true"]` on slot (currently focused)
- **Filled**: `[data-filled="true"]` on slot (contains a character)
- **Disabled**: `[data-disabled="true"]` on container and slots
- **Invalid**: `[data-invalid="true"]` on slots
## API Reference
### InputOTP Props
InputOTP is built on top of the [input-otp](https://github.com/guilhermerodz/input-otp) library with additional features.
#### Base Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `maxLength` | `number` | - | **Required.** Number of input slots. |
| `value` | `string` | - | Controlled value (uncontrolled if not provided). |
| `onChange` | `(value: string) => void` | - | Handler called when the value changes. |
| `onComplete` | `(value: string) => void` | - | Handler called when all slots are filled. |
| `className` | `string` | - | Additional CSS classes for the container. |
| `containerClassName` | `string` | - | CSS classes for the inner container. |
| `isOnSurface` | `boolean` | `false` | Whether the input is displayed on a surface (affects styling) |
| `children` | `React.ReactNode` | - | InputOTP.Group, InputOTP.Slot, and InputOTP.Separator components. |
#### Validation Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isDisabled` | `boolean` | `false` | Whether the input is disabled. |
| `isInvalid` | `boolean` | `false` | Whether the input is in an invalid state. |
| `validationErrors` | `string[]` | - | Server-side or custom validation errors. |
| `validationDetails` | `ValidityState` | - | HTML5 validation details. |
#### Input Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `pattern` | `string` | - | Regex pattern for allowed characters (e.g., `REGEXP_ONLY_DIGITS`). |
| `textAlign` | `'left' \| 'center' \| 'right'` | `'left'` | Text alignment within slots. |
| `inputMode` | `'numeric' \| 'text' \| 'decimal' \| 'tel' \| 'search' \| 'email' \| 'url'` | `'numeric'` | Virtual keyboard type on mobile devices. |
| `placeholder` | `string` | - | Placeholder text for empty slots. |
| `pasteTransformer` | `(text: string) => string` | - | Transform pasted text (e.g., remove hyphens). |
#### Form Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `name` | `string` | - | Name attribute for form submission. |
| `autoFocus` | `boolean` | - | Whether to focus the first slot on mount. |
### InputOTP.Group Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes for the group. |
| `children` | `React.ReactNode` | - | InputOTP.Slot components. |
### InputOTP.Slot Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `index` | `number` | - | **Required.** Zero-based index of the slot. |
| `className` | `string` | - | Additional CSS classes for the slot. |
### InputOTP.Separator Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes for the separator. |
### Exported Patterns
HeroUI re-exports common regex patterns from input-otp for convenience:
```tsx
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@heroui/react';
// Use with pattern prop
<InputOTP pattern={REGEXP_ONLY_DIGITS} maxLength={6}>
{/* ... */}
</InputOTP>
```
- **REGEXP_ONLY_DIGITS** - Only numeric characters (0-9)
- **REGEXP_ONLY_CHARS** - Only alphabetic characters (a-z, A-Z)
- **REGEXP_ONLY_DIGITS_AND_CHARS** - Alphanumeric characters (0-9, a-z, A-Z)
+12 -4
View File
@@ -1,11 +1,12 @@
---
title: Input
description: Primitive single-line text input component that accepts standard HTML attributes
icon: updated
links:
rac: Input
source: input/input.tsx
styles: input.css
storybook: input
storybook: Components/Forms/Input
---
## Import
@@ -30,6 +31,12 @@ import { Input } from '@heroui/react';
<ComponentPreview name="input-controlled" />
### On Surface
When used inside a [Surface](/docs/components/surface) component, Input automatically applies on-surface styling.
<ComponentPreview name="input-on-surface" />
<RelatedComponents component="input" />
## Styling
@@ -45,7 +52,7 @@ function CustomInput() {
<Label htmlFor="custom-input">Project name</Label>
<Input
id="custom-input"
className="rounded-xl border border-border/70 bg-surface-2 px-4 py-2 text-sm shadow-sm focus-visible:border-primary"
className="rounded-xl border border-border/70 bgsurface px-4 py-2 text-sm shadow-sm focus-visible:border-primary"
placeholder="New web app"
/>
</div>
@@ -60,11 +67,11 @@ The base class `.input` powers every instance. Override it once with `@layer com
```css
@layer components {
.input {
@apply rounded-lg border border-border bg-surface-2 px-4 py-2 text-sm shadow-sm transition-colors;
@apply rounded-lg border border-border bgsurface px-4 py-2 text-sm shadow-sm transition-colors;
&:hover,
&[data-hovered="true"] {
@apply bg-surface-3 border-border/80;
@apply bg-surface-secondary border-border/80;
}
&:focus-visible,
@@ -116,5 +123,6 @@ Input accepts all standard HTML `<input>` attributes plus the following:
| `min` | `number \| string` | - | Minimum value (for number/date inputs). |
| `max` | `number \| string` | - | Maximum value (for number/date inputs). |
| `step` | `number \| string` | - | Stepping interval (for number inputs). |
| `isOnSurface` | `boolean` | `false` | Whether the input is displayed on a surface (affects styling) |
> For validation props like `isInvalid`, `isRequired`, and error handling, use **[TextField](/docs/components/text-field)** with Input as a child component.
+13 -7
View File
@@ -1,11 +1,10 @@
---
title: Kbd
description: Display keyboard shortcuts and key combinations
icon: updated
links:
source: kbd/kbd.tsx
styles: kbd.css
storybook: kbd
storybook: Components/Typography/Kbd
figma: true
---
@@ -29,10 +28,10 @@ Import the Kbd component and access all parts using dot notation.
import { Kbd } from "@heroui/react";
export default () => (
<Kbd.Root>
<Kbd>
<Kbd.Abbr title="Command">⌘</Kbd.Abbr>
<Kbd.Content>K</Kbd.Content>
</Kbd.Root>
</Kbd>
);
```
@@ -60,6 +59,12 @@ export default () => (
name="kbd-special-keys"
/>
### Variants
<ComponentPreview
name="kbd-variants"
/>
## Styling
### Passing Tailwind CSS classes
@@ -69,9 +74,9 @@ import { Kbd } from "@heroui/react";
function CustomKbd() {
return (
<Kbd.Root className="bg-gray-100 dark:bg-gray-800">
<Kbd className="bg-gray-100 dark:bg-gray-800">
<Kbd.Content>K</Kbd.Content>
</Kbd.Root>
</Kbd>
);
}
```
@@ -111,11 +116,12 @@ The Kbd component uses these CSS classes ([View source styles](https://github.co
## API Reference
### Kbd.Root Props
### Kbd Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `children` | `React.ReactNode` | - | Content of the key |
| `variant` | `"default" | "light"` | `default` | Variant of the keyboard key |
| `className` | `string` | - | Custom CSS classes |
### Kbd.Abbr Props
@@ -5,7 +5,6 @@ links:
rac: TextField
source: label/label.tsx
styles: label.css
storybook: label
---
## Import
+95 -8
View File
@@ -1,12 +1,11 @@
---
title: Link
description: A styled anchor component for navigation with built-in icon support
icon: updated
links:
rac: Link
source: link/link.tsx
styles: link.css
storybook: link
storybook: Components/Navigation/Link
figma: true
---
@@ -30,10 +29,10 @@ Import the Link component and access all parts using dot notation.
import { Link } from '@heroui/react';
export default () => (
<Link.Root href="#">
<Link href="#">
Call to action
<Link.Icon />
</Link.Root>
</Link>
);
```
@@ -49,6 +48,81 @@ export default () => (
name="link-icon-placement"
/>
### Underline Variants
Control the underline behavior with the `underline` prop:
<ComponentPreview
name="link-underline-variants"
/>
- `underline="hover"` (default) - Animated underline appears on hover
- `underline="always"` - Underline always visible (50% opacity, 100% on hover)
- `underline="none"` - No underline
### Underline Offset
Adjust the spacing between text and underline with the `underlineOffset` prop:
<ComponentPreview
name="link-underline-offset"
/>
- `underlineOffset={1}` (default) - No space
- `underlineOffset={2}` - 2px spacing
- `underlineOffset={3}` - 4px spacing
### Using with Routing Libraries
Use the `asChild` prop to compose Link with framework-specific links like Next.js:
```tsx
import { Link } from '@heroui/react';
import NextLink from 'next/link';
export default function Demo() {
return (
<Link asChild underline="hover">
<NextLink href="/about">
About Page
<Link.Icon />
</NextLink>
</Link>
);
}
```
### Direct Class Application
Since HeroUI uses BEM classes, you can apply Link styles directly to any link element without using `asChild`:
```tsx
import NextLink from 'next/link';
// Apply classes directly
export default function Demo() {
return (
<NextLink href="/about" className="link link--underline-hover link--offset-1">
About Page
</NextLink>
);
}
// Or with a native anchor
export default function NativeLink() {
return (
<a href="/about" className="link link--underline-always link--offset-2">
About Page
</a>
);
}
```
Available BEM classes:
- Base: `link`
- Underline: `link--underline-none`, `link--underline-hover`, `link--underline-always`
- Offset: `link--offset-1`, `link--offset-2`, `link--offset-3`
## Styling
### Passing Tailwind CSS classes
@@ -58,12 +132,12 @@ import { Link } from '@heroui/react';
function CustomLink() {
return (
<Link.Root
<Link
href="#"
className="text-lg font-bold text-accent hover:text-accent/80"
>
Custom styled link
</Link.Root>
</Link>
);
}
```
@@ -88,7 +162,17 @@ HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component va
The Link component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/link.css)):
#### Base Classes
- `.link` - Base link styles with underline on hover
- `.link` - Base link styles
#### Underline Variants
- `.link--underline-none` - No underline
- `.link--underline-hover` - Animated underline on hover (default)
- `.link--underline-always` - Always visible underline
#### Underline Offset
- `.link--offset-1` - No spacing (default)
- `.link--offset-2` - 2px spacing
- `.link--offset-3` - 4px spacing
### Interactive States
@@ -99,7 +183,7 @@ The component supports both CSS pseudo-classes and data attributes for flexibili
## API Reference
### Link.Root Props
### Link Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
@@ -107,6 +191,9 @@ The component supports both CSS pseudo-classes and data attributes for flexibili
| `target` | `string` | `"_self"` | Controls where to open the linked document |
| `rel` | `string` | - | Relationship between the current and linked documents |
| `download` | `boolean \| string` | - | Prompts file download instead of navigation |
| `underline` | `"none" \| "hover" \| "always"` | `"hover"` | Controls underline visibility and behavior |
| `underlineOffset` | `1 \| 2 \| 3` | `1` | Spacing between text and underline |
| `asChild` | `boolean` | `false` | Merge props with child element (useful for routing libraries) |
| `isDisabled` | `boolean` | `false` | Disables pointer and keyboard interaction |
| `className` | `string` | - | Custom classes merged with the default styles |
| `children` | `React.ReactNode` | - | Content rendered inside the link |
@@ -0,0 +1,301 @@
---
title: ListBox
description: A listbox displays a list of options and allows a user to select one or more of them
icon: new
links:
rac: ListBox
source: listbox/listbox.tsx
styles: listbox.css
storybook: Components/Collections/ListBox
---
## Import
```tsx
import { ListBox } from '@heroui/react';
```
### Usage
<ComponentPreview
name="listbox-default"
/>
### Anatomy
Import the ListBox component and access all parts using dot notation.
```tsx
import { ListBox, Label, Description, Header } from '@heroui/react';
export default () => (
<ListBox>
<ListBox.Item>
<Label />
<Description />
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Section>
<Header />
<ListBox.Item>
<Label />
</ListBox.Item>
</ListBox.Section>
</ListBox>
)
```
### With Sections
<ComponentPreview
name="listbox-with-sections"
/>
### Multi Select
<ComponentPreview
name="listbox-multi-select"
/>
### With Disabled Items
<ComponentPreview
name="listbox-with-disabled-items"
/>
### Custom Check Icon
<ComponentPreview
name="listbox-custom-check-icon"
/>
### Controlled
<ComponentPreview
name="listbox-controlled"
/>
<RelatedComponents component="listbox" />
## Styling
### Passing Tailwind CSS classes
```tsx
import { ListBox } from '@heroui/react';
function CustomListBox() {
return (
<ListBox className="border rounded-lg p-2 bg-surface">
<ListBox.Item id="1" textValue="Item 1" className="hover:bg-surface-secondary">
Item 1
</ListBox.Item>
</ListBox>
);
}
```
### Customizing the component classes
To customize the ListBox component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.listbox {
@apply rounded-lg border border-border bg-surface p-2;
}
.listbox-item {
@apply rounded px-2 py-1 cursor-pointer;
}
.listbox-item--danger {
@apply text-danger;
}
.listbox-item__indicator {
@apply text-accent;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The ListBox component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/listbox.css)):
#### Base Classes
- `.listbox` - Base listbox container
- `.listbox-item` - Individual listbox item
- `.listbox-item__indicator` - Selection indicator icon
- `.listbox-section` - Section container for grouping items
#### Variant Classes
- `.listbox--default` - Default variant styling
- `.listbox--danger` - Danger variant styling
- `.listbox-item--default` - Default item variant
- `.listbox-item--danger` - Danger item variant
#### State Classes
- `.listbox-item[data-selected="true"]` - Selected item state
- `.listbox-item[data-focus-visible="true"]` - Focused item state
- `.listbox-item[data-disabled="true"]` - Disabled item state
- `.listbox-item__indicator[data-visible="true"]` - Visible indicator state
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- **Hover**: `:hover` or `[data-hovered="true"]` on item
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on item
- **Selected**: `[data-selected="true"]` on item
- **Disabled**: `:disabled` or `[data-disabled="true"]` on item
## API Reference
### ListBox Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `aria-label` | `string` | - | Accessibility label for the listbox |
| `aria-labelledby` | `string` | - | ID of element that labels the listbox |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"single"` | Selection behavior |
| `selectedKeys` | `Selection` | - | Controlled selected keys |
| `defaultSelectedKeys` | `Selection` | - | Initial selected keys |
| `onSelectionChange` | `(keys: Selection) => void` | - | Handler called when selection changes |
| `disabledKeys` | `Iterable<Key>` | - | Keys of disabled items |
| `onAction` | `(key: Key) => void` | - | Handler called when an item is activated |
| `variant` | `"default" \| "danger"` | `"default"` | Visual variant |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | ListBox items and sections |
### ListBox.Item Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `id` | `Key` | - | Unique identifier for the item |
| `textValue` | `string` | - | Text value for accessibility and typeahead |
| `isDisabled` | `boolean` | `false` | Whether this item is disabled |
| `variant` | `"default" \| "danger"` | `"default"` | Visual variant |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Item content or render function |
### ListBox.ItemIndicator Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Custom indicator content or render function |
### ListBox.Section Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Section content including Header and Items |
### RenderProps
When using render functions with ListBox.Item or ListBox.ItemIndicator, these values are provided:
| Prop | Type | Description |
|------|------|-------------|
| `isSelected` | `boolean` | Whether the item is selected |
| `isFocused` | `boolean` | Whether the item is focused |
| `isDisabled` | `boolean` | Whether the item is disabled |
| `isPressed` | `boolean` | Whether the item is being pressed |
## Examples
### Basic Usage
```tsx
import { ListBox, Label, Description } from '@heroui/react';
<ListBox aria-label="Users" selectionMode="single">
<ListBox.Item id="1" textValue="Bob">
<Label>Bob</Label>
<Description>bob@heroui.com</Description>
</ListBox.Item>
<ListBox.Item id="2" textValue="Alice">
<Label>Alice</Label>
<Description>alice@heroui.com</Description>
</ListBox.Item>
</ListBox>
```
### With Sections
```tsx
import { ListBox, Header, Separator } from '@heroui/react';
<ListBox aria-label="Actions" selectionMode="none" onAction={(key) => console.log(key)}>
<ListBox.Section>
<Header>Actions</Header>
<ListBox.Item id="new" textValue="New file">New file</ListBox.Item>
<ListBox.Item id="edit" textValue="Edit file">Edit file</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Danger zone</Header>
<ListBox.Item id="delete" textValue="Delete" variant="danger">Delete</ListBox.Item>
</ListBox.Section>
</ListBox>
```
### Controlled Selection
```tsx
import { ListBox, Selection } from '@heroui/react';
import { useState } from 'react';
function ControlledListBox() {
const [selected, setSelected] = useState<Selection>(new Set(["1"]));
return (
<ListBox
aria-label="Options"
selectedKeys={selected}
selectionMode="multiple"
onSelectionChange={setSelected}
>
<ListBox.Item id="1" textValue="Option 1">Option 1</ListBox.Item>
<ListBox.Item id="2" textValue="Option 2">Option 2</ListBox.Item>
<ListBox.Item id="3" textValue="Option 3">Option 3</ListBox.Item>
</ListBox>
);
}
```
### Custom Indicator
```tsx
import { ListBox, ListBoxItemIndicator } from '@heroui/react';
import { Icon } from '@iconify/react';
<ListBox aria-label="Options" selectionMode="multiple">
<ListBox.Item id="1" textValue="Option 1">
Option 1
<ListBox.ItemIndicator>
{({isSelected}) =>
isSelected ? <Icon icon="gravity-ui:check" /> : null
}
</ListBox.ItemIndicator>
</ListBox.Item>
</ListBox>
```
## Accessibility
The ListBox component implements the ARIA listbox pattern and provides:
- Full keyboard navigation support
- Screen reader announcements for selection changes
- Proper focus management
- Support for disabled states
- Typeahead search functionality
For more information, see the [React Aria ListBox documentation](https://react-spectrum.adobe.com/react-aria/ListBox.html).
@@ -1,12 +1,11 @@
---
title: Popover
description: Displays rich content in a portal triggered by a button or any custom element
icon: updated
links:
rac: Popover
source: popover/popover.tsx
styles: popover.css
storybook: popover
storybook: Components/Overlays/Popover
---
## Import
@@ -29,7 +28,7 @@ Import the Popover component and access all parts using dot notation.
import { Popover } from '@heroui/react';
export default () => (
<Popover.Root>
<Popover>
<Popover.Trigger/>
<Popover.Content>
<Popover.Arrow />
@@ -38,7 +37,7 @@ export default () => (
{/* content goes here */}
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
</Popover>
)
```
@@ -69,7 +68,7 @@ import { Popover, Button } from '@heroui/react';
function CustomPopover() {
return (
<Popover.Root>
<Popover>
<Popover.Trigger>
<Button>Open</Button>
</Popover.Trigger>
@@ -79,7 +78,7 @@ function CustomPopover() {
<p>This popover has custom styling</p>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
</Popover>
);
}
```
@@ -128,7 +127,7 @@ The component supports animation states:
## API Reference
### Popover.Root Props
### Popover Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
@@ -6,7 +6,7 @@ links:
rac: RadioGroup
source: radio-group/radio-group.tsx
styles: radio-group.css
storybook: radio-group
storybook: Components/Forms/RadioGroup
---
## Import
@@ -33,7 +33,7 @@ export default () => (
<RadioGroup>
<Label />
<Description />
<Radio.Root value="option1">
<Radio value="option1">
<Radio.Control>
<Radio.Indicator>
<span>✓</span> {/* Custom indicator (optional) */}
@@ -43,7 +43,7 @@ export default () => (
<Label />
<Description />
</Radio.Content>
</Radio.Root>
</Radio>
<FieldError />
</RadioGroup>
)
@@ -87,6 +87,14 @@ Combine `defaultValue` with `onChange` when you only need to react to updates.
name="radio-group-disabled"
/>
### On Surface
When used inside a [Surface](/docs/components/surface) component, RadioGroup automatically applies on-surface styling.
<ComponentPreview
name="radio-group-on-surface"
/>
### Delivery & Payment
<ComponentPreview
@@ -107,27 +115,27 @@ import { RadioGroup, Radio } from '@heroui/react';
export default () => (
<RadioGroup defaultValue="premium" name="plan">
<Radio.Root
<Radio
className="border-border group cursor-pointer rounded-xl border-2 p-4 hover:border-blue-300 data-[selected=true]:border-blue-500 data-[selected=true]:bg-blue-500/10"
value="basic"
>
<Radio.Indicator className="border-border border-2 group-hover:border-blue-400 group-data-[selected=true]:border-blue-500 group-data-[selected=true]:bg-blue-500" />
Basic Plan
</Radio.Root>
<Radio.Root
</Radio>
<Radio
className="border-border group cursor-pointer rounded-xl border-2 p-4 hover:border-purple-300 data-[selected=true]:border-purple-500 data-[selected=true]:bg-purple-500/10"
value="premium"
>
<Radio.Indicator className="border-border border-2 group-hover:border-purple-400 group-data-[selected=true]:border-purple-500 group-data-[selected=true]:bg-purple-500" />
Premium Plan
</Radio.Root>
<Radio.Root
</Radio>
<Radio
className="border-border group cursor-pointer rounded-xl border-2 p-4 hover:border-emerald-300 data-[selected=true]:border-emerald-500 data-[selected=true]:bg-emerald-500/10"
value="business"
>
<Radio.Indicator className="border-border border-2 group-hover:border-emerald-400 group-data-[selected=true]:border-emerald-500 group-data-[selected=true]:bg-emerald-500" />
Business Plan
</Radio.Root>
</Radio>
</RadioGroup>
);
```
@@ -201,11 +209,12 @@ The radio supports both CSS pseudo-classes and data attributes for flexibility:
| `isRequired` | `boolean` | `false` | Whether the radio group is required |
| `isReadOnly` | `boolean` | `false` | Whether the radio group is read only |
| `isInvalid` | `boolean` | `false` | Whether the radio group is in an invalid state |
| `isOnSurface` | `boolean` | `false` | Whether the radio group is displayed on a surface (affects styling) |
| `name` | `string` | - | The name of the radio group, used when submitting an HTML form |
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | The orientation of the radio group |
| `children` | `React.ReactNode \| (values: RadioGroupRenderProps) => React.ReactNode` | - | Radio group content or render prop |
### Radio.Root Props
### Radio Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
@@ -0,0 +1,445 @@
---
title: Select
description: A select displays a collapsible list of options and allows a user to select one of them
icon: new
links:
rac: Select
source: select/select.tsx
styles: select.css
storybook: Components/Pickers/Select
---
## Import
```tsx
import { Select } from '@heroui/react';
```
### Usage
<ComponentPreview
name="select-default"
/>
### Anatomy
Import the Select component and access all parts using dot notation.
```tsx
import { Select, Label, Description, Header, ListBox, Separator } from '@heroui/react';
export default () => (
<Select>
<Label />
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Description />
<Select.Content>
<ListBox>
<ListBox.Item>
<Label />
<Description />
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Section>
<Header />
<ListBox.Item>
<Label />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</Select.Content>
</Select>
)
```
### With Description
<ComponentPreview
name="select-with-description"
/>
### Multiple Select
<ComponentPreview
name="select-multiple-select"
/>
### With Sections
<ComponentPreview
name="select-with-sections"
/>
### With Disabled Options
<ComponentPreview
name="select-with-disabled-options"
/>
### Custom Indicator
<ComponentPreview
name="select-custom-indicator"
/>
### Required
<ComponentPreview
name="select-required"
/>
### On Surface
<ComponentPreview
name="select-on-surface"
/>
### Custom Value
<ComponentPreview
name="select-custom-value"
/>
### Controlled
<ComponentPreview
name="select-controlled"
/>
### Controlled Multiple
<ComponentPreview
name="select-controlled-multiple"
/>
### Controlled Open State
<ComponentPreview
name="select-controlled-open-state"
/>
### Asynchronous Loading
<ComponentPreview
name="select-asynchronous-loading"
/>
### Disabled
<ComponentPreview
name="select-disabled"
/>
<RelatedComponents component="select" />
## Styling
### Passing Tailwind CSS classes
```tsx
import { Select } from '@heroui/react';
function CustomSelect() {
return (
<Select className="w-full">
<Label>State</Label>
<Select.Trigger className="border rounded-lg p-2 bg-surface">
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Content>
<ListBox>
<ListBox.Item id="1" textValue="Item 1" className="hover:bg-surface-secondary">
Item 1
</ListBox.Item>
</ListBox>
</Select.Content>
</Select>
);
}
```
### Customizing the component classes
To customize the Select component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.select {
@apply flex flex-col gap-1;
}
.select__trigger {
@apply rounded-lg border border-border bg-surface p-2;
}
.select__value {
@apply text-current;
}
.select__indicator {
@apply text-muted;
}
.select__content {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Select component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/select.css)):
#### Base Classes
- `.select` - Base select container
- `.select__trigger` - The button that triggers the select
- `.select__value` - The displayed value or placeholder
- `.select__indicator` - The dropdown indicator icon
- `.select__content` - The popover content container
#### Variant Classes
- `.select__trigger--on-surface` - On surface variant styling
#### State Classes
- `.select[data-invalid="true"]` - Invalid state
- `.select__trigger[data-focus-visible="true"]` - Focused trigger state
- `.select__trigger[data-disabled="true"]` - Disabled trigger state
- `.select__value[data-placeholder="true"]` - Placeholder state
- `.select__indicator[data-open="true"]` - Open indicator state
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- **Hover**: `:hover` or `[data-hovered="true"]` on trigger
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on trigger
- **Disabled**: `:disabled` or `[data-disabled="true"]` on select
- **Open**: `[data-open="true"]` on indicator
## API Reference
### Select Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `placeholder` | `string` | `'Select an item'` | Temporary text that occupies the select when it is empty |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | Whether single or multiple selection is enabled |
| `isOpen` | `boolean` | - | Sets the open state of the menu (controlled) |
| `defaultOpen` | `boolean` | - | Sets the default open state of the menu (uncontrolled) |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Handler called when the open state changes |
| `disabledKeys` | `Iterable<Key>` | - | Keys of disabled items |
| `isDisabled` | `boolean` | - | Whether the select is disabled |
| `value` | `Key \| Key[] \| null` | - | Current value (controlled) |
| `defaultValue` | `Key \| Key[] \| null` | - | Default value (uncontrolled) |
| `onChange` | `(value: Key \| Key[] \| null) => void` | - | Handler called when the value changes |
| `isRequired` | `boolean` | - | Whether user input is required |
| `isInvalid` | `boolean` | - | Whether the select value is invalid |
| `name` | `string` | - | The name of the input, used when submitting an HTML form |
| `autoComplete` | `string` | - | Describes the type of autocomplete functionality |
| `isOnSurface` | `boolean` | - | Whether the select is displayed on a surface component |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Select content or render function |
### Select.Trigger Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Trigger content or render function |
### Select.Value Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Value content or render function |
### Select.Indicator Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator content |
### Select.Content Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | Placement of the popover relative to the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content children |
### RenderProps
When using render functions with Select.Value, these values are provided:
| Prop | Type | Description |
|------|------|-------------|
| `defaultChildren` | `ReactNode` | The default rendered value |
| `isPlaceholder` | `boolean` | Whether the value is a placeholder |
| `state` | `SelectState` | The state of the select |
| `selectedItems` | `Node[]` | The currently selected items |
## Examples
### Basic Usage
```tsx
import { Select, Label, ListBox } from '@heroui/react';
<Select className="w-[256px]" placeholder="Select one">
<Label>State</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Content>
<ListBox>
<ListBox.Item id="florida" textValue="Florida">
Florida
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="california" textValue="California">
California
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Content>
</Select>
```
### With Sections
```tsx
import { Select, Label, ListBox, Header, Separator } from '@heroui/react';
<Select className="w-[256px]" placeholder="Select a country">
<Label>Country</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Content>
<ListBox>
<ListBox.Section>
<Header>North America</Header>
<ListBox.Item id="usa" textValue="United States">
United States
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Europe</Header>
<ListBox.Item id="uk" textValue="United Kingdom">
United Kingdom
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</Select.Content>
</Select>
```
### Controlled Selection
```tsx
import type { Key } from '@heroui/react';
import { Select, Label, ListBox } from '@heroui/react';
import { useState } from 'react';
function ControlledSelect() {
const [value, setValue] = useState<Key | null>('california');
return (
<Select
className="w-[256px]"
placeholder="Select a state"
value={value}
onChange={setValue}
>
<Label>State</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Content>
<ListBox>
<ListBox.Item id="california" textValue="California">
California
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="texas" textValue="Texas">
Texas
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Content>
</Select>
);
}
```
### Custom Value Display
```tsx
import { Select, Label, ListBox, Avatar, AvatarImage, AvatarFallback } from '@heroui/react';
<Select className="w-[256px]" placeholder="Select a user">
<Label>User</Label>
<Select.Trigger>
<Select.Value>
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItem = users.find((user) => user.id === state.selectedItems[0].key);
if (!selectedItem) {
return defaultChildren;
}
return (
<div className="flex items-center gap-2">
<Avatar className="size-4" size="sm">
<AvatarImage src={selectedItem.avatarUrl} />
<AvatarFallback>{selectedItem.fallback}</AvatarFallback>
</Avatar>
<span>{selectedItem.name}</span>
</div>
);
}}
</Select.Value>
<Select.Indicator />
</Select.Trigger>
<Select.Content>
<ListBox>
{/* ListBox items */}
</ListBox>
</Select.Content>
</Select>
```
## Accessibility
The Select component implements the ARIA listbox pattern and provides:
- Full keyboard navigation support
- Screen reader announcements for selection changes
- Proper focus management
- Support for disabled states
- Typeahead search functionality
- HTML form integration
For more information, see the [React Aria Select documentation](https://react-spectrum.adobe.com/react-aria/Select.html).
@@ -5,7 +5,7 @@ links:
rac: Separator
source: separator/separator.tsx
styles: separator.css
storybook: separator
storybook: Components/Layout/Separator
figma: true
---
+17 -3
View File
@@ -4,7 +4,7 @@ description: Skeleton is a placeholder to show a loading state and the expected
links:
source: skeleton/skeleton.tsx
styles: skeleton.css
storybook: skeleton
storybook: Components/Feedback/Skeleton
figma: true
---
@@ -45,6 +45,20 @@ import { Skeleton } from '@heroui/react';
name="skeleton-animation-types"
/>
### Grid
<ComponentPreview
name="skeleton-grid"
/>
### Single Shimmer
A synchronized shimmer effect that passes over all skeleton elements at once. Apply the `skeleton--shimmer` class to a parent container and set `animationType="none"` on child skeletons.
<ComponentPreview
name="skeleton-single-shimmer"
/>
## Styling
### Global Animation Configuration
@@ -91,12 +105,12 @@ To customize the Skeleton component classes, you can use the `@layer components`
@layer components {
/* Base skeleton styles */
.skeleton {
@apply bg-surface-3/50; /* Change base background */
@apply bg-surface-secondary/50; /* Change base background */
}
/* Shimmer animation gradient */
.skeleton--shimmer:before {
@apply via-surface-2; /* Change shimmer gradient color */
@apply viasurface; /* Change shimmer gradient color */
}
/* Pulse animation */
@@ -0,0 +1,371 @@
---
title: Slider
description: A slider allows a user to select one or more values within a range
icon: new
links:
rac: Slider
source: slider/slider.tsx
styles: slider.css
storybook: Components/Controls/Slider
---
## Import
```tsx
import { Slider } from '@heroui/react';
```
### Usage
<ComponentPreview
name="slider-default"
/>
### Anatomy
Import the Slider component and access all parts using dot notation.
```tsx
import { Slider, Label } from '@heroui/react';
export default () => (
<Slider>
<Label />
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>
)
```
### Range Slider Anatomy
```tsx
import { Slider, Label } from '@heroui/react';
export default () => (
<Slider defaultValue={[25, 75]}>
<Label />
<Slider.Output />
<Slider.Track>
{({state}) => (
<>
<Slider.Fill />
{state.values.map((_, i) => (
<Slider.Thumb key={i} index={i} />
))}
</>
)}
</Slider.Track>
</Slider>
)
```
### Vertical
<ComponentPreview
name="slider-vertical"
/>
### Range
<ComponentPreview
name="slider-range"
/>
### Disabled
<ComponentPreview
name="slider-disabled"
/>
<RelatedComponents component="slider" />
## Styling
### Passing Tailwind CSS classes
```tsx
import { Slider, Label } from '@heroui/react';
function CustomSlider() {
return (
<Slider className="w-full">
<Label>Volume</Label>
<Slider.Output className="text-muted-fg text-sm" />
<Slider.Track className="h-2 rounded-full bg-surface-secondary">
<Slider.Fill className="bg-accent" />
<Slider.Thumb className="size-4 rounded-full bg-accent" />
</Slider.Track>
</Slider>
);
}
```
### Customizing the component classes
To customize the Slider component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.slider {
@apply flex flex-col gap-2;
}
.slider__output {
@apply text-muted-fg text-sm;
}
.slider-track {
@apply relative h-2 w-full rounded-full bg-surface-secondary;
}
.slider-fill {
@apply absolute h-full rounded-full bg-accent;
}
.slider-thumb {
@apply size-4 rounded-full bg-accent border-2 border-background;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Slider component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/slider.css)):
#### Base Classes
- `.slider` - Base slider container
- `.slider__output` - Output element displaying current value(s)
- `.slider-track` - Track element containing fill and thumbs
- `.slider-fill` - Fill element showing selected range
- `.slider-thumb` - Individual thumb element
#### State Classes
- `.slider[data-disabled="true"]` - Disabled slider state
- `.slider[data-orientation="vertical"]` - Vertical orientation
- `.slider-thumb[data-dragging="true"]` - Thumb being dragged
- `.slider-thumb[data-focus-visible="true"]` - Thumb keyboard focused
- `.slider-thumb[data-disabled="true"]` - Disabled thumb state
- `.slider-track[data-fill-start="true"]` - Fill starts at beginning
- `.slider-track[data-fill-end="true"]` - Fill ends at end
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- **Hover**: `:hover` or `[data-hovered="true"]` on thumb
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on thumb
- **Dragging**: `[data-dragging="true"]` on thumb
- **Disabled**: `:disabled` or `[data-disabled="true"]` on slider or thumb
## API Reference
### Slider Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | `number \| number[]` | - | The current value (controlled) |
| `defaultValue` | `number \| number[]` | - | The default value (uncontrolled) |
| `onChange` | `(value: number \| number[]) => void` | - | Handler called when the value changes |
| `onChangeEnd` | `(value: number \| number[]) => void` | - | Handler called when dragging ends |
| `minValue` | `number` | `0` | The slider's minimum value |
| `maxValue` | `number` | `100` | The slider's maximum value |
| `step` | `number` | `1` | The slider's step value |
| `formatOptions` | `Intl.NumberFormatOptions` | - | The display format of the value label |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the slider |
| `isDisabled` | `boolean` | - | Whether the slider is disabled |
| `aria-label` | `string` | - | Accessibility label for the slider |
| `aria-labelledby` | `string` | - | ID of element that labels the slider |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Slider content or render function |
### Slider.Output Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Output content or render function |
### Slider.Track Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Track content or render function |
### Slider.Fill Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `style` | `CSSProperties` | - | Inline styles |
### Slider.Thumb Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `index` | `number` | `0` | Index of the thumb within the slider |
| `isDisabled` | `boolean` | - | Whether this thumb is disabled |
| `name` | `string` | - | The name of the input element, used when submitting an HTML form |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Thumb content or render function |
### RenderProps
When using render functions with Slider.Output or Slider.Track, these values are provided:
| Prop | Type | Description |
|------|------|-------------|
| `state` | `SliderState` | The state of the slider |
| `values` | `number[]` | Values managed by the slider by thumb index |
| `getThumbValueLabel` | `(index: number) => string` | Returns the string label for the specified thumb's value |
| `orientation` | `"horizontal" \| "vertical"` | The orientation of the slider |
| `isDisabled` | `boolean` | Whether the slider is disabled |
## Examples
### Basic Usage
```tsx
import { Slider, Label } from '@heroui/react';
<Slider defaultValue={30}>
<Label>Volume</Label>
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>
```
### Range Slider
```tsx
import { Slider, Label } from '@heroui/react';
<Slider
defaultValue={[100, 500]}
formatOptions={{style: "currency", currency: "USD"}}
maxValue={1000}
minValue={0}
step={50}
>
<Label>Price Range</Label>
<Slider.Output />
<Slider.Track>
{({state}) => (
<>
<Slider.Fill />
{state.values.map((_, i) => (
<Slider.Thumb key={i} index={i} />
))}
</>
)}
</Slider.Track>
</Slider>
```
### Controlled Value
```tsx
import { Slider, Label } from '@heroui/react';
import { useState } from 'react';
function ControlledSlider() {
const [value, setValue] = useState(25);
return (
<>
<Slider value={value} onChange={setValue}>
<Label>Volume</Label>
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>
<p>Current value: {value}</p>
</>
);
}
```
### Custom Value Formatting
```tsx
import { Slider, Label } from '@heroui/react';
<Slider
defaultValue={60}
formatOptions={{style: "currency", currency: "USD"}}
>
<Label>Price</Label>
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>
```
### Vertical Orientation
```tsx
import { Slider, Label } from '@heroui/react';
<Slider defaultValue={30} orientation="vertical" aria-label="Volume">
<Label>Volume</Label>
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>
```
### Custom Output Display
```tsx
import { Slider, Label } from '@heroui/react';
<Slider defaultValue={[25, 75]}>
<Label>Range</Label>
<Slider.Output>
{({state}) =>
state.values.map((_, i) => state.getThumbValueLabel(i)).join(' ')
}
</Slider.Output>
<Slider.Track>
{({state}) => (
<>
<Slider.Fill />
{state.values.map((_, i) => (
<Slider.Thumb key={i} index={i} />
))}
</>
)}
</Slider.Track>
</Slider>
```
## Accessibility
The Slider component implements the ARIA slider pattern and provides:
- Full keyboard navigation support (Arrow keys, Home, End, Page Up/Down)
- Screen reader announcements for value changes
- Proper focus management
- Support for disabled states
- HTML form integration via hidden input elements
- Internationalization support with locale-aware value formatting
- Right-to-left (RTL) language support
For more information, see the [React Aria Slider documentation](https://react-spectrum.adobe.com/react-aria/Slider.html).
@@ -4,7 +4,7 @@ description: A loading indicator component to show pending states
links:
source: spinner/spinner.tsx
styles: spinner.css
storybook: spinner
storybook: Components/Feedback/Spinner
figma: true
---
@@ -0,0 +1,145 @@
---
title: Surface
description: Container component that provides surface-level styling and context for child components
icon: new
links:
source: surface/surface.tsx
styles: surface.css
storybook: surface
---
## Import
```tsx
import { Surface } from '@heroui/react';
```
### Usage
<ComponentPreview
name="surface-variants"
/>
## Overview
The Surface component is a semantic container that provides different levels of visual prominence through variants. It also exposes a context that child components (like Input, TextArea, RadioGroup, and InputOTP) can use to automatically apply on-surface styling.
### Variants
Surface comes in semantic variants that describe their prominence level:
- **`default`** - Standard surface appearance (bg-surface)
- **`secondary`** - Medium prominence (bg-surface-secondary)
- **`tertiary`** - Higher prominence (bg-surface-tertiary)
- **`quaternary`** - Highest prominence (bg-surface-quaternary)
## Context
The Surface component provides a `SurfaceContext` that child components can access via `useContext(SurfaceContext)`. Form components like Input, TextArea, RadioGroup, and InputOTP automatically detect when they're inside a Surface and apply the appropriate on-surface styling.
```tsx
import { Surface, Input, TextArea } from '@heroui/react';
import { useContext } from 'react';
import { SurfaceContext } from '@heroui/react';
function MyComponent() {
const surfaceContext = useContext(SurfaceContext);
// Access the variant if needed
const variant = surfaceContext.variant;
return (
<>
<Input placeholder="This input will use on-surface styling" />
<TextArea placeholder="This textarea will use on-surface styling" />
</>
);
}
function App() {
return (
<Surface variant="default">
<MyComponent />
</Surface>
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import { Surface } from '@heroui/react';
function CustomSurface() {
return (
<Surface
className="rounded-2xl p-8 shadow-lg"
variant="secondary"
>
<h2>Custom Styled Surface</h2>
<p>Content goes here</p>
</Surface>
);
}
```
### Customizing the component classes
To customize the Surface component classes, you can use the `@layer components` directive.
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.surface {
@apply rounded-2xl border border-border;
}
.surface--secondary {
@apply bg-gradient-to-br from-blue-50 to-purple-50;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Surface component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/surface.css)):
#### Base Classes
- `.surface` - Base surface container
#### Variant Classes
- `.surface--default` - Default surface variant (bg-surface)
- `.surface--secondary` - Secondary surface variant (bg-surface-secondary)
- `.surface--tertiary` - Tertiary surface variant (bg-surface-tertiary)
- `.surface--quaternary` - Quaternary surface variant (bg-surface-quaternary)
## API Reference
### Surface Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `"default" \| "secondary" \| "tertiary" \| "quaternary"` | `"default"` | The visual variant of the surface |
| `asChild` | `boolean` | `false` | Merge props onto the child element |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The surface content |
## Context API
### SurfaceContext
Child components can access the Surface context to get the current variant:
```tsx
import { useContext } from 'react';
import { SurfaceContext } from '@heroui/react';
function MyComponent() {
const { variant } = useContext(SurfaceContext);
// variant will be "default" | "secondary" | "tertiary" | "quaternary" | undefined
}
```
+14 -15
View File
@@ -1,12 +1,11 @@
---
title: Switch
description: A toggle switch component for boolean states
icon: updated
links:
rac: Switch
source: switch/switch.tsx
styles: switch.css
storybook: switch
storybook: Components/Controls/Switch
figma: true
---
@@ -30,14 +29,14 @@ Import the Switch component and access all parts using dot notation.
import { Switch, Label } from '@heroui/react';
export default () => (
<Switch.Root>
<Switch>
<Switch.Control>
<Switch.Thumb>
<Switch.Icon/> {/* Optional */}
</Switch.Thumb>
</Switch.Control>
<Label/> {/* Optional */}
</Switch.Root>
</Switch>
);
```
@@ -48,18 +47,18 @@ import { Switch, SwitchGroup, Label } from '@heroui/react';
export default () => (
<SwitchGroup>
<Switch.Root>
<Switch>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Label>Option 1</Label>
</Switch.Root>
<Switch.Root>
</Switch>
<Switch>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Label>Option 2</Label>
</Switch.Root>
</Switch>
</SwitchGroup>
);
```
@@ -153,7 +152,7 @@ import { Switch, Label } from '@heroui/react';
function CustomSwitch() {
return (
<Switch.Root>
<Switch>
{({isSelected}) => (
<>
<Switch.Control
@@ -166,7 +165,7 @@ function CustomSwitch() {
<Label>Custom Switch</Label>
</>
)}
</Switch.Root>
</Switch>
);
}
```
@@ -179,18 +178,18 @@ import { Switch, SwitchGroup, Label } from '@heroui/react';
function CustomSwitchGroup() {
return (
<SwitchGroup className="gap-8" orientation="horizontal">
<Switch.Root>
<Switch>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Label>Option 1</Label>
</Switch.Root>
<Switch.Root>
</Switch>
<Switch>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Label>Option 2</Label>
</Switch.Root>
</Switch>
</SwitchGroup>
);
}
@@ -258,7 +257,7 @@ The switch supports both CSS pseudo-classes and data attributes for flexibility:
## API Reference
### Switch.Root Props
### Switch Props
Inherits from [React Aria Switch](https://react-spectrum.adobe.com/react-aria/Switch.html).
+11 -12
View File
@@ -1,12 +1,11 @@
---
title: Tabs
description: Tabs organize content into multiple sections and allow users to navigate between them.
icon: updated
links:
rac: Tabs
source: tabs/tabs.tsx
styles: tabs.css
storybook: tabs
storybook: Components/Navigation/Tabs
figma: true
---
@@ -31,16 +30,16 @@ Import the Tabs component and access all parts using dot notation.
import { Tabs } from '@heroui/react';
export default () => (
<Tabs.Root>
<Tabs.ListWrapper>
<Tabs>
<Tabs.ListContainer>
<Tabs.List aria-label="Options">
<Tabs.Tab>
<Tabs.Indicator />
</Tabs.Tab>
</Tabs.List>
</Tabs.ListWrapper>
</Tabs.ListContainer>
<Tabs.Panel/>
</Tabs.Root>
</Tabs>
)
```
@@ -76,8 +75,8 @@ import { Tabs } from '@heroui/react';
function CustomTabs() {
return (
<Tabs.Root className="w-full max-w-lg text-center">
<Tabs.ListWrapper>
<Tabs className="w-full max-w-lg text-center">
<Tabs.ListContainer>
<Tabs.List
aria-label="Options"
className="*:data-[selected=true]:text-accent-foreground w-fit *:h-6 *:w-fit *:px-3 *:text-sm *:font-normal"
@@ -87,7 +86,7 @@ function CustomTabs() {
<Tabs.Tab id="bi-weekly">Bi-Weekly<Tabs.Indicator /></Tabs.Tab>
<Tabs.Tab id="monthly">Monthly<Tabs.Indicator /></Tabs.Tab>
</Tabs.List>
</Tabs.ListWrapper>
</Tabs.ListContainer>
<Tabs.Panel className="px-4" id="daily">
<h3 className="mb-2 font-semibold">Daily</h3>
<p className="text-sm text-gray-600">Manage your daily tasks and goals.</p>
@@ -104,7 +103,7 @@ function CustomTabs() {
<h3 className="mb-2 font-semibold">Monthly</h3>
<p className="text-sm text-gray-600">Manage your monthly tasks and goals.</p>
</Tabs.Panel>
</Tabs.Root>
</Tabs>
);
}
`} />
@@ -115,7 +114,7 @@ The Tabs component uses these CSS classes:
#### Base Classes
- `.tabs` - Base tabs container
- `.tabs__list-wrapper` - Tab list wrapper
- `.tabs__list-container` - Tab list container wrapper
- `.tabs__list` - Tab list container
- `.tabs__tab` - Individual tab button
- `.tabs__panel` - Tab panel content
@@ -138,7 +137,7 @@ The component supports both CSS pseudo-classes and data attributes:
## API Reference
### Tabs.Root Props
### Tabs Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
@@ -1,11 +1,12 @@
---
title: TextField
description: Composition-friendly text fields with labels, descriptions, and inline validation
icon: updated
links:
rac: TextField
source: text-field/text-field.tsx
styles: text-field.css
storybook: text-field
storybook: Components/Forms/TextField
figma: true
---
@@ -24,16 +25,14 @@ import { TextField } from '@heroui/react';
```tsx
import {TextField, Label, Input, Description, FieldError} from '@heroui/react';
export default function Example() {
return (
<TextField isRequired>
<Label>Display name</Label>
<Input placeholder="Jane" />
<Description>Visible to other team members.</Description>
export default () => (
<TextField>
<Label />
<Input />
<Description />
<FieldError />
</TextField>
);
}
)
```
> **TextField** combines label, input, description, and error into a single accessible component.
@@ -77,6 +76,12 @@ Use [TextArea](/docs/components/textarea) instead of [Input](/docs/components/in
<ComponentPreview name="text-field-input-types" />
### On Surface
When used inside a [Surface](/docs/components/surface) component, TextField and its child Input/TextArea components automatically apply on-surface styling.
<ComponentPreview name="text-field-on-surface" />
<RelatedComponents component="textfield" />
<RelatedShowcases component="TextField" />
@@ -90,11 +95,11 @@ import {TextField, Label, Input, Description} from '@heroui/react';
function CustomTextField() {
return (
<TextField className="gap-2 rounded-xl border border-border/60 bg-surface-2 p-4 shadow-sm">
<TextField className="gap-2 rounded-xl border border-border/60 bgsurface p-4 shadow-sm">
<Label className="text-sm font-semibold text-default-700">
Project name
</Label>
<Input className="rounded-lg border border-border/60 bg-surface-2 px-3 py-2" />
<Input className="rounded-lg border border-border/60 bgsurface px-3 py-2" />
<Description className="text-xs text-default-500">
Keep it short and memorable.
</Description>
+12 -4
View File
@@ -1,11 +1,12 @@
---
title: TextArea
description: Primitive multiline text input component that accepts standard HTML attributes
icon: updated
links:
rac: TextArea
source: textarea/textarea.tsx
styles: textarea.css
storybook: textarea
storybook: Components/Forms/Textarea
---
## Import
@@ -30,6 +31,12 @@ import { TextArea } from '@heroui/react';
<ComponentPreview name="textarea-rows" />
### On Surface
When used inside a [Surface](/docs/components/surface) component, TextArea automatically applies on-surface styling.
<ComponentPreview name="textarea-on-surface" />
<RelatedComponents component="textarea" />
<RelatedShowcases component="TextArea" />
@@ -47,7 +54,7 @@ function CustomTextArea() {
<Label htmlFor="custom-textarea">Message</Label>
<TextArea
id="custom-textarea"
className="rounded-xl border border-border/70 bg-surface-2 px-4 py-3 text-sm leading-6 shadow-sm"
className="rounded-xl border border-border/70 bgsurface px-4 py-3 text-sm leading-6 shadow-sm"
placeholder="Let us know how we can help..."
rows={5}
style={{resize: "vertical"}}
@@ -64,11 +71,11 @@ Override the shared `.textarea` class once with Tailwind's `@layer components`.
```css
@layer components {
.textarea {
@apply rounded-xl border border-border bg-surface-2 px-4 py-3 text-sm leading-6 shadow-sm;
@apply rounded-xl border border-border bgsurface px-4 py-3 text-sm leading-6 shadow-sm;
&:hover,
&[data-hovered="true"] {
@apply bg-surface-3 border-border/80;
@apply bg-surface-secondary border-border/80;
}
&:focus-visible,
@@ -117,5 +124,6 @@ TextArea accepts all standard HTML `<textarea>` attributes plus the following:
| `maxLength` | `number` | - | Maximum number of characters. |
| `minLength` | `number` | - | Minimum number of characters. |
| `wrap` | `'soft' \| 'hard'` | - | How text wraps when submitted. |
| `isOnSurface` | `boolean` | `false` | Whether the textarea is displayed on a surface (affects styling) |
> For validation props like `isInvalid`, `isRequired`, and error handling, use **[TextField](/docs/components/text-field)** with TextArea as a child component.
@@ -1,12 +1,11 @@
---
title: Tooltip
description: Displays informative text when users hover over or focus on an element
icon: updated
links:
rac: Tooltip
source: tooltip/tooltip.tsx
styles: tooltip.css
storybook: tooltip
storybook: Components/Overlays/Tooltip
figma: true
---
@@ -30,7 +29,7 @@ Import the Tooltip component and access all parts using dot notation.
import { Tooltip } from '@heroui/react';
export default () => (
<Tooltip.Root>
<Tooltip>
<Tooltip.Trigger>
<button>Hover for tooltip</button>
</Tooltip.Trigger>
@@ -38,7 +37,7 @@ export default () => (
<Tooltip.Arrow />
Helpful information about this element
</Tooltip.Content>
</Tooltip.Root>
</Tooltip>
)
```
@@ -69,14 +68,14 @@ import { Tooltip, Button } from '@heroui/react';
function CustomTooltip() {
return (
<Tooltip.Root>
<Tooltip>
<Tooltip.Trigger>
<Button>Hover me</Button>
</Tooltip.Trigger>
<Tooltip.Content className="bg-accent text-accent-foreground">
<p>Custom styled tooltip</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip>
);
}
```
@@ -118,7 +117,7 @@ The component supports animation states:
## API Reference
### Tooltip.Root Props
### Tooltip Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
+5 -5
View File
@@ -17,8 +17,8 @@ Components use semantic naming (primary, secondary, tertiary) instead of visual
Inspired by [Uber's Base design system](https://base.uber.com/6d2425e9f/p/756216-button), our variants follow a clear hierarchy:
<DocsImage
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/semantic-intent.jpg"
darkSrc="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/semantic-intent-dark.jpg"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/emphasis.jpg"
darkSrc="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/emphasis-dark.jpg"
alt="Semantic Intent Hierarchy"
/>
@@ -124,10 +124,10 @@ import { Alert, AlertIcon, CardHeader, AccordionTrigger } from '@heroui/react';
<AlertIcon />
</Alert>
// Dot notation (requires .Root)
<Alert.Root>
// Dot notation
<Alert>
<Alert.Icon />
</Alert.Root>
</Alert>
```
### 6. Type Safety First
+27 -4
View File
@@ -172,17 +172,25 @@ Use render props to apply dynamic animations based on component state:
### Respecting Motion Preferences
Always respect user motion preferences:
HeroUI components automatically respect user motion preferences using Tailwind's `motion-reduce:` utility. All built-in transitions and animations are disabled when users enable "reduce motion" in their system settings.
HeroUI extends Tailwind's `motion-reduce:` variant to support both the native `prefers-reduced-motion` media query and the `data-reduce-motion` attribute, providing flexible control over animations.
```css
/* HeroUI pattern - uses Tailwind's motion-reduce: */
.button {
@apply transition-colors motion-reduce:transition-none;
}
/* Expands to support both approaches: */
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
}
.animate-* {
animation: none !important;
}
[data-reduce-motion="true"] .button {
transition: none;
}
```
@@ -206,6 +214,19 @@ function AnimatedCard() {
}
```
### Disabling Animations Globally
Disable all animations by adding `data-reduce-motion="true"` to the `<html>` or `<body>` tag:
```html
<!DOCTYPE html>
<html data-reduce-motion="true">
<!-- All HeroUI animations will be disabled -->
</html>
```
HeroUI automatically detects the user's `prefers-reduced-motion: reduce` setting and disables animations accordingly. By extending Tailwind's native `motion-reduce:` variant, HeroUI provides a consistent API that works seamlessly with both system preferences and manual control via the data attribute.
## Performance Tips
### Use GPU-Accelerated Properties
@@ -228,6 +249,8 @@ Prefer `transform` and `opacity` for smooth animations:
### Will-Change Optimization
Use `will-change` to optimize animations, but remove it when not animating:
```css
.button {
will-change: transform;
+88 -105
View File
@@ -34,8 +34,6 @@ We follow a simple naming pattern:
<div className="bg-accent text-accent-foreground">Hello</div>
```
## Available Colors
### Base Colors
These four colors stay the same in all themes:
@@ -53,11 +51,10 @@ These four colors stay the same in all themes:
colors={[
{ name: "Background", variable: "--background", foreground: "--foreground" },
{ name: "Foreground", variable: "--foreground" },
{ name: "Panel", variable: "--panel", foreground: "--panel-foreground" },
{ name: "Panel Foreground", variable: "--panel-foreground" },
{ name: "Surface 1", variable: "--surface-1", foreground: "--foreground" },
{ name: "Surface 2", variable: "--surface-2", foreground: "--foreground" },
{ name: "Surface 3", variable: "--surface-3", foreground: "--foreground" },
{ name: "Surface", variable: "--surface", foreground: "--surface-foreground" },
{ name: "Surface Foreground", variable: "--surface-foreground" },
{ name: "Overlay", variable: "--overlay", foreground: "--overlay-foreground" },
{ name: "Overlay Foreground", variable: "--overlay-foreground" },
]}
/>
@@ -103,8 +100,6 @@ For consistent form field styling across input components:
]}
/>
> **Note**: These variables default to existing colors but can be overridden for specialized form styling without affecting other components.
### Other Colors
<ColorPalette
@@ -163,107 +158,98 @@ For consistent form field styling across input components:
## Default Theme
The complete theme definition can be found in ([default.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/index.css)). This theme automatically switches between light and dark modes based on the `class="dark"` or `data-theme="dark"` attributes.
The complete theme definition can be found in ([variables.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/variables.css)). This theme automatically switches between light and dark modes based on the `class="dark"` or `data-theme="dark"` attributes.
<CollapsibleCode lang="css" code={` @layer base {
/* HeroUI Default Theme */
:root {
color-scheme: light;
/* == Common Variables == */
/* Primitive Colors (Do not change between light and dark) */
--white: oklch(100% 0 0);
--black: oklch(0% 0 0);
--snow: oklch(0.9911 0 0);
--eclipse: oklch(0.2221 0 0);
--eclipse: oklch(0.2103 0.0059 285.89);
/* Spacing scale */
--spacing: 0.25rem;
/* Border */
--border-width: 1px;
--border-width: 0px; /* no border by default */
--field-border-width: var(--border-width);
--disabled-opacity: 0.5;
/* Radius */
--radius: 0.5rem;
--field-radius: calc(var(--radius) * 1.5);
/* Panel specific radius */
--radius-panel: 0.5rem;
--radius-panel-inner: calc(var(--radius-panel) * 0.5);
/* Small radius for compact components */
--radius-sm: 0.375rem;
/* Ring offset - Used for focus ring */
--ring-offset-width: 2px;
/* Typography */
--font-size-base: 1rem;
--font-size-scale-desktop: 0.875;
--font-weight-medium: 500;
--font-weight-semibold: 600;
/* Cursor */
--cursor-interactive: pointer;
--cursor-disabled: not-allowed;
/* Radius */
--radius: 0.5rem;
--field-radius: calc(var(--radius) * 1.5);
/* == Light Theme Variables == */
/* Base Colors */
--background: var(--white);
--background: oklch(0.9702 0 0);
--foreground: var(--eclipse);
--panel: var(--white);
--panel-foreground: var(--foreground);
--radius: 1rem;
/* Surface: Used for non-overlay components (cards, accordions, disclosure groups) */
--surface: var(--white);
--surface-foreground: var(--foreground);
--muted: var(--color-neutral-500);
/* Overlay: Used for floating/overlay components (tooltips, popovers, modals, menus) */
--overlay: var(--white);
--overlay-foreground: var(--foreground);
--scrollbar: var(--color-neutral-300);
--muted: oklch(0.5517 0.0138 285.94);
--scrollbar: oklch(87.1% 0.006 286.286);
--default: var(--color-neutral-50);
--default: oklch(94% 0.001 286.375);
--default-foreground: var(--eclipse);
--surface-1: var(--background);
--surface-2: var(--color-neutral-100);
--surface-3: var(--color-neutral-200);
--accent: var(--color-neutral-950);
--accent: oklch(0.6204 0.195 253.83);
--accent-foreground: var(--snow);
--accent-soft: var(--color-neutral-200);
--accent-soft-foreground: var(--color-neutral-900);
/* Form Field Defaults */
--field-background: var(--default);
--field-foreground: var(--foreground);
/* Form Field Defaults - Colors */
--field-background: var(--white);
--field-foreground: oklch(0.2103 0.0059 285.89);
--field-placeholder: var(--muted);
--field-border: var(--border);
--field-border: transparent; /* no border by default on form fields */
/* Status Colors */
--success: oklch(0.5503 0.1244 153.56);
--success-foreground: var(--snow);
--success: oklch(0.7329 0.1935 150.81);
--success-foreground: var(--eclipse);
--warning: oklch(0.7186 0.1521 64.85);
--warning: oklch(0.7819 0.1585 72.33);
--warning-foreground: var(--eclipse);
--danger: oklch(0.6259 0.1908 29.19);
--danger: oklch(0.6532 0.2328 25.74);
--danger-foreground: var(--snow);
/* Component Colors */
--segment: var(--white);
--segment-foreground: var(--foreground);
--segment-foreground: var(--eclipse);
/* Misc Colors */
--border: oklch(0 0 0 / 15%);
--divider: var(--color-neutral-200);
--focus: oklch(0% 0 0 / 20%);
--border: oklch(0 0 0 / 0%);
--divider: oklch(92% 0.004 286.32);
--focus: var(--accent);
--link: var(--foreground);
/* Shadows */
--panel-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.3) inset, 0 2px 8px 0 rgba(0, 0, 0, 0.08);
/* Shadow used for form fields */
--field-shadow: 0 0 0 0 rgba(255, 255, 255, 0.1) inset, 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--surface-shadow:
0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06),
0 0 1px 0 rgba(0, 0, 0, 0.06);
/* Overlay shadow */
--overlay-shadow: 0 4px 16px 0 rgba(24, 24, 27, 0.08), 0 8px 24px 0 rgba(24, 24, 27, 0.09);
--field-shadow:
0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06),
0 0 1px 0 rgba(0, 0, 0, 0.06);
/* Skeleton Default Global Animation */
--skeleton-animation: shimmer; /* shimmer, pulse, none */
}
@@ -271,62 +257,77 @@ The complete theme definition can be found in ([default.css](https://github.com/
.dark,
[data-theme="dark"] {
color-scheme: dark;
/* == Dark Theme Variables == */
/* Base Colors */
--background: var(--black);
--background: oklch(12% 0.005 285.823);
--foreground: var(--snow);
--panel: var(--eclipse);
--muted: var(--color-neutral-400);
/* Surface: Used for non-overlay components (cards, accordions, disclosure groups) */
--surface: oklch(0.2103 0.0059 285.89);
--surface-foreground: var(--foreground);
--scrollbar: var(--color-neutral-800);
/* Overlay: Used for floating/overlay components (tooltips, popovers, modals, menus) - lighter for contrast */
--overlay: oklch(0.22 0.0059 285.89); /* A bit lighter than surface for visibility in dark mode */
--overlay-foreground: var(--foreground);
--default: var(--color-neutral-900);
--muted: oklch(70.5% 0.015 286.067);
--scrollbar: oklch(70.5% 0.015 286.067);
--default: oklch(27.4% 0.006 286.033);
--default-foreground: var(--snow);
--surface-1: var(--background);
--surface-2: var(--color-neutral-900);
--surface-3: var(--color-neutral-800);
--accent: var(--color-neutral-50);
--accent-foreground: var(--eclipse);
--accent-soft: var(--color-neutral-800);
--accent-soft-foreground: var(--color-neutral-200);
/* Form Field Defaults - Colors (only the ones that are different from light theme) */
--field-background: var(--default);
--field-foreground: var(--foreground);
/* Status Colors */
--success: oklch(0.5503 0.1244 153.56);
--success-foreground: var(--snow);
--warning: oklch(85% 0.1414 77.88);
--warning: oklch(0.8203 0.1388 76.34);
--warning-foreground: var(--eclipse);
--danger: oklch(0.5798 0.1635 30.83);
--danger: oklch(0.594 0.1967 24.63);
--danger-foreground: var(--snow);
/* Component Colors */
--segment: oklch(1 0 0 / 16%);
--segment: oklch(0.3964 0.01 285.93);
--segment-foreground: var(--foreground);
/* Misc Colors */
--border: oklch(1 0 0 / 12%);
--divider: var(--color-neutral-800);
--focus: oklch(100% 0 0 / 50%);
--border: oklch(1 0 0 / 0%);
--divider: oklch(22% 0.006 286.033);
--focus: var(--accent);
--link: var(--foreground);
/* Shadows */
--panel-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.3) inset, 0 2px 8px 0 rgba(0, 0, 0, 0.08);
--field-shadow: 0 0 0 0 rgba(255, 255, 255, 0.1) inset, 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--surface-shadow: 0 0 0 0 transparent inset; /* No shadow on dark mode */
--overlay-shadow: 0 0 0 0 transparent inset; /* No shadow on dark mode */
--field-shadow: 0 0 0 0 transparent inset; /* Transparent shadow to allow ring utilities to work */
}
}
`}
/>
> **Note**: Colors like `--color-neutral-*`, `--color-white`, and `--color-black` come from [Tailwind CSS v4's built-in theme](https://tailwindcss.com/docs/colors).
## Customizing Colors
### Override Existing Colors
```css
:root {
/* Override default colors */
--accent: oklch(0.7 0.15 250);
--success: oklch(0.65 0.15 155);
}
[data-theme="dark"] {
/* Override dark theme colors */
--accent: oklch(0.8 0.12 250);
--success: oklch(0.75 0.12 155);
}
```
**Tip**: Convert colors at [oklch.com](https://oklch.com)
### Add Your Own Colors
```css
@@ -358,24 +359,6 @@ Now you can use it:
> **Note**: To learn more about theme variables and how they work in Tailwind CSS v4, see the [Tailwind CSS Theme documentation](https://tailwindcss.com/docs/theme).
### Override Existing Colors
```css
:root {
/* Override default colors */
--accent: oklch(0.7 0.15 250);
--success: oklch(0.65 0.15 155);
}
[data-theme="dark"] {
/* Override dark theme colors */
--accent: oklch(0.8 0.12 250);
--success: oklch(0.75 0.12 155);
}
```
**Tip**: Convert colors at [oklch.com](https://oklch.com)
## Quick Tips
- Always use color variables, not hard-coded values
+107 -34
View File
@@ -38,41 +38,34 @@ import Link from 'next/link';
These components support `asChild`:
- `Button` - Change button element
- `Alert` and its parts - `Alert`, `AlertIcon`, `AlertContent`, `AlertTitle`, `AlertDescription`, `AlertAction`, `AlertClose`
- `Avatar` and its parts - `Avatar`, `AvatarImage`, `AvatarFallback`
- More components coming soon
- `Alert` and its parts - `Alert` (or `Alert.Root`), `Alert.Icon`, `Alert.Content`, `Alert.Title`, `Alert.Description`, `Alert.Action`, `Alert.Close`
- `Avatar` and its parts - `Avatar` (or `Avatar.Root`), `Avatar.Image`, `Avatar.Fallback`
- All other HeroUI components with root elements
## Compound Components
HeroUI components are built as compound components - they export multiple parts that work together. You can use them in two ways:
HeroUI components are built as compound components - they export multiple parts that work together. You can use them in three flexible ways:
### Option 1: Named Exports (Recommended)
### Option 1: Compound Pattern (Recommended)
Import each part separately:
Use the main component directly without `.Root` suffix:
```tsx
import {
Alert,
AlertIcon,
AlertContent,
AlertTitle,
AlertDescription,
AlertClose
} from '@heroui/react';
import { Alert } from '@heroui/react';
<Alert>
<AlertIcon />
<AlertContent>
<AlertTitle>Success</AlertTitle>
<AlertDescription>Your changes have been saved.</AlertDescription>
</AlertContent>
<AlertClose />
<Alert.Icon />
<Alert.Content>
<Alert.Title>Success</Alert.Title>
<Alert.Description>Your changes have been saved.</Alert.Description>
</Alert.Content>
<Alert.Close />
</Alert>
```
### Option 2: Dot Notation (Compound Pattern)
### Option 2: Compound Pattern with .Root
Import the main component and access parts via dot notation:
Use the `.Root` suffix if you prefer explicit naming:
```tsx
import { Alert } from '@heroui/react';
@@ -87,15 +80,73 @@ import { Alert } from '@heroui/react';
</Alert.Root>
```
**Note:** When using dot notation, the main component requires `.Root` suffix (e.g., `<Alert.Root>` instead of `<Alert>`).
### Option 3: Named Exports
Import each part separately:
```tsx
import {
AlertRoot,
AlertIcon,
AlertContent,
AlertTitle,
AlertDescription,
AlertClose
} from '@heroui/react';
<AlertRoot>
<AlertIcon />
<AlertContent>
<AlertTitle>Success</AlertTitle>
<AlertDescription>Your changes have been saved.</AlertDescription>
</AlertContent>
<AlertClose />
</AlertRoot>
```
### Mixed Syntax
You can mix compound and named exports in the same component:
```tsx
import { Alert, AlertTitle, AlertDescription } from '@heroui/react';
<Alert>
<Alert.Icon />
<Alert.Content>
<AlertTitle>Success</AlertTitle>
<AlertDescription>Your changes have been saved.</AlertDescription>
</Alert.Content>
<Alert.Close />
</Alert>
```
### Simple Components
Simple components like `Button` work the same way - no `.Root` needed:
```tsx
import { Button } from '@heroui/react';
// Recommended - no .Root needed
<Button>Click me</Button>
// Or with .Root
<Button.Root>Click me</Button.Root>
// Or named export
import { ButtonRoot } from '@heroui/react';
<ButtonRoot>Click me</ButtonRoot>
```
### Benefits of Compound Components
Both patterns provide:
All three patterns provide:
- **Flexibility** - Arrange parts as needed
- **Customization** - Style each part independently
- **Control** - Add or remove parts
- **Consistency** - Choose the pattern that fits your codebase
## Style Variants
@@ -160,14 +211,14 @@ function LinkButton({ href, children, ...props }) {
// Icon button with tooltip
function IconButton({ icon, label, ...props }) {
return (
<Tooltip.Root>
<Tooltip>
<Tooltip.Trigger>
<Button isIconOnly {...props}>
<Icon icon={icon} />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>{label}</Tooltip.Content>
</Tooltip.Root>
</Tooltip>
);
}
```
@@ -177,11 +228,11 @@ function IconButton({ icon, label, ...props }) {
You can create your own custom variants by extending the component's variant function.
```tsx
import type {ButtonProps} from "@heroui/react";
import type {VariantProps} from "tailwind-variants";
import type { ButtonRootProps } from "@heroui/react";
import type { VariantProps } from "tailwind-variants";
import {Button, buttonVariants} from "@heroui/react";
import {tv} from "tailwind-variants";
import { Button, buttonVariants } from "@heroui/react";
import { tv } from "tailwind-variants";
const myButtonVariants = tv({
extend: buttonVariants,
@@ -210,11 +261,11 @@ const myButtonVariants = tv({
});
type MyButtonVariants = VariantProps<typeof myButtonVariants>;
export type MyButtonProps = Omit<ButtonProps, "className"> &
MyButtonVariants & {className?: string};
export type MyButtonProps = Omit<ButtonRootProps, "className"> &
MyButtonVariants & { className?: string };
function CustomButton({className, radius, variant, ...props}: MyButtonProps) {
return <Button className={myButtonVariants({className, radius, variant})} {...props} />;
function CustomButton({ className, radius, variant, ...props }: MyButtonProps) {
return <Button className={myButtonVariants({ className, radius, variant })} {...props} />;
}
export function CustomVariants() {
@@ -222,6 +273,28 @@ export function CustomVariants() {
}
```
### Type References
When working with component types, use named type imports or object-style syntax:
**Recommended - Named type imports:**
```tsx
import type { ButtonRootProps, AvatarRootProps } from "@heroui/react";
type MyButtonProps = ButtonRootProps;
type MyAvatarProps = AvatarRootProps;
```
**Alternative - Object-style syntax:**
```tsx
import { Button, Avatar } from "@heroui/react";
type MyButtonProps = Button["RootProps"];
type MyAvatarProps = Avatar["RootProps"];
```
**Note:** The namespace syntax `Button.RootProps` is no longer supported. Use `Button["RootProps"]` or named imports instead.
## With Next.js
+185 -120
View File
@@ -72,39 +72,61 @@ Add a theme class to your HTML and apply colors to the body:
[data-theme="ocean"] {
color-scheme: light;
/* Base */
/* Primitive Colors (Do not change between light and dark) */
--white: oklch(100% 0 0);
--black: oklch(0% 0 0);
--snow: oklch(0.9911 0 0);
--eclipse: oklch(0.2103 0.0059 285.89);
/* Spacing & Layout */
--spacing: 0.25rem;
--border-width: 0px;
--field-border-width: var(--border-width);
--disabled-opacity: 0.5;
--ring-offset-width: 2px;
--cursor-interactive: pointer;
--cursor-disabled: not-allowed;
/* Radius */
--radius: 0.75rem;
--field-radius: calc(var(--radius) * 1.5);
/* Base Colors */
--background: oklch(0.985 0.015 225);
--foreground: var(--eclipse);
--panel: var(--white);
--panel-foreground: var(--foreground);
--radius: 0.75rem;
/* Surface: Used for non-overlay components */
--surface: var(--white);
--surface-foreground: var(--foreground);
--muted: var(--color-neutral-500);
--scrollbar: var(--color-neutral-300);
/* Overlay: Used for floating/overlay components */
--overlay: var(--white);
--overlay-foreground: var(--foreground);
--default: var(--color-white);
--default-foreground: var(--color-neutral-600);
--muted: oklch(0.5517 0.0138 285.94);
--scrollbar: oklch(87.1% 0.006 286.286);
--surface-1: var(--background);
--surface-2: oklch(0.965 0.020 225);
--surface-3: oklch(0.930 0.030 225);
--default: oklch(94% 0.001 286.375);
--default-foreground: var(--eclipse);
/* Ocean accent */
--accent: oklch(0.450 0.150 230);
--accent-foreground: var(--snow);
--accent-soft: oklch(0.920 0.040 230);
--accent-soft-foreground: oklch(0.280 0.080 230);
/* Form Field Defaults */
--field-background: var(--white);
--field-foreground: oklch(0.2103 0.0059 285.89);
--field-placeholder: var(--muted);
--field-border: transparent;
/* Status (kept compatible) */
--success: oklch(0.5503 0.1244 153.56);
--success-foreground: var(--snow);
--success: oklch(0.7329 0.1935 150.81);
--success-foreground: var(--eclipse);
--warning: oklch(0.7186 0.1521 64.85);
--warning: oklch(0.7819 0.1585 72.33);
--warning-foreground: var(--eclipse);
--danger: oklch(0.6259 0.1908 29.19);
--danger: oklch(0.6532 0.2328 25.74);
--danger-foreground: var(--snow);
/* Component Colors */
@@ -113,14 +135,18 @@ Add a theme class to your HTML and apply colors to the body:
/* Misc */
--border: oklch(0.50 0.060 230 / 22%);
--divider: var(--color-neutral-200);
--focus: oklch(0.60 0.150 230 / 35%);
--divider: oklch(92% 0.004 286.32);
--focus: var(--accent);
--link: var(--accent);
--shadow-with-border:
0 0px 5px 0px rgba(0, 10, 40, 0.03),
0 2px 10px 0px rgba(0, 10, 40, 0.07),
0 0px 1px 0px rgba(0, 0, 0, 0.25);
/* Shadows */
--surface-shadow:
0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06),
0 0 1px 0 rgba(0, 0, 0, 0.06);
--overlay-shadow: 0 4px 16px 0 rgba(24, 24, 27, 0.08), 0 8px 24px 0 rgba(24, 24, 27, 0.09);
--field-shadow:
0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06),
0 0 1px 0 rgba(0, 0, 0, 0.06);
/* Skeleton Default Global Animation */
--skeleton-animation: shimmer; /* Possible values: shimmer, pulse, none */
@@ -130,52 +156,56 @@ Add a theme class to your HTML and apply colors to the body:
[data-theme="ocean-dark"] {
color-scheme: dark;
/* Base */
/* Base Colors */
--background: oklch(0.140 0.020 230);
--foreground: var(--snow);
--panel: oklch(0.180 0.020 230);
--muted: var(--color-neutral-400);
--scrollbar: var(--color-neutral-800);
/* Surface: Used for non-overlay components */
--surface: oklch(0.2103 0.0059 285.89);
--surface-foreground: var(--foreground);
--default: var(--color-neutral-900);
--default-foreground: var(--color-neutral-200);
/* Overlay: Used for floating/overlay components */
--overlay: oklch(0.22 0.0059 285.89);
--overlay-foreground: var(--foreground);
--surface-1: var(--background);
--surface-2: oklch(0.220 0.030 230);
--surface-3: oklch(0.280 0.030 230);
--muted: oklch(70.5% 0.015 286.067);
--scrollbar: oklch(70.5% 0.015 286.067);
--default: oklch(27.4% 0.006 286.033);
--default-foreground: var(--snow);
/* Form Field Defaults */
--field-background: var(--default);
--field-foreground: var(--foreground);
/* Ocean accent */
--accent: oklch(0.860 0.080 230);
--accent-foreground: var(--eclipse);
--accent-soft: oklch(0.300 0.050 230);
--accent-soft-foreground: var(--color-neutral-200);
/* Status */
--success: oklch(80% 0.1561 154);
--success: oklch(0.7329 0.1935 150.81);
--success-foreground: var(--eclipse);
--warning: oklch(85% 0.1414 77.88);
--warning: oklch(0.8203 0.1388 76.34);
--warning-foreground: var(--eclipse);
--danger: oklch(0.5798 0.1635 30.83);
--danger: oklch(0.594 0.1967 24.63);
--danger-foreground: var(--snow);
/* Component Colors */
--segment: oklch(1 0 0 / 16%);
--segment: oklch(0.3964 0.01 285.93);
--segment-foreground: var(--foreground);
/* Misc */
--border: oklch(1 0 0 / 12%);
--divider: var(--color-neutral-800);
--focus: oklch(0.860 0.080 230 / 40%);
--border: oklch(1 0 0 / 0%);
--divider: oklch(22% 0.006 286.033);
--focus: var(--accent);
--link: var(--accent);
--shadow-with-border:
0 0 0 1px var(--border),
0 5px 10px -3px rgba(0, 10, 40, 0.35),
0 3px 4px -2px rgba(0, 0, 0, 0.12);
/* Shadows */
--surface-shadow: 0 0 0 0 transparent inset;
--overlay-shadow: 0 0 0 0 transparent inset;
--field-shadow: 0 0 0 0 transparent inset;
}
}
`}
@@ -264,8 +294,8 @@ Import only what you need:
/* Base requirements */
@import "tailwindcss";
@import "@heroui/styles/base/base.css" layer(base);
@import "@heroui/styles/themes/theme.css" layer(theme);
@import "@heroui/styles/themes/default.css" layer(theme);
@import "@heroui/styles/themes/shared/theme.css" layer(theme);
@import "@heroui/styles/themes/default" layer(theme);
/* Components (all components) */
@import "@heroui/styles/components/index.css" layer(components);
@@ -329,28 +359,27 @@ HeroUI defines three types of variables:
For a complete reference of all variables and their values, see:
- [Colors Documentation](/docs/handbook/colors) - Visual color palette and base variables
- [Default Theme Source](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/index.css) - Complete theme implementation
- [Default Theme Variables](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/variables.css) - Complete theme variables
- [Shared Theme Utilities](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/shared/theme.css) - Calculated variables and utilities
### Calculated Variables (Tailwind)
We use Tailwind's `@theme` directive to automatically create calculated variables for hover states and radius variants:
We use Tailwind's `@theme` directive to automatically create calculated variables for hover states and radius variants. These are defined in `themes/shared/theme.css`:
<CollapsibleCode lang="css" code={` @theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-panel: var(--panel);
--color-panel-foreground: var(--panel-foreground);
--color-surface: var(--surface);
--color-surface-foreground: var(--surface-foreground);
--color-overlay: var(--overlay);
--color-overlay-foreground: var(--overlay-foreground);
--color-muted: var(--muted);
--color-surface: var(--surface-1);
--color-surface-foreground: var(--foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-accent-soft: var(--accent-soft);
--color-accent-soft-foreground: var(--accent-soft-foreground);
--color-segment: var(--segment);
--color-segment-foreground: var(--segment-foreground);
@@ -361,10 +390,6 @@ We use Tailwind's `@theme` directive to automatically create calculated variable
--color-default: var(--default);
--color-default-foreground: var(--default-foreground);
--color-field: var(--field-background, var(--color-default));
--color-field-foreground: var(--field-foreground, var(--color-foreground));
--color-field-placeholder: var(--field-placeholder, var(--color-muted));
--color-field-border: var(--field-border, var(--color-border));
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
@@ -375,43 +400,49 @@ We use Tailwind's `@theme` directive to automatically create calculated variable
--color-danger: var(--danger);
--color-danger-foreground: var(--danger-foreground);
--shadow-panel: var(--panel-shadow);
--shadow-surface: var(--surface-shadow);
--shadow-overlay: var(--overlay-shadow);
--shadow-field: var(--field-shadow);
/* Form Field Tokens */
--color-field: var(--field-background, var(--color-default));
--color-field-hover: color-mix(
in oklab,
var(--field-background, var(--color-default)) 90%,
var(--field-foreground, var(--color-default-foreground)) 10%
);
--color-field-foreground: var(--field-foreground, var(--color-foreground));
--color-field-placeholder: var(--field-placeholder, var(--color-muted));
--color-field-border: var(--field-border, var(--color-border));
--radius-field: var(--field-radius, var(--radius-xl));
--border-width-field: var(--field-border-width, var(--border-width));
/* Calculated Variables */
/* Colors */
--color-panel-hover: color-mix(
/* --- background shades --- */
--color-background-secondary: color-mix(
in oklab,
var(--color-panel) 90%,
var(--color-panel-foreground) 5%
var(--color-background) 96%,
var(--color-foreground) 4%
);
--color-default-hover: color-mix(
--color-background-tertiary: color-mix(
in oklab,
var(--color-default) 90%,
var(--color-default-foreground) 2%
var(--color-background) 92%,
var(--color-foreground) 8%
);
--color-field-hover: color-mix(
--color-background-quaternary: color-mix(
in oklab,
var(--color-field) 88%,
var(--color-field-foreground) 8%
);
--color-field-focus: color-mix(
in oklab,
var(--color-field) 80%,
var(--color-field-foreground) 14%
var(--color-background) 86%,
var(--color-foreground) 14%
);
--color-background-inverse: var(--color-foreground);
/* Hover states */
--color-default-hover: color-mix(in oklab, var(--color-default) 80%, transparent);
--color-accent-hover: color-mix(
in oklab,
var(--color-accent) 90%,
var(--color-accent-foreground) 20%
);
--color-accent-soft-hover: color-mix(
in oklab,
var(--color-accent-soft) 10%,
var(--color-accent-soft-foreground) 18%
var(--color-accent-foreground) 10%
);
--color-success-hover: color-mix(
in oklab,
@@ -428,10 +459,18 @@ We use Tailwind's `@theme` directive to automatically create calculated variable
var(--color-danger) 90%,
var(--color-danger-foreground) 10%
);
/* Form Field Colors */
--color-field-hover: color-mix(
in oklab,
var(--color-field) 90%,
var(--color-field-foreground) 2%
);
--color-field-focus: var(--color-field);
--color-field-border-hover: color-mix(
in oklab,
var(--color-field-border) 88%,
var(--color-field-foreground) 8%
var(--color-field-foreground) 10%
);
--color-field-border-focus: color-mix(
in oklab,
@@ -439,75 +478,101 @@ We use Tailwind's `@theme` directive to automatically create calculated variable
var(--color-field-foreground) 22%
);
/* Surface Colors */
--color-surface-1: var(--surface-1);
--color-surface-1-foreground: color-mix(
/* Soft Colors */
--color-accent-soft: color-mix(in oklab, var(--color-accent) 15%, transparent);
--color-accent-soft-foreground: var(--color-accent);
--color-accent-soft-hover: color-mix(in oklab, var(--color-accent) 20%, transparent);
--color-danger-soft: color-mix(in oklab, var(--color-danger) 15%, transparent);
--color-danger-soft-foreground: var(--color-danger);
--color-danger-soft-hover: color-mix(in oklab, var(--color-danger) 20%, transparent);
--color-warning-soft: color-mix(in oklab, var(--color-warning) 15%, transparent);
--color-warning-soft-foreground: var(--color-warning);
--color-warning-soft-hover: color-mix(in oklab, var(--color-warning) 20%, transparent);
--color-success-soft: color-mix(in oklab, var(--color-success) 15%, transparent);
--color-success-soft-foreground: var(--color-success);
--color-success-soft-hover: color-mix(in oklab, var(--color-success) 20%, transparent);
/* Surface Levels */
--color-surface-secondary: color-mix(in oklab, var(--surface) 94%, var(--surface-foreground) 6%);
--color-surface-tertiary: color-mix(in oklab, var(--surface) 92%, var(--surface-foreground) 8%);
--color-surface-quaternary: color-mix(
in oklab,
var(--color-surface-1) 80%,
var(--color-foreground) 3.5%
);
--color-surface-2: var(--surface-2);
--color-surface-2-foreground: color-mix(
in oklab,
var(--color-surface-2) 80%,
var(--color-foreground) 3.5%
);
--color-surface-3: var(--surface-3);
--color-surface-3-foreground: color-mix(
in oklab,
var(--color-surface-3) 80%,
var(--color-foreground) 3.5%
var(--surface) 86%,
var(--default-foreground) 14%
);
/* Radius */
--radius-xs: calc(var(--radius) * 0.25);
--radius-sm: calc(var(--radius) * 0.5);
--radius-md: calc(var(--radius) * 0.75);
--radius-lg: calc(var(--radius) * 1);
--radius-xl: calc(var(--radius) * 1.5);
--radius-2xl: calc(var(--radius) * 2);
--radius-3xl: calc(var(--radius) * 3);
--radius-4xl: calc(var(--radius) * 4);
--radius-panel: var(--radius-xl);
--radius-panel-inner: var(--radius-2xl);
/* On Surface Colors */
--color-on-surface: color-mix(
in oklab,
var(--color-surface) 93%,
var(--color-surface-foreground) 7%
);
--color-on-surface-foreground: var(--color-surface-foreground);
--color-on-surface-hover: color-mix(
in oklab,
var(--color-surface) 91%,
var(--color-surface-foreground) 9%
);
--color-on-surface-focus: var(--color-on-surface);
/* Radius scale */
--radius-xs: calc(var(--radius) * 0.25); /* 0.125rem (2px) */
--radius-sm: calc(var(--radius) * 0.5); /* 0.25rem (4px) */
--radius-md: calc(var(--radius) * 0.75); /* 0.375rem (6px) */
--radius-lg: calc(var(--radius) * 1); /* 0.5rem (8px) */
--radius-xl: calc(var(--radius) * 1.5); /* 0.75rem (12px) */
--radius-2xl: calc(var(--radius) * 2); /* 1rem (16px) */
--radius-3xl: calc(var(--radius) * 3); /* 1.5rem (24px) */
--radius-4xl: calc(var(--radius) * 4); /* 2rem (32px) */
/* Transition Timing Functions */
--ease-smooth: ease; /* same as transition: ease; */
/* These custom curves are made by https://twitter.com/bdc */
/* From smoother to faster */
--ease-smooth: ease;
--ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
--ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
--ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
--ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
--ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
/* From slower to faster */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
/* Custom smooth-out curve: fast start, smooth stop - Apple style */
--ease-fluid-out: cubic-bezier(0.32, 0.72, 0, 1);
/* From slower to faster */
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
--ease-linear: linear;
/* Animations */
--animate-spin-fast: spin 0.75s linear infinite;
--animate-skeleton: skeleton 2s linear infinite;
--animate-caret-blink: caret-blink 1.2s ease-out infinite;
@keyframes skeleton {
100% {
transform: translateX(200%);
}
}
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
}
`}
/>
+4 -3
View File
@@ -8,9 +8,10 @@ icon: book-open
HeroUI is a UI library for React that helps you build beautiful and accessible user interfaces. Created on top of [Tailwind CSS v4](https://tailwindcss.com/) and [React Aria Components](https://react-spectrum.adobe.com/react-aria/index.html).
<DocsImage
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/docs-intro-cover.jpg"
darkSrc="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/docs-intro-cover-dark.jpg"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/heroui-og_2x.jpg"
darkSrc="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/heroui-og-black_2x.jpg"
alt="HeroUI v3 Introduction"
className="h-[220px] md:h-[384px]"
/>
Every component is designed with care: smooth animations, polished details, and accessibility built in. It works out of the box, yet gives you the freedom to make it your own.
@@ -101,7 +102,7 @@ Huge thanks to [Devon Govett](https://x.com/devongovett) and the Adobe team for
Yes, completely free and open source under the MIT license.
### Is it production-ready?
Currently in alpha. We're actively working towards a stable release with community feedback.
Currently in **beta**. We're actively working towards a stable release with community feedback.
### Can I customize the components?
Yes! Use Tailwind utilities, CSS variables, BEM modifiers, or compose component parts differently. Every slot is customizable.
+7
View File
@@ -23,10 +23,12 @@
"ui-for-agents/mcp-server",
"---Components---",
"components/accordion",
"components/alert",
"components/avatar",
"components/button",
"components/card",
"components/checkbox",
"components/checkbox-group",
"components/chip",
"components/close-button",
"components/description",
@@ -36,15 +38,20 @@
"components/fieldset",
"components/form",
"components/input",
"components/input-otp",
"components/kbd",
"components/label",
"components/link",
"components/listbox",
"components/popover",
"components/radio-group",
"components/separator",
"components/select",
"components/skeleton",
"components/slider",
"components/spinner",
"components/switch",
"components/surface",
"components/tabs",
"components/text-field",
"components/textarea",
+4 -4
View File
@@ -16,22 +16,22 @@ Install HeroUI and required dependencies:
<Tabs items={["npm", "pnpm", "yarn", "bun"]}>
<Tab value="npm">
```bash
npm i @heroui/styles@alpha @heroui/react@alpha
npm i @heroui/styles@beta @heroui/react@beta
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add @heroui/styles@alpha @heroui/react@alpha
pnpm add @heroui/styles@beta @heroui/react@beta
```
</Tab>
<Tab value="yarn">
```bash
yarn add @heroui/styles@alpha @heroui/react@alpha
yarn add @heroui/styles@beta @heroui/react@beta
```
</Tab>
<Tab value="bun">
```bash
bun add @heroui/styles@alpha @heroui/react@alpha
bun add @heroui/styles@beta @heroui/react@beta
```
</Tab>
</Tabs>
@@ -1,14 +1,13 @@
---
title: MCP Server
description: Access HeroUI v3 documentation directly in your AI assistant
icon: preview
---
The HeroUI MCP Server gives AI assistants direct access to HeroUI v3 component documentation, making it easier to build with HeroUI in AI-powered development environments.
<DocsImage
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/hero-react-mcp-light-3.jpg"
darkSrc="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/hero-react-mcp-dark-2.jpg"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/hero-react-mcp-light-new-2.jpg"
darkSrc="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/hero-react-mcp-dark-new.jpg"
alt="HeroUI v3 MCP Server"
/>
@@ -26,12 +25,12 @@ The MCP server currently supports **@heroui/react v3** only and [stdio transport
<div className="flex items-center gap-3 mb-4">
<a
href="https://link.heroui.com/mcp-cursor-install"
className="button button--tertiary button--sm no-underline bg-default/50 dark:bg-default/30"
className="button button--tertiary button--sm no-underline"
>
<svg viewBox="0 0 466.73 532.09" className="w-5 h-5 fill-current">
<path d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
</svg>
<span className="text-accent">Install in Cursor</span>
<span >Install in Cursor</span>
</a>
</div>
+13
View File
@@ -17,6 +17,19 @@ const config: NextConfig = {
optimizePackageImports: ["@heroui/react"],
reactCompiler: true,
},
async headers() {
return [
{
headers: [
{
key: "X-Robots-Tag",
value: "index, follow",
},
],
source: "/:path*",
},
];
},
images: {
remotePatterns: [
{
+1
View File
@@ -17,6 +17,7 @@
"@heroui/react": "workspace:*",
"@iconify-json/gravity-ui": "1.2.10",
"@iconify/react": "6.0.2",
"@react-stately/data": "3.14.1",
"@t3-oss/env-nextjs": "0.13.8",
"@vercel/analytics": "1.5.0",
"@vercel/og": "0.8.5",
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+1 -1
View File
@@ -6,4 +6,4 @@ Allow: /llms.txt
Allow: /llms-full.txt
# Sitemap
Sitemap: https://heroui.dev/sitemap.xml
Sitemap: https://v3.heroui.com/sitemap.xml
@@ -0,0 +1,14 @@
import {Alert, Button} from "@heroui/react";
export function AlertDemo() {
return (
<Alert className="w-[400px] items-center">
<Alert.Indicator />
<Alert.Content>
<Alert.Title>You have 2 credits left</Alert.Title>
<Alert.Description>Get a paid plan for more credits</Alert.Description>
</Alert.Content>
<Button variant="tertiary">Upgrade</Button>
</Alert>
);
}
@@ -0,0 +1,26 @@
import {Avatar, Button, Card, CloseButton} from "@heroui/react";
import {Iconify} from "@/components/iconify";
export function AlertDialogDemo() {
return (
<Card className="h-[208px] w-[340px] items-start justify-center p-5">
<Card.Header className="flex w-full items-start justify-center gap-2 px-1">
<Avatar color="warning" variant="soft">
<Avatar.Fallback>
<Iconify className="text-lg" icon="gravity-ui:floppy-disk" />
</Avatar.Fallback>
</Avatar>
<Card.Title>Unsaved changes</Card.Title>
<Card.Description>Do you want to save or discard changes?</Card.Description>
<CloseButton className="absolute right-3 top-3" />
</Card.Header>
<Card.Footer className="flex w-full items-center gap-2 px-0.5 pt-3">
<Button className="w-full" variant="tertiary">
Discard
</Button>
<Button className="w-full">Save changes</Button>
</Card.Footer>
</Card>
);
}
@@ -0,0 +1,19 @@
import {Description, Label, Switch} from "@heroui/react";
export function AllowNotificationsDemo() {
return (
<div className="flex w-full justify-center">
<Switch defaultSelected>
<div className="flex gap-8 p-4">
<div className="-mt-0.5 flex flex-col justify-start gap-1">
<Label className="w-fit text-sm font-medium">Allow notifications</Label>
<Description>Receive push notifications from HeroUI</Description>
</div>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</div>
</Switch>
</div>
);
}
@@ -0,0 +1,60 @@
import {Avatar} from "@heroui/react";
import Image from "next/image";
const avatars = [
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "Blue",
},
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Green",
},
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Purple",
},
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Orange",
},
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "red",
},
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "Blue",
},
{
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/black.jpg",
name: "Black",
},
];
export function AvatarGroupDemo() {
return (
<div className="flex w-full justify-center">
<div className="flex -space-x-2">
{avatars.slice(0, 5).map((item, index) => (
<Avatar key={`${item.name}-${index}`} className="ring-background ring-2">
<Avatar.Image asChild alt={item.name}>
<Image alt={item.name} height={40} src={item.image} width={40} />
</Avatar.Image>
<Avatar.Fallback>
{item.name
.split(" ")
.map((n) => n[0])
.join("")}
</Avatar.Fallback>
</Avatar>
))}
<Avatar className="ring-background ring-2">
<Avatar.Fallback className="bg-surface text-muted text-xs font-medium">
+{avatars.length - 2}
</Avatar.Fallback>
</Avatar>
</div>
</div>
);
}
@@ -0,0 +1,24 @@
import {Button} from "@heroui/react";
export const ButtonsDemo = () => {
return (
<div className="grid grid-cols-3 gap-2">
<Button size="sm">Call to action</Button>
<Button size="sm" variant="secondary">
Call to action
</Button>
<Button size="sm" variant="tertiary">
Call to action
</Button>
<Button size="sm" variant="danger">
Call to action
</Button>
<Button size="sm" variant="danger-soft">
Call to action
</Button>
<Button size="sm" variant="ghost">
Call to action
</Button>
</div>
);
};
@@ -0,0 +1,48 @@
import {AlertDemo} from "./alert-demo";
import {AlertDialogDemo} from "./alert-dialog-demo";
import {AllowNotificationsDemo} from "./allow-notifications-demo";
import {AvatarGroupDemo} from "./avatar-group-demo";
import {ButtonsDemo} from "./buttons-demo";
import {InputOTPDemo} from "./input-otp-demo";
import {ListBoxDemo} from "./listbox-demo";
import {LoginDemo} from "./login-demo";
import {SelectDemo} from "./select-demo";
import {SliderDemo} from "./slider-demo";
import {SubtleCardsDemo} from "./subtle-cards-demo";
import {TabsDemo1} from "./tabs-1-demo";
import {TabsDemo2} from "./tabs-2-demo";
import {TextfieldDemo} from "./textfield-demo";
import {UIComponentsDemo} from "./ui-components-demo";
import {XProfileDemo} from "./x-profile-demo";
export function DemoComponents() {
return (
<div className="mx-auto grid grid-cols-3 gap-8 py-24">
{/* Left */}
<div className="flex flex-col items-center gap-10">
<TextfieldDemo />
<SelectDemo />
<UIComponentsDemo />
<SliderDemo />
<TabsDemo1 />
<TabsDemo2 />
<ListBoxDemo />
</div>
{/* Center */}
<div className="flex flex-col items-center gap-10">
<AvatarGroupDemo />
<InputOTPDemo />
<ButtonsDemo />
<XProfileDemo />
<AlertDemo />
<AllowNotificationsDemo />
</div>
{/* Right */}
<div className="flex flex-col items-center gap-10">
<LoginDemo />
<SubtleCardsDemo />
<AlertDialogDemo />
</div>
</div>
);
}
@@ -0,0 +1,39 @@
"use client";
import {InputOTP, Label, Link} from "@heroui/react";
import {useState} from "react";
export function InputOTPDemo() {
// HERO - 4320 - easter egg
const [value, setValue] = useState("4320");
return (
<div className="flex w-full justify-center">
<div className="flex w-[280px] flex-col items-start justify-center gap-2">
<div className="flex flex-col items-start gap-1">
<Label>Verify account</Label>
<p className="text-muted text-sm">We&apos;ve sent a code to a****@gmail.com</p>
</div>
<InputOTP maxLength={6} value={value} onChange={setValue}>
<InputOTP.Group>
<InputOTP.Slot index={0} />
<InputOTP.Slot index={1} />
<InputOTP.Slot index={2} />
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
<InputOTP.Slot index={3} />
<InputOTP.Slot index={4} />
<InputOTP.Slot index={5} />
</InputOTP.Group>
</InputOTP>
<div className="flex items-center gap-[5px] px-1 pt-1">
<p className="text-muted text-sm">Didn&apos;t receive a code?</p>
<Link className="text-foreground" underline="always">
Resend
</Link>
</div>
</div>
</div>
);
}
@@ -0,0 +1,61 @@
"use client";
import {Description, Header, Kbd, Label, ListBox, Separator, Surface} from "@heroui/react";
import {Iconify} from "@/components/iconify";
export function ListBoxDemo() {
return (
<Surface className="shadow-surface w-[256px] rounded-3xl">
<ListBox aria-label="File actions" className="w-full p-2" selectionMode="none">
<ListBox.Section>
<Header>Actions</Header>
<ListBox.Item id="new-file" textValue="New file">
<div className="flex h-8 items-start justify-center pt-px">
<Iconify className="text-muted size-4 flex-shrink-0" icon="gravity-ui:square-plus" />
</div>
<div className="flex flex-col">
<Label>New file</Label>
<Description>Create a new file</Description>
</div>
<Kbd className="ms-auto" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>N</Kbd.Content>
</Kbd>
</ListBox.Item>
<ListBox.Item id="edit-file" textValue="Edit file">
<div className="flex h-8 items-start justify-center pt-px">
<Iconify className="text-muted size-4 flex-shrink-0" icon="gravity-ui:pencil" />
</div>
<div className="flex flex-col">
<Label>Edit file</Label>
<Description>Make changes</Description>
</div>
<Kbd className="ms-auto" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>E</Kbd.Content>
</Kbd>
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Danger zone</Header>
<ListBox.Item id="delete-file" textValue="Delete file" variant="danger">
<div className="flex h-8 items-start justify-center pt-px">
<Iconify className="text-danger size-4 flex-shrink-0" icon="gravity-ui:trash-bin" />
</div>
<div className="flex flex-col">
<Label>Delete file</Label>
<Description>Move to trash</Description>
</div>
<Kbd className="ms-auto" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</ListBox.Item>
</ListBox.Section>
</ListBox>
</Surface>
);
}
@@ -0,0 +1,40 @@
import {Avatar, Button, Card, CloseButton, Separator} from "@heroui/react";
import {Iconify} from "@/components/iconify";
import {AppleIcon} from "@/icons/apple";
import {GoogleIcon} from "@/icons/google";
export function LoginDemo() {
return (
<Card className="w-[320px] items-start justify-center p-5">
<Card.Header className="flex w-full items-center justify-center gap-2">
<Avatar>
<Avatar.Fallback>
<Iconify icon="gravity-ui:person" />
</Avatar.Fallback>
</Avatar>
<Card.Title>Create an account</Card.Title>
<CloseButton className="absolute right-3 top-3" />
</Card.Header>
<Card.Content className="gap-2">
<p className="text-muted text-center text-sm font-medium">
Start your free 7-day trial. No credit card required.
</p>
<Button className="w-full">Get Started</Button>
<div className="flex w-full items-center gap-2 py-2">
<Separator className="flex-1" />
<p className="text-muted text-center text-xs font-medium uppercase">Or</p>
<Separator className="flex-1" />
</div>
<Button className="w-full" variant="tertiary">
<GoogleIcon />
Continue with Google
</Button>
<Button className="w-full" variant="tertiary">
<AppleIcon />
Continue with Apple
</Button>
</Card.Content>
</Card>
);
}
@@ -0,0 +1,41 @@
import {Label, ListBox, Select} from "@heroui/react";
export function SelectDemo() {
return (
<Select isRequired className="w-[256px]" placeholder="Select one">
<Label>State</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Content>
<ListBox>
<ListBox.Item id="florida" textValue="Florida">
Florida
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="delaware" textValue="Delaware">
Delaware
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="california" textValue="California">
California
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="texas" textValue="Texas">
Texas
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="new-york" textValue="New York">
New York
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="washington" textValue="Washington">
Washington
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Content>
</Select>
);
}
@@ -0,0 +1,25 @@
"use client";
import {Label, Slider} from "@heroui/react";
export function SliderDemo() {
return (
<div className="w-[256px] px-1">
<Slider
className="w-full max-w-xs"
defaultValue={250}
formatOptions={{currency: "USD", style: "currency"}}
maxValue={500}
minValue={0}
step={10}
>
<Label>Price</Label>
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>
</div>
);
}
@@ -0,0 +1,88 @@
import {Avatar, Card} from "@heroui/react";
import Image from "next/image";
import {tv} from "tailwind-variants";
const cardStyles = tv({
slots: {
avatar: "size-[56px] rounded-xl",
card: "w-full",
cardContent: "items-start",
footer: "items-center gap-2",
footerAvatar: "size-4",
},
});
export function SubtleCardsDemo() {
const {avatar, card, cardContent, footer, footerAvatar} = cardStyles();
return (
<div className="flex w-full flex-row gap-4">
<Card className={card()}>
<Card.Header>
<Avatar className={avatar()}>
<Avatar.Image asChild>
<Image
alt="Demo 1"
height={56}
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/demo1.jpg"
width={56}
/>
</Avatar.Image>
<Avatar.Fallback>JK</Avatar.Fallback>
</Avatar>
</Card.Header>
<Card.Content className={cardContent()}>
<p className="text-sm font-medium">Indie Hackers</p>
<p className="text-muted text-sm">148 members</p>
</Card.Content>
<Card.Footer className={footer()}>
<Avatar className={footerAvatar()}>
<Avatar.Image asChild>
<Image
alt="John"
height={16}
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg"
width={16}
/>
</Avatar.Image>
<Avatar.Fallback>JK</Avatar.Fallback>
</Avatar>
<p className="text-muted text-xs">By John</p>
</Card.Footer>
</Card>
<Card className={card()}>
<Card.Header>
<Avatar className={avatar()}>
<Avatar.Image asChild>
<Image
alt="AI Builders"
height={56}
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/demo2.jpg"
width={56}
/>
</Avatar.Image>
<Avatar.Fallback>J</Avatar.Fallback>
</Avatar>
</Card.Header>
<Card.Content className={cardContent()}>
<p className="text-sm font-medium">AI Builders</p>
<p className="text-muted text-sm">362 members</p>
</Card.Content>
<Card.Footer className={footer()}>
<Avatar className={footerAvatar()}>
<Avatar.Image asChild>
<Image
alt="Martha"
height={16}
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg"
width={16}
/>
</Avatar.Image>
<Avatar.Fallback>M</Avatar.Fallback>
</Avatar>
<p className="text-muted text-xs">By Martha</p>
</Card.Footer>
</Card>
</div>
);
}
@@ -0,0 +1,32 @@
import {Tabs} from "@heroui/react";
export function TabsDemo1() {
return (
<Tabs className="w-[256px]">
<Tabs.ListContainer>
<Tabs.List aria-label="Options">
<Tabs.Tab id="1d">
1D
<Tabs.Indicator />
</Tabs.Tab>
<Tabs.Tab id="7d">
7D
<Tabs.Indicator />
</Tabs.Tab>
<Tabs.Tab id="1m">
1M
<Tabs.Indicator />
</Tabs.Tab>
<Tabs.Tab id="1y">
1Y
<Tabs.Indicator />
</Tabs.Tab>
<Tabs.Tab id="all">
All
<Tabs.Indicator />
</Tabs.Tab>
</Tabs.List>
</Tabs.ListContainer>
</Tabs>
);
}
@@ -0,0 +1,24 @@
import {Tabs} from "@heroui/react";
import {Iconify} from "@/components/iconify";
export function TabsDemo2() {
return (
<Tabs className="w-[256px]">
<Tabs.ListContainer>
<Tabs.List aria-label="Options">
<Tabs.Tab className="gap-1.5" id="chats">
<Iconify icon="gravity-ui:comment" />
<span>Chats</span>
<Tabs.Indicator />
</Tabs.Tab>
<Tabs.Tab className="gap-1.5" id="emails">
<Iconify icon="gravity-ui:envelope" />
<span>Emails</span>
<Tabs.Indicator />
</Tabs.Tab>
</Tabs.List>
</Tabs.ListContainer>
</Tabs>
);
}
@@ -0,0 +1,14 @@
import {Description, FieldError, Input, Label, TextField} from "@heroui/react";
export function TextfieldDemo() {
return (
<div>
<TextField isRequired className="items-start" name="name">
<Label>Your email</Label>
<Input className="w-[256px]" placeholder="john@email.com" />
<Description className="mt-0.5">We'll never share this with anyone else</Description>
<FieldError>The email is invalid</FieldError>
</TextField>
</div>
);
}
@@ -0,0 +1,38 @@
import {Checkbox, Radio, RadioGroup, Spinner, Switch} from "@heroui/react";
export function UIComponentsDemo() {
return (
<div className="flex w-full items-center justify-center gap-8">
{/* Checkbox - Selected State */}
<Checkbox defaultSelected>
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
</Checkbox>
{/* Switch - On State */}
<Switch defaultSelected>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
{/* Radio Buttons - Unselected and Selected */}
<RadioGroup className="gap-8" defaultValue="option2" name="demo" orientation="horizontal">
<Radio value="option1">
<Radio.Control>
<Radio.Indicator />
</Radio.Control>
</Radio>
<Radio value="option2">
<Radio.Control>
<Radio.Indicator />
</Radio.Control>
</Radio>
</RadioGroup>
{/* Spinner */}
<Spinner />
</div>
);
}
@@ -0,0 +1,53 @@
import {Avatar, Card} from "@heroui/react";
import Image from "next/image";
import {VerifiedBadgeIcon} from "@/icons/verified-badge";
export function XProfileDemo() {
return (
<Card className="w-[400px] items-start justify-center">
<Card.Header className="items-top w-full flex-row justify-between">
<div className="flex items-center gap-3">
<Avatar size="sm">
<Avatar.Image asChild alt="HeroUI">
<Image
alt="HeroUI"
height={32}
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/heroui_isotipo.png"
width={32}
/>
</Avatar.Image>
<Avatar.Fallback>H</Avatar.Fallback>
</Avatar>
<div className="flex h-full flex-col items-start justify-center">
<div className="flex items-center gap-0.5">
<span className="text-sm font-semibold leading-4">HeroUI</span>
<VerifiedBadgeIcon height={18} width={18} />
</div>
<span className="text-muted text-sm tracking-tight">@hero_ui</span>
</div>
</div>
</Card.Header>
<Card.Content className="flex-row text-left">
<p className="pl-px text-sm font-medium">
Building the future of UI for web & mobile.&nbsp;
<br />
<span aria-label="confetti" role="img">
🚀
</span>
&nbsp;(YC S24)&nbsp;
</p>
</Card.Content>
<Card.Footer className="gap-2">
<div className="flex gap-1">
<p className="text-sm font-semibold">4</p>
<p className="text-muted text-sm">Following</p>
</div>
<div className="flex gap-1">
<p className="text-sm font-semibold">97.1K</p>
<p className="text-muted text-sm">Followers</p>
</div>
</Card.Footer>
</Card>
);
}
+3 -28
View File
@@ -89,36 +89,11 @@ export default function Layout({children}: {children: ReactNode}) {
// text: "Theming",
// url: "/docs/theming",
// },
{
external: true,
icon: (
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
),
text: "Github",
type: "icon",
url: "https://github.com/heroui-inc/heroui",
},
]}
// links={[
// {
// children: (
// <NavbarMenu>
// <NavbarMenuTrigger>Documentation</NavbarMenuTrigger>
// <NavbarMenuContent>
// <NavbarMenuLink href="/docs">Hello World</NavbarMenuLink>
// </NavbarMenuContent>
// </NavbarMenu>
// ),
// // only displayed on navbar, not mobile menu
// on: "nav",
// type: "custom",
// },
// other items
// ]}
themeSwitch={{
mode: "light-dark-system",
}}
>
<div aria-hidden="true" className="gradient-background home-gradient-background" />
{children}
</HomeLayout>
);
+46 -19
View File
@@ -1,25 +1,31 @@
import {buttonVariants} from "@heroui/react";
import LinkRoot from "fumadocs-core/link";
import Image from "next/image";
// TODO:
import {GitHubLink} from "@/components/github-link";
import {Iconify} from "@/components/iconify";
import {SocialLinks} from "@/components/social-links";
// import {ThemeBackground} from "@/components/theme-background";
import {currentVersion} from "@/utils/version";
import {DemoComponents} from "./components/demo";
const VersionChip = () => {
return (
<LinkRoot
className="chip bg-pink-400/8 hover:bg-default/40 rounded-full border-pink-400/20 dark:border-pink-400/10 dark:bg-white/10 dark:hover:bg-pink-400/20"
href="/docs/changelog"
className="text-muted chip bg-surface-secondary rounded-full text-xs"
href="/docs/changelog/v3-0-0-beta-1"
>
<Iconify className="text-pink-400/90" icon="sparkles" />
{/* <span className="bg-gradient-to-r from-[#CA8501] to-[#BD3232] bg-clip-text text-transparent"> */}
<span className="text-pink-400/90">v{currentVersion}: RSC Support</span>
<Iconify icon="gravity-ui:sparkles" />
<span>v{currentVersion}: Major Redesign</span>
</LinkRoot>
);
};
export const dynamic = "force-static";
export const revalidate = false;
export default function HomePage() {
return (
<main className="flex h-[calc(100vh-4rem)] flex-col">
@@ -27,29 +33,50 @@ export default function HomePage() {
<section className="z-10 flex flex-col items-center pt-20 text-center">
<div className="mx-auto flex max-w-2xl flex-col items-center justify-center gap-y-4">
<VersionChip />
<h1 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
The design system you don&apos;t have to build
<h1 className="text-foreground mt-2 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
Beautiful by default. Customizable by design.
</h1>
<p className="text-muted text-balance text-lg">
HeroUI is a set of beautiful, customizable, always up-to-date components that help you
skip the copy-paste and focus on building.
<p className="text-muted text-balance md:text-lg">
HeroUI is the modern UI library built to help you move fast, stay consistent, and
deliver delightful user experiences.
</p>
<div className="mt-2 flex gap-3">
<LinkRoot
className={buttonVariants({class: "shadow-lg", variant: "primary"})}
href="/docs"
>
Explore Docs
</LinkRoot>
<LinkRoot
className={buttonVariants({class: "bg-default/50", variant: "tertiary"})}
className={buttonVariants({variant: "primary"})}
href="/docs/components/accordion"
>
View Components
View components
</LinkRoot>
<GitHubLink>Stars</GitHubLink>
</div>
</div>
{/* <HomeThemeCustomizer /> */}
{/* Mobile/Tablet: Show images */}
<section className="-ml-4 mt-16 w-[100vw] overflow-hidden lg:hidden lg:w-[150vw]">
<Image
alt="HeroUI components preview"
className="block dark:hidden"
fetchPriority="high"
height={1592}
loading="eager"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/home-components-light.jpeg"
width={2528}
/>
<Image
alt="HeroUI components preview"
className="hidden dark:block"
fetchPriority="high"
height={1592}
loading="eager"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/home-components-dark.jpeg"
width={2528}
/>
</section>
{/* Desktop: Show demos */}
<div className="hidden lg:block">
<DemoComponents />
</div>
</section>
<footer className="text-muted mt-auto flex w-full flex-row flex-wrap items-center justify-center gap-2 py-3">
<p className="text-sm">
+95 -1
View File
@@ -1,5 +1,99 @@
import type {NextRequest} from "next/server";
import {createFromSource} from "fumadocs-core/search/server";
import {source} from "@/lib/source";
export const {GET} = createFromSource(source);
// Create a filtered source that excludes changelog pages
const filteredSource = {
...source,
getPages() {
return source.getPages().filter((page) => {
// Filter out changelog pages by checking slugs, URL, and path
const firstSlug = page.slugs[0];
const url = page.url;
const path = page.path;
return (
firstSlug !== "changelog" && !url.includes("/changelog") && !path.includes("changelog")
);
});
},
} as typeof source;
const {GET: originalGET} = createFromSource(filteredSource);
// Wrap the GET handler to filter out changelog results from the response
export async function GET(request: NextRequest) {
const response = await originalGET(request);
// Clone the response to read it without consuming the original
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("application/json")) {
// If it's not JSON, return as-is (might be a stream or other format)
return response;
}
try {
const data = await response.json();
// Helper function to check if an item is a changelog entry
const isChangelog = (item: any): boolean => {
const url = item?.url || item?.href || "";
const id = item?.id || "";
const title = item?.title || "";
const path = item?.path || "";
return (
url.includes("/changelog") ||
id.includes("/changelog") ||
path.includes("changelog") ||
title.toLowerCase().includes("changelog")
);
};
// Filter out changelog results from the search response
if (Array.isArray(data)) {
const filtered = data.filter((item: any) => !isChangelog(item));
return Response.json(filtered, {
headers: response.headers,
status: response.status,
statusText: response.statusText,
});
}
// If the response structure is different, try to filter nested results
if (data && typeof data === "object") {
const filtered: any = {};
for (const [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
filtered[key] = (value as any[]).filter((item: any) => !isChangelog(item));
} else if (value && typeof value === "object" && "results" in value) {
// Handle Orama-style search results
filtered[key] = {
...value,
results: Array.isArray((value as any).results)
? (value as any).results.filter((item: any) => !isChangelog(item))
: (value as any).results,
};
} else {
filtered[key] = value;
}
}
return Response.json(filtered, {
headers: response.headers,
status: response.status,
statusText: response.statusText,
});
}
return response;
} catch (error) {
// If parsing fails, return the original response
return response;
}
}
+1 -2
View File
@@ -16,7 +16,7 @@ export default function Layout({children}: {children: ReactNode}) {
defaultOpenLevel: 0,
}}
themeSwitch={{
mode: "light-dark",
mode: "light-dark-system",
}}
{...baseOptions}
nav={{
@@ -30,7 +30,6 @@ export default function Layout({children}: {children: ReactNode}) {
title: <HeroUILogo />,
}}
>
<div aria-hidden="true" className="gradient-background docs-gradient-background" />
{children}
</DocsLayout>
);
+183 -161
View File
@@ -20,7 +20,7 @@
/* Fumadocs UI Variables */
--fd-layout-width: 1400px;
--color-fd-background: #f9f9f9;
--color-fd-background: var(--background);
--color-fd-primary: oklch(0.14 0 0);
--color-fd-secondary: #f9f9f9;
--color-fd-card: #f9f9f9;
@@ -39,6 +39,10 @@
-moz-osx-font-smoothing: grayscale;
}
[data-theme="light"],
.light {
}
/* Dark Theme Variables */
[data-theme="dark"],
.dark {
@@ -48,9 +52,8 @@
/* Fumadocs UI Dark Theme */
--color-fd-primary: oklch(0.99 0 0);
--color-fd-background: #1a1a1a;
--color-fd-card: rgba(40, 40, 40, 0.4);
--color-fd-secondary: #1a1a1a;
--color-fd-secondary: #18181b;
--color-fd-accent: #2c2c2c;
--color-fd-popover: #383435;
--color-fd-border: rgba(255, 255, 255, 0.1);
@@ -66,10 +69,18 @@
@apply rounded-sm;
} */
/* Fumadocs UI Components Styles */
/* ============================================================================
FUMADOCS UI - NAVIGATION COMPONENTS
============================================================================ */
[data-theme-toggle] {
@apply bg-default/80 border-none;
}
/* Home Layout Navigation */
#nd-home-layout {
#nd-nav {
@apply bg-background blur-none backdrop-blur-none;
[data-theme-toggle] {
margin-left: 4px;
}
@@ -80,16 +91,7 @@
padding-top: 0;
}
[data-theme="dark"],
.dark {
#nd-nav,
#nd-subnav {
[data-theme-toggle] {
border: 1px solid rgba(255, 255, 255, 0.1);
}
}
}
/* Main Navigation */
#nd-nav {
border: none;
background-color: transparent;
@@ -111,10 +113,10 @@
}
[data-search-full] {
@apply bg-default border-border shadow-xs/5 rounded-xl;
@apply bg-default/80 rounded-field;
kbd {
@apply bg-default-hover border-border;
@apply bg-surface text-muted px-1 font-sans text-xs font-medium;
}
}
@@ -123,15 +125,26 @@
}
}
[data-theme="dark"],
.dark {
#nd-nav {
/* Sub Navigation */
#nd-subnav {
@apply bg-background h-14;
backdrop-filter: none;
> div {
@apply border-none px-6;
}
[data-search-full] {
background: rgba(10, 10, 10, 0.1);
@apply bg-field dark:bg-field/80 shadow-field rounded-field sm:max-w-[300px] md:max-w-[400px] lg:mr-12 dark:backdrop-blur-md;
kbd {
@apply bg-default dark:bg-surface border-border;
}
}
}
/* Sidebar */
#nd-sidebar {
@apply pt-0;
@@ -157,6 +170,8 @@
}
#nd-sidebar a {
@apply rounded-field;
transition: none;
&:hover {
@@ -164,19 +179,18 @@
}
&[data-active="true"] {
background: rgba(0, 0, 0, 0.08);
@apply bg-surface shadow-surface;
}
}
[data-theme="dark"],
.dark {
#nd-sidebar a {
&:hover {
background: rgba(255, 255, 255, 0.04);
}
/* Nested items inside open groups */
#nd-sidebar [data-state="open"] > [data-state="open"] {
padding: 4px 16px;
&[data-active="true"] {
background: rgba(255, 255, 255, 0.08);
a {
padding-left: 14px;
&::before {
display: none;
}
}
}
@@ -198,88 +212,13 @@
transition: opacity 0.2s;
}
[data-theme="dark"],
.dark {
#nd-sidebar a[rel="noreferrer noopener"]::after {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="10" height="10"><path d="M6 18L18 6m0 0H9m9 0v9" fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>');
}
}
#nd-sidebar a[rel="noreferrer noopener"]:hover::after {
opacity: 1;
}
#nd-subnav {
@apply h-14;
backdrop-filter: none;
> div {
@apply border-none px-6;
}
[data-search-full] {
@apply bg-default border-border shadow-xs/5 rounded-xl sm:max-w-[300px] md:max-w-[400px] lg:mr-12;
kbd {
@apply bg-default-hover border-border;
}
}
@media (max-width: 48rem) {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(16px) saturate(120%);
border: none !important;
}
}
[data-theme="dark"],
.dark {
#nd-subnav {
[data-search-full] {
background: rgba(10, 10, 10, 0.4);
backdrop-filter: blur(5px) saturate(120%);
}
@media (max-width: 48rem) {
background: rgba(10, 10, 10, 0.4);
}
}
}
.gradient-background {
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background-image: linear-gradient(
350deg,
hsl(0 0% 98%) 0%,
hsl(11 15% 95%) 24%,
hsl(11 21% 92%) 61%,
hsl(353 21% 92%) 81%,
hsl(318 16% 93%) 90%,
hsl(228 26% 96%) 100%
);
}
[data-theme="dark"],
.dark {
.gradient-background {
background-image: linear-gradient(
340deg,
hsl(0, 33%, 0%) 0%,
hsl(0, 33%, 8%) 40%,
hsl(284, 22%, 10%) 70%,
hsl(233deg 24% 8%) 100%
);
}
}
/* ============================================================================
FUMADOCS UI - LAYOUT COMPONENTS
============================================================================ */
#nd-docs-layout {
position: relative;
@@ -308,13 +247,21 @@
}
}
.component-preview-tabs {
[role="tab"] {
border-bottom: none;
.component-preview-container {
/* Preview and code are now displayed vertically */
.code-section {
.code-block-wrapper {
@apply rounded-none rounded-b-xl;
}
}
}
}
/* ============================================================================
FUMADOCS UI - TABLE OF CONTENTS
============================================================================ */
#nd-toc {
padding-top: 10px;
right: 0;
@@ -333,29 +280,26 @@
#nd-tocnav {
border-radius: 0rem;
top: calc(var(--fd-banner-height) + var(--fd-nav-height) - 6px) !important;
top: calc(var(--fd-banner-height) + var(--fd-nav-height) - 12px) !important;
margin-right: 0rem;
margin-left: 0rem;
margin-top: 0px;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(16px) saturate(120%) !important;
background: var(--background);
backdrop-filter: none !important;
border-left: none !important;
border-right: none !important;
border-top: 1px solid var(--border) !important;
border-bottom: 1px solid var(--border) !important;
border-bottom: 1px solid var(--divider) !important;
@media (min-width: 600px) {
margin-right: 1rem;
border-bottom: 0px solid var(--divider) !important;
}
}
[data-theme="dark"],
.dark {
#nd-tocnav {
background: rgba(10, 10, 10, 0.4);
border: 1px solid rgba(60, 60, 60, 0.5);
}
}
/* ============================================================================
PROSE & CONTENT STYLES
============================================================================ */
.prose {
.shiki {
@@ -394,30 +338,51 @@
max-height: fit-content;
}
[data-theme="dark"],
.dark {
.prose {
.shiki {
background: transparent;
[role="tabpanel"] .mdx-code-block {
padding: 0;
}
figure {
background: red;
code {
@apply border-divider bg-transparent px-[3px];
}
/* Table styles for .prose-no-margin */
.prose-no-margin {
table {
@apply border-none bg-transparent;
border-collapse: separate;
border-spacing: 0;
thead {
tr {
th {
@apply bg-default dark:bg-default/50 border-none;
&:first-child {
@apply rounded-bl-xl rounded-tl-xl;
}
/* Scroll container */
.fd-scroll-container {
background: rgba(40, 40, 40, 0.4);
&:last-child {
@apply rounded-br-xl rounded-tr-xl;
}
}
}
}
.docs-code-block {
.fd-scroll-container {
background: transparent !important;
tbody {
tr {
td {
@apply border-none;
}
}
}
}
}
/* ============================================================================
CODE BLOCK STYLES
============================================================================ */
.docs-code-block {
@apply m-0 rounded-none border-none pl-2 pr-2 pt-4;
@@ -430,29 +395,13 @@
}
}
.shiki:not(.not-fumadocs-codeblock *) code .highlighted {
border-radius: 4px;
background-color: #d6d8e3 !important;
}
[data-theme="dark"],
.dark {
.shiki:not(.not-fumadocs-codeblock *) code .highlighted {
background-color: #2d2d2d !important;
}
/* Apply !important only for isolated code blocks (not code-block-wrapper) */
.is-isolated .docs-code-block {
background: rgba(0, 0, 0, 0.04) !important;
}
.code-block-wrapper {
@apply rounded-xl;
background: rgba(0, 0, 0, 0.04);
}
[data-theme="dark"],
.dark {
.code-block-wrapper {
background: rgba(40, 40, 40, 0.4);
}
}
.docs-code-block-line-numbers {
@@ -472,7 +421,26 @@
}
}
/* Scrolling Banner Animations */
.shiki:not(.not-fumadocs-codeblock *) code .highlighted {
border-radius: 4px;
background-color: #d6d8e3 !important;
}
.showcase-source-wrapper {
.code-block-wrapper {
background: transparent !important;
}
.fd-scroll-container {
max-height: 100%;
background: transparent;
}
}
/* ============================================================================
ANIMATIONS & KEYFRAMES
============================================================================ */
@keyframes scrolling-banner {
from {
transform: translateX(0);
@@ -517,7 +485,10 @@
}
}
/* Animation utility classes */
/* ============================================================================
UTILITY CLASSES
============================================================================ */
.animate-scrolling-banner {
animation: scrolling-banner var(--duration) linear infinite;
}
@@ -534,18 +505,69 @@
mask: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 1) 70%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 0) 100%
);
}
.showcase-source-wrapper {
.code-block-wrapper {
background: transparent !important;
/* ============================================================================
DARK THEME OVERRIDES
============================================================================ */
[data-theme="dark"],
.dark {
/* Navigation Components */
#nd-sidebar a {
&:hover {
background: rgba(255, 255, 255, 0.04);
}
.fd-scroll-container {
max-height: 100%;
&[data-active="true"] {
background: rgba(255, 255, 255, 0.08);
}
}
#nd-sidebar a[rel="noreferrer noopener"]::after {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="10" height="10"><path d="M6 18L18 6m0 0H9m9 0v9" fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>');
}
/* Table of Contents */
#nd-tocnav {
background: rgba(10, 10, 10, 0.4);
border: 1px solid rgba(60, 60, 60, 0.5);
}
/* Prose & Code Blocks */
.prose {
.shiki {
background: transparent;
figure {
background: red;
}
/* Scroll container */
.fd-scroll-container {
background: rgba(40, 40, 40, 0.4);
}
}
}
.docs-code-block {
background: rgba(40, 40, 40, 0.4);
.fd-scroll-container {
background: transparent !important;
}
}
/* Apply !important only for isolated code blocks (not code-block-wrapper) */
.is-isolated .docs-code-block {
background: rgba(40, 40, 40, 0.4) !important;
}
.shiki:not(.not-fumadocs-codeblock *) code .highlighted {
background-color: #2d2d2d !important;
}
}
-1
View File
@@ -10,7 +10,6 @@ import {HeroUILogo} from "@/components/heroui-logo";
* Docs Layout: app/docs/layout.tsx
*/
export const baseOptions: BaseLayoutProps = {
githubUrl: "https://github.com/heroui-inc/heroui",
nav: {
title: <HeroUILogo />,
transparentMode: "always",
+5 -2
View File
@@ -81,6 +81,10 @@ export const metadata: Metadata = {
type: "website",
url: "/",
},
robots: {
follow: true,
index: true,
},
title: {
default: siteConfig.name,
template: `%s | ${siteConfig.name}`,
@@ -96,11 +100,10 @@ export const metadata: Metadata = {
export const viewport: Viewport = {
initialScale: 1,
maximumScale: 1,
maximumScale: 5,
themeColor: [
{color: "#f4f4f5", media: "(prefers-color-scheme: light)"},
{color: "#111111", media: "(prefers-color-scheme: dark)"},
],
userScalable: false,
width: "device-width",
};
+3 -6
View File
@@ -31,11 +31,8 @@ function generate({...props}: GenerateProps): ReactElement {
return (
<div
style={{
backgroundImage: `
linear-gradient(350deg, #000 0%, rgba(31, 20, 21, .7) 15%, rgba(41, 26, 27, .6) 30%, rgba(37, 24, 28, .65) 40%, rgba(33, 23, 29, .65) 50%, rgba(30, 21, 30, .65) 60%, #1c151e 70%, #18131b 78%, #15121c 85%, #111019 92%, #101119 100%),
linear-gradient(350deg, #000 0%, #291a1b 40%, #1c151e 70%, #15121c 85%, #101119 100%)
`,
color: "#FAFAFA",
background: "#F5F5F5",
color: "#18181B",
display: "flex",
flexDirection: "column",
height: "100%",
@@ -56,7 +53,7 @@ function generate({...props}: GenerateProps): ReactElement {
{/* Description */}
<p
style={{
color: "#A1A1A1",
color: "#71717A",
fontSize: "52px",
}}
>
@@ -75,7 +75,7 @@ export function ShowcaseCodePanel({
initial={{opacity: 0, x: "100%"}}
transition={{bounce: 0, duration: 0.4, type: "spring"}}
className={cn(
"bg-surface-2/94 absolute right-6 z-[9999] h-full max-h-[88%] w-[45%] overflow-hidden rounded-xl shadow-xl backdrop-blur-md",
"bg-surface/94 absolute right-6 z-[9999] h-full max-h-[88%] w-[45%] overflow-hidden rounded-xl shadow-xl backdrop-blur-md",
className,
)}
>
+1 -1
View File
@@ -65,7 +65,7 @@ export function LLMCopyButton({
<button
disabled={isLoading}
className={docsButtonVariants({
className: "[&_svg]:text-muted [&_svg]:size-3.5",
className: "[&_svg]:text-muted text-sm [&_svg]:size-3.5",
})}
onClick={onClick}
>
@@ -12,10 +12,12 @@ export function CodeBlock({
children,
className,
collapsible,
isIsolated = false,
showLineNumbers,
title,
...props
}: {
isIsolated?: boolean;
lang?: string;
collapsible?: boolean;
showLineNumbers?: boolean;
@@ -30,6 +32,7 @@ export function CodeBlock({
title={title}
className={cn(
"code-block-wrapper docs-code-block",
isIsolated && "is-isolated",
showLineNumbers && "docs-code-block-line-numbers",
className,
)}
@@ -45,7 +48,8 @@ export function CodeBlock({
<div
className={cn(
"code-block-wrapper",
isCollapsed && "mask-to-bottom relative max-h-[300px] overflow-hidden",
isIsolated && "is-isolated",
isCollapsed && "mask-to-bottom relative max-h-[150px] overflow-hidden",
!isCollapsed && "pb-10",
)}
>
@@ -62,10 +66,10 @@ export function CodeBlock({
</Base.CodeBlock>
</div>
<Button
className={cn("absolute bottom-0 right-1/2 translate-x-1/2", !isCollapsed && "bottom-4")}
className="bg-surface absolute bottom-2 right-1/2 translate-x-1/2 text-xs shadow-sm shadow-black/5"
size="sm"
type="button"
variant="secondary"
variant="tertiary"
onPress={() => setIsCollapsed(!isCollapsed)}
>
{isCollapsed ? "Expand code" : "Collapse code"}
+4
View File
@@ -10,6 +10,7 @@ export async function CodeBlock({
className,
code,
collapsible,
isIsolated = false,
lang,
showLineNumbers,
title,
@@ -17,6 +18,8 @@ export async function CodeBlock({
}: {
code: string;
lang: string;
// Means the code block is rendered directly in the MDX file using <CollapsibleCode>
isIsolated?: boolean;
showLineNumbers?: boolean;
title: string | undefined;
collapsible?: boolean;
@@ -57,6 +60,7 @@ export async function CodeBlock({
<CodeBlockClient
className={className}
collapsible={collapsible}
isIsolated={isIsolated}
lang={lang}
showLineNumbers={showLineNumbers}
title={title}
@@ -17,6 +17,7 @@ export function CollapsibleCode({
}) {
return (
<CodeBlock
isIsolated
className={className}
code={code}
collapsible={collapsible}
+2 -2
View File
@@ -83,7 +83,7 @@ export function ColorSwatch({className, foreground, name, value, variable}: Colo
return (
<div className={cn("flex flex-col items-center gap-2", className)}>
<button
className="border-border focus:ring-accent group relative size-20 rounded-lg border shadow-sm transition-all hover:scale-105 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2"
className="focus:ring-accent shadow-surface group relative size-10 rounded-full transition-all hover:scale-105 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2"
type="button"
style={{
backgroundColor: `var(${variable})`,
@@ -111,7 +111,7 @@ export function ColorSwatch({className, foreground, name, value, variable}: Colo
</div>
</button>
<div className="text-center">
<div className="text-xs font-medium">{name}</div>
<div className="text-xs font-medium">{variable}</div>
</div>
</div>
);
+1 -2
View File
@@ -17,8 +17,7 @@ export function ComponentItem({className, component, ...props}: ComponentItemPro
<a className="block" href={href} rel="noopener noreferrer" target="_blank">
<div
className={cn(
"border-border/60 group relative flex h-full flex-col gap-3 rounded-xl border bg-transparent p-5 py-4 transition-all duration-200",
"hover:border-border hover:bg-foreground/2",
"bg-surface group relative flex h-full flex-col gap-3 rounded-xl p-5 py-4",
className,
)}
{...props}
+1 -1
View File
@@ -54,7 +54,7 @@ export const ComponentLinks = ({links}: ComponentLinksProps) => {
) : null}
{componentLinks.storybook ? (
<ButtonLink
href={`${componentLinks.storybook}--default`}
href={`${componentLinks.storybook}--docs`}
startContent={<StorybookIcon className="text-lg text-[#ff4785]" />}
>
Storybook
@@ -0,0 +1,101 @@
"use client";
import React from "react";
import {useIntersectionObserver} from "usehooks-ts";
import {cn} from "@/utils/cn";
interface ComponentPreviewContainerProps extends React.HTMLAttributes<HTMLDivElement> {
align?: "center" | "start" | "end";
isBgSolid?: boolean;
description?: string;
hideCode?: boolean;
name: string;
}
export function ComponentPreviewContainer({
align = "center",
children,
className,
description,
hideCode = false,
isBgSolid = false,
name,
...props
}: React.PropsWithChildren<ComponentPreviewContainerProps>) {
const [Component, Code] = React.Children.toArray(children) as React.ReactElement[];
const {isIntersecting: previewInView, ref: previewRef} = useIntersectionObserver({
initialIsIntersecting: true,
rootMargin: "400px 0px 100px 0px",
threshold: 0,
});
const {isIntersecting: codeInView, ref: codeRef} = useIntersectionObserver({
initialIsIntersecting: true,
rootMargin: "300px 0px 0px 0px",
threshold: 0,
});
const alignmentClasses = {
center: "items-center justify-center",
end: "items-end justify-end",
start: "items-start justify-start",
};
return (
<div
className={cn("component-preview-container group relative my-4 w-full", className)}
data-name={name}
{...props}
>
{!!description && <p className="text-muted-foreground mb-2 text-sm">{description}</p>}
{/* Preview Section */}
<div
ref={previewRef}
data-name={name}
className={cn(
"preview not-prose border-divider relative min-h-[350px] w-full overflow-hidden rounded-t-xl border-l border-r border-t p-4 sm:p-10",
isBgSolid && "bg-background",
alignmentClasses[align],
"flex",
)}
style={{
contain: "layout style",
contentVisibility: "auto",
isolation: "isolate",
}}
>
<div
className="flex w-full items-center justify-center"
style={{
pointerEvents: previewInView ? "auto" : "none",
visibility: previewInView ? "visible" : "hidden",
}}
>
{Component}
</div>
</div>
{/* Code Section */}
{!hideCode && !!Code && (
<div
ref={codeRef}
className="code-section border-divider relative rounded-b-xl border bg-transparent"
style={{
contentVisibility: "auto",
}}
>
<div
className="code-block-wrapper min-h-[124px]"
style={{
pointerEvents: codeInView ? "auto" : "none",
visibility: codeInView ? "visible" : "hidden",
}}
>
{Code}
</div>
</div>
)}
</div>
);
}
@@ -1,73 +0,0 @@
import {Tab, Tabs} from "fumadocs-ui/components/tabs";
import React from "react";
import {cn} from "@/utils/cn";
interface ComponentPreviewTabsProps extends React.HTMLAttributes<HTMLDivElement> {
align?: "center" | "start" | "end";
isBgSolid?: boolean;
description?: string;
hideCode?: boolean;
name: string;
}
export function ComponentPreviewTabs({
align = "center",
children,
className,
description,
hideCode = false,
isBgSolid = false,
name,
...props
}: React.PropsWithChildren<ComponentPreviewTabsProps>) {
const [Component, Code] = React.Children.toArray(children) as React.ReactElement[];
const alignmentClasses = {
center: "items-center justify-center",
end: "items-end justify-end",
start: "items-start justify-start",
};
return (
<div
className={cn("component-preview-tabs group relative my-4 w-full", className)}
data-name={name}
{...props}
>
{!!description && <p className="text-muted-foreground mb-2 text-sm">{description}</p>}
<Tabs
className="relative w-full border-none bg-transparent p-0"
items={hideCode ? ["Preview"] : ["Preview", "Code"]}
>
<Tab className="border-border overflow-hidden border bg-transparent p-0" value="Preview">
<div
data-name={name}
className={cn(
"preview not-prose relative min-h-[350px] w-full overflow-hidden rounded-md p-4 before:absolute before:inset-0 before:z-[-1] before:bg-white/40 sm:p-10 dark:before:bg-black/25",
isBgSolid && "bg-background",
alignmentClasses[align],
"flex",
)}
style={{
contain: "layout style",
isolation: "isolate",
}}
>
{Component}
</div>
</Tab>
{!hideCode && !!Code && (
<Tab className="bg-transparent p-0" value="Code">
<div className="relative">
<div className="code-block-wrapper max-h-[650px] min-h-[350px] overflow-auto">
{Code}
</div>
</div>
</Tab>
)}
</Tabs>
</div>
);
}
@@ -4,7 +4,7 @@ import * as React from "react";
import {getDemo} from "@/demos";
import {cn} from "@/utils/cn";
import {ComponentPreviewTabs} from "./component-preview-tabs";
import {ComponentPreviewContainer} from "./component-preview-container";
import {ComponentSource} from "./component-source";
interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -40,7 +40,7 @@ export function ComponentPreview({
const Component = demo.component;
return (
<ComponentPreviewTabs
<ComponentPreviewContainer
align={align}
className={className}
description={description}
@@ -51,6 +51,6 @@ export function ComponentPreview({
>
<Component />
{!hideCode && !!demo.file && <ComponentSource language="tsx" name={name} title={name} />}
</ComponentPreviewTabs>
</ComponentPreviewContainer>
);
}
@@ -11,7 +11,7 @@ import {CodeBlock} from "./codeblock";
export async function ComponentSource({
className,
// TODO: Disclosure is not ready yet, so we need to use collapsible for now
collapsible = false,
collapsible = true,
language,
name,
showCodeTitle = false,
+29 -10
View File
@@ -1,32 +1,44 @@
import React from "react";
import Image from "next/image";
interface DocsImageProps {
src: string;
darkSrc?: string;
alt: string;
className?: string;
width?: number;
height?: number;
priority?: boolean;
}
export function DocsImage({
alt,
className = "h-[220px] md:h-[340px]",
darkSrc,
height = 1000,
priority = true,
src,
width = 1000,
}: DocsImageProps) {
const wrapperClasses = `not-prose relative w-full overflow-hidden rounded-xl shadow-panel ${className}`;
const wrapperClasses = `not-prose border border-divider relative w-full overflow-hidden rounded-xl ${className}`;
if (darkSrc) {
return (
<div className={wrapperClasses}>
<img
<Image
alt={alt}
className="absolute inset-0 h-full w-full object-cover opacity-0 transition-opacity duration-300 ease-in-out dark:opacity-100"
src={darkSrc}
/>
<img
alt={alt}
className="absolute inset-0 h-full w-full object-cover opacity-100 transition-opacity duration-300 ease-in-out dark:opacity-0"
className="absolute inset-0 block h-full w-full object-cover dark:hidden"
height={height}
priority={priority}
src={src}
width={width}
/>
<Image
alt={alt}
className="absolute inset-0 hidden h-full w-full object-cover dark:block"
height={height}
priority={priority}
src={darkSrc}
width={width}
/>
</div>
);
@@ -34,7 +46,14 @@ export function DocsImage({
return (
<div className={wrapperClasses}>
<img alt={alt} className="absolute inset-0 h-full w-full object-cover" src={src} />
<Image
alt={alt}
className="absolute inset-0 h-full w-full object-cover"
height={height}
priority={priority}
src={src}
width={width}
/>
</div>
);
}
+48
View File
@@ -0,0 +1,48 @@
import {Skeleton, buttonVariants} from "@heroui/react";
import * as React from "react";
import {siteConfig} from "@/config/site";
import {GitHubIcon} from "@/icons/github";
import {cn} from "@/utils/cn";
import {GITHUB_API_URL} from "@/utils/constants";
export function GitHubLink({
children,
className,
}: {
className?: string;
children?: React.ReactNode;
}) {
return (
<a
href={siteConfig.links.github}
rel="noreferrer"
target="_blank"
className={buttonVariants({
className: cn("bg-surface-tertiary", className),
variant: "tertiary",
})}
>
<GitHubIcon />
{children}
<React.Suspense fallback={<Skeleton className="h-4 w-8" />}>
<StarsCount />
</React.Suspense>
</a>
);
}
export async function StarsCount() {
const data = await fetch(`${GITHUB_API_URL}/repos/${siteConfig.githubRepo}`, {
next: {revalidate: 86400}, // Cache for 1 day (86400 seconds)
});
const json = await data.json();
return (
<span className="text-muted pt-px text-xs font-medium">
{json.stargazers_count >= 1000
? `${(json.stargazers_count / 1000).toFixed(1)}k`
: json.stargazers_count.toLocaleString()}
</span>
);
}
+6 -5
View File
@@ -1,5 +1,3 @@
"use client";
import type {IconProps} from "@iconify/react";
import {Icon} from "@iconify/react";
@@ -34,15 +32,17 @@ const icons = {
...customIcons,
};
// TODO: Hydration error
const Iconify = forwardRef<SVGSVGElement, IconifyProps>(({icon: iconProp, ...props}, ref) => {
// Check if it's a gravity-ui icon (no prefix or explicitly in gravity icons)
const isGravityIcon =
typeof iconProp === "string" && (iconProp in icons || !iconProp.includes(":"));
typeof iconProp === "string" && (iconProp in icons || iconProp.startsWith("gravity-ui:"));
// Only use offline icon after hydration to avoid mismatch
if (isGravityIcon && typeof iconProp === "string") {
// Use offline version with gravity-ui icons
const gravityIconData = icons[iconProp as keyof typeof icons];
// Remove "gravity-ui:" prefix if present
const iconName = iconProp.replace(/^gravity-ui:/, "");
const gravityIconData = icons[iconName as keyof typeof icons];
if (gravityIconData) {
return <OfflineIcon {...props} ref={ref} icon={gravityIconData} />;
@@ -50,6 +50,7 @@ const Iconify = forwardRef<SVGSVGElement, IconifyProps>(({icon: iconProp, ...pro
}
// Use online version for other icon sets (like simple-icons:vite, lineicons:nextjs)
// Also use during SSR to ensure consistent rendering
return <Icon {...props} ref={ref} icon={iconProp} />;
});
+39 -36
View File
@@ -1,6 +1,6 @@
"use client";
import {Button, Spinner} from "@heroui/react";
import {Button, FieldError, Form, Input, Label, Spinner, TextField} from "@heroui/react";
import {AnimatePresence, LazyMotion, domAnimation} from "motion/react";
import * as m from "motion/react-m";
import React from "react";
@@ -8,7 +8,7 @@ import React from "react";
export function NewsletterForm() {
const [email, setEmail] = React.useState("");
const [status, setStatus] = React.useState<"idle" | "loading" | "success" | "error">("idle");
const [errorMessage, setErrorMessage] = React.useState("Something went wrong. Please try again.");
const [apiErrorMessage, setApiErrorMessage] = React.useState<string | null>(null);
const circleCheckIcon = (
<svg fill="none" height="16" viewBox="0 0 17 16" width="17" xmlns="http://www.w3.org/2000/svg">
@@ -40,20 +40,21 @@ export function NewsletterForm() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
setStatus("error");
setErrorMessage("Please enter a valid email.");
const formData = new FormData(e.currentTarget);
const emailValue = formData.get("email") as string;
if (!emailValue) {
return;
}
setStatus("loading");
setApiErrorMessage(null);
try {
// Loops - newsletter
const newsletterResponse = await fetch("/api/newsletter", {
body: JSON.stringify({
email,
email: emailValue,
source: "Subscribe from HeroUI Docs",
}),
headers: {
@@ -71,7 +72,7 @@ export function NewsletterForm() {
// Featurebase - changelog
const changelogResponse = await fetch("/api/changelog", {
body: JSON.stringify({
email,
email: emailValue,
}),
headers: {
"Content-Type": "application/json",
@@ -90,7 +91,7 @@ export function NewsletterForm() {
setTimeout(() => setStatus("idle"), 3000);
} catch (error) {
setStatus("error");
setErrorMessage(
setApiErrorMessage(
error instanceof Error ? error.message : "Something went wrong. Please try again.",
);
setTimeout(() => setStatus("idle"), 3000);
@@ -100,36 +101,38 @@ export function NewsletterForm() {
return (
<div className="relative w-full py-3">
<div className="relative h-[172px] w-full">
<form noValidate className="flex flex-col gap-2" onSubmit={handleSubmit}>
<div className="flex w-full flex-col gap-1">
<label
className={`pb-1 text-sm font-medium tracking-[-0.07px] ${status === "error" ? "text-[#e44c3d] dark:text-[#eb5545]" : "text-muted"}`}
htmlFor="newsletter-email"
>
Hero Newsletter
</label>
<div className="relative w-full">
<input
required
disabled={status === "loading"}
id="newsletter-email"
placeholder="name@email.com"
<Form className="flex flex-col gap-2" onSubmit={handleSubmit}>
<TextField
isRequired
className="flex w-full flex-col gap-1"
isDisabled={status === "loading"}
name="email"
type="email"
value={email}
className={`text-foreground placeholder:text-foreground-muted/80 min-h-8 w-full rounded-xl border px-3 py-2 text-[14px] tracking-[-0.28px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.05),0px_1px_1px_0px_inset_rgba(255,255,255,0.1)] transition-all duration-150 ease-out focus:outline-none ${
status === "error"
? "border-danger bg-white/0 dark:bg-black/0"
: "border-black/[0.04] bg-black/[0.07] hover:bg-black/[0.1] dark:bg-white/[0.14] dark:hover:bg-white/[0.18]"
}`}
onChange={(e) => setEmail(e.target.value)}
/>
{status === "error" && (
<p className="text-danger mt-1 px-1 text-xs">{errorMessage}</p>
)}
</div>
</div>
validate={(value) => {
if (!value) {
return "Email is required";
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return "Please enter a valid email.";
}
return null;
}}
onChange={setEmail}
>
<Label className="pb-1 text-sm font-medium tracking-[-0.07px] after:hidden">
Hero Newsletter
</Label>
<Input placeholder="name@email.com" value={email} />
<FieldError className="mt-1 px-1 text-xs" />
{apiErrorMessage && status === "error" ? (
<p className="text-danger mt-1 px-1 text-xs">{apiErrorMessage}</p>
) : null}
</TextField>
<Button
className="overflow-clip border-black/[0.1] bg-[#FAFAFA]/[0.7] text-sm transition-all hover:bg-[#F5F5F5] dark:border-white/[0.1] dark:bg-[#171717]/[0.7] dark:hover:bg-[#262626]"
className="w-full"
isPending={status === "loading" || status === "success"}
type="submit"
variant="tertiary"
@@ -152,7 +155,7 @@ export function NewsletterForm() {
</AnimatePresence>
</LazyMotion>
</Button>
</form>
</Form>
</div>
</div>
);
+159
View File
@@ -0,0 +1,159 @@
"use client";
import type {FC} from "react";
import {Button, Spinner, Tooltip, cn} from "@heroui/react";
import {useCallback, useEffect, useRef, useState} from "react";
import {useIntersectionObserver} from "usehooks-ts";
import {useIsMobileDevice} from "@/hooks/use-is-mobile-device";
import {Iconify} from "./iconify";
interface VideoPlayerProps {
src: string;
playMode?: "auto" | "manual";
autoPlay?: boolean;
poster?: string;
width?: number;
height?: number;
controls?: boolean;
className?: string;
}
export const VideoPlayer: FC<VideoPlayerProps> = ({
autoPlay = true,
className,
controls = false,
height,
playMode = "auto",
poster,
src,
width,
}) => {
const [isLoading, setIsLoading] = useState(true);
const [isPlaying, setIsPlaying] = useState(false);
const isMobile = useIsMobileDevice();
const videoRef = useRef<HTMLVideoElement>(null);
const {isIntersecting: isVisible, ref: intersectionRef} = useIntersectionObserver({
threshold: 0.5,
});
// Merge refs: videoRef for video operations and intersectionRef for intersection observer
const setVideoRef = useCallback(
(element: HTMLVideoElement | null) => {
videoRef.current = element;
if (element) {
if (typeof intersectionRef === "function") {
intersectionRef(element);
} else if (intersectionRef && "current" in intersectionRef) {
(intersectionRef as React.MutableRefObject<HTMLVideoElement | null>).current = element;
}
}
},
[intersectionRef],
);
// Determine effective play mode: force manual on mobile devices
const effectivePlayMode = isMobile ? "manual" : playMode;
// play video when it is visible and playMode is auto (only on non-mobile devices)
useEffect(() => {
if (effectivePlayMode !== "auto" || !videoRef.current) {
return;
}
if (isVisible) {
videoRef.current.play();
setIsPlaying(true);
} else {
videoRef.current.pause();
setIsPlaying(false);
}
}, [isVisible, effectivePlayMode]);
const handleCanPlay = useCallback(() => {
setIsLoading(false);
}, []);
useEffect(() => {
const videoEl = videoRef.current;
if (videoEl) {
if (videoEl.readyState > 3) {
// HAVE_FUTURE_DATA: enough data to start playing
handleCanPlay();
} else {
videoEl.addEventListener("canplaythrough", handleCanPlay);
}
// Cleanup the event listener
return () => {
videoEl.removeEventListener("canplaythrough", handleCanPlay);
};
}
}, [handleCanPlay]);
const onTogglePlay = useCallback(() => {
if (videoRef.current) {
if (!isPlaying) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
setIsPlaying((v) => !v);
}
}, [isPlaying]);
return (
<div
className="not-prose border-divider relative overflow-hidden rounded-xl border"
data-playing={isPlaying}
>
{isLoading ? (
<Spinner
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
color="accent"
size="lg"
/>
) : !isPlaying ? (
<Tooltip delay={1000}>
<Tooltip.Trigger>
<Button
isIconOnly
className="absolute left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 bg-transparent before:absolute before:inset-0 before:z-[-1] before:block before:rounded-lg before:bg-black/10 before:backdrop-blur-md before:backdrop-saturate-150 before:content-['']"
size="sm"
variant="tertiary"
onPress={onTogglePlay}
>
{isPlaying ? (
<Iconify icon="gravity-ui:pause-fill" size={16} />
) : (
<Iconify icon="gravity-ui:play-fill" size={16} />
)}
</Button>
</Tooltip.Trigger>
<Tooltip.Content>{isPlaying ? "Pause" : "Play"}</Tooltip.Content>
</Tooltip>
) : null}
<video
ref={setVideoRef}
loop
muted
playsInline
autoPlay={!!autoPlay && effectivePlayMode === "auto"}
className={cn("object-fit aspect-video w-full", className)}
controls={controls}
height={height}
poster={poster}
src={src}
width={width}
onCanPlay={handleCanPlay}
/>
</div>
);
};
+2
View File
@@ -13,6 +13,8 @@ export const siteConfig = {
figmaCommunityFile: "https://www.figma.com/community/file/1546526812159103429",
githubRawUrl:
"https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs",
githubRepo: "heroui-inc/heroui",
githubUrl: "https://github.com/heroui-inc/heroui",
links: {
discord: "https://discord.gg/9b6yyZKmH4",
github: "https://github.com/heroui-inc",
+2 -4
View File
@@ -1,5 +1,3 @@
"use client";
import {Accordion} from "@heroui/react";
import {Icon} from "@iconify/react";
@@ -43,7 +41,7 @@ const items = [
export function Basic() {
return (
<Accordion.Root className="w-full max-w-md">
<Accordion className="w-full max-w-md">
{items.map((item, index) => (
<Accordion.Item key={index}>
<Accordion.Heading>
@@ -62,6 +60,6 @@ export function Basic() {
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion.Root>
</Accordion>
);
}

Some files were not shown because too many files have changed in this diff Show More