106k
New

October 2025 - New Components

Spinner, Kbd, Button Group, Input Group, Field, Item, and Empty components.

For this round of components, I looked at what we build every day, the boring stuff we rebuild over and over, and made reusable abstractions you can actually use.

These components work with every component library, Radix, Base UI, React Aria, you name it. Copy and paste to your projects.

  • Spinner: An indicator to show a loading state.
  • Kbd: Display a keyboard key or group of keys.
  • Button Group: A group of buttons for actions and split buttons.
  • Input Group: Input with icons, buttons, labels and more.
  • Field: One component. All your forms.
  • Item: Display lists of items, cards, and more.
  • Empty: Use this one for empty states.

Spinner

Okay let's start with the easiest ones: Spinner and Kbd. Pretty basic. We all know what they do.

Here's how you render a spinner:

import { Spinner } from "@/components/ui/spinner"
<Spinner />

Here's what it looks like:

import { Spinner } from "@/components/ui/spinner"

export function SpinnerBasic() {

Here's what it looks like in a button:

import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"

You can edit the code and replace it with your own spinner.

import { LoaderIcon } from "lucide-react"

import { cn } from "@/lib/utils"

Kbd

Kbd is a component that renders a keyboard key.

import { Kbd, KbdGroup } from "@/components/ui/kbd"
<Kbd>Ctrl</Kbd>

Use KbdGroup to group keyboard keys together.

<KbdGroup>
  <Kbd>Ctrl</Kbd>
  <Kbd>B</Kbd>
</KbdGroup>
⌘⇧⌥⌃Ctrl+B
import { Kbd, KbdGroup } from "@/components/ui/kbd"

export function KbdDemo() {

You can add it to buttons, tooltips, input groups, and more.

Button Group

I got a lot of requests for this one: Button Group. It's a container that groups related buttons together with consistent styling. Great for action groups, split buttons, and more.

"use client"

import * as React from "react"

Here's the code:

import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup>
  <Button>Button 1</Button>
  <Button>Button 2</Button>
</ButtonGroup>

You can nest button groups to create more complex layouts with spacing.

<ButtonGroup>
  <ButtonGroup>
    <Button>Button 1</Button>
    <Button>Button 2</Button>
  </ButtonGroup>
  <ButtonGroup>
    <Button>Button 3</Button>
    <Button>Button 4</Button>
  </ButtonGroup>
</ButtonGroup>

Use ButtonGroupSeparator to create split buttons. Classic dropdown pattern.

"use client"

import {

You can also use it to add prefix or suffix buttons and text to inputs.

"use client"

import * as React from "react"
<ButtonGroup>
  <ButtonGroupText>Prefix</ButtonGroupText>
  <Input placeholder="Type something here..." />
  <Button>Button</Button>
</ButtonGroup>

Input Group

Input Group lets you add icons, buttons, and more to your inputs. You know, all those little bits you always need around your inputs.

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group"
<InputGroup>
  <InputGroupInput placeholder="Search..." />
  <InputGroupAddon>
    <SearchIcon />
  </InputGroupAddon>
</InputGroup>

Here's a preview with icons:

import {
  CheckIcon,
  CreditCardIcon,

You can also add buttons to the input group.

https://
"use client"

import * as React from "react"

Or text, labels, tooltips,...

$
USD
https://
.com
@company.com
120 characters left
import {
  InputGroup,
  InputGroupAddon,

It also works with textareas so you can build really complex components with lots of knobs and dials or yet another prompt form.

Line 1, Column 1
script.js
import {
  IconBrandJavascript,
  IconCopy,

Oh here are some cool ones with spinners:

Saving...
Please wait...
import { LoaderIcon } from "lucide-react"

import {

Field

Introducing Field, a component for building really complex forms. The abstraction here is beautiful.

It took me a long time to get it right but I made it work with all your form libraries: Server Actions, React Hook Form, TanStack Form, Bring Your Own Form.

import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/components/ui/field"

Here's a basic field with an input:

<Field>
  <FieldLabel htmlFor="username">Username</FieldLabel>
  <Input id="username" placeholder="Max Leiter" />
  <FieldDescription>
    Choose a unique username for your account.
  </FieldDescription>
</Field>

Choose a unique username for your account.

Must be at least 8 characters long.

import {
  Field,
  FieldDescription,

It works with all form controls. Inputs, textareas, selects, checkboxes, radios, switches, sliders, you name it. Here's a full example:

Payment Method

All transactions are secure and encrypted

Enter your 16-digit card number

Billing Address

The billing address associated with your payment method

import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {

Here are some checkbox fields:

Show these items on the desktop

Select the items you want to show on the desktop.

Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.

import { Checkbox } from "@/components/ui/checkbox"
import {
  Field,

You can group fields together using FieldGroup and FieldSet. Perfect for multi-section forms.

<FieldSet>
  <FieldLegend />
  <FieldGroup>
    <Field />
    <Field />
  </FieldGroup>
</FieldSet>
Address Information

We need your address to deliver your order.

import {
  Field,
  FieldDescription,

Making it responsive is easy. Use orientation="responsive" and it switches between vertical and horizontal layouts based on container width. Done.

Profile

Fill in your profile information.

Provide your full name for identification

You can write your message here. Keep it short, preferably under 100 characters.

import { Button } from "@/components/ui/button"
import {
  Field,

Wait here's more. Wrap your fields in FieldLabel to create a selectable field group. Really easy. And it looks great.

Select the compute environment for your cluster.

import {
  Field,
  FieldContent,

Item

This one is a straightforward flex container that can house nearly any type of content.

I've built this so many times that I decided to create a component for it. Now I use it all the time. I use it to display lists of items, cards, and more.

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

Here's a basic item:

<Item>
  <ItemMedia variant="icon">
    <HomeIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Dashboard</ItemTitle>
    <ItemDescription>Overview of your account and activity.</ItemDescription>
  </ItemContent>
</Item>
Basic Item

A simple item with title and description.

Your profile has been verified.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

You can add icons, avatars, or images to the item.

Security Alert

New login detected from unknown device.

import { ShieldAlertIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
ER
Evil Rabbit

Last seen 5 months ago

ER
No Team Members

Invite your team to collaborate on this project.

import { Plus } from "lucide-react"

import {

And here's what a list of items looks like with ItemGroup:

s
shadcn

shadcn@vercel.com

m
maxleiter

maxleiter@vercel.com

e
evilrabbit

evilrabbit@vercel.com

import * as React from "react"
import { PlusIcon } from "lucide-react"

Need it as a link? Use the asChild prop:

<Item asChild>
  <a href="/dashboard">
    <ItemMedia variant="icon">
      <HomeIcon />
    </ItemMedia>
    <ItemContent>
      <ItemTitle>Dashboard</ItemTitle>
      <ItemDescription>Overview of your account and activity.</ItemDescription>
    </ItemContent>
  </a>
</Item>
import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react"

import {

Empty

Okay last one: Empty. Use this to display empty states in your app.

import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty"

Here's how you use it:

<Empty>
  <EmptyMedia variant="icon">
    <InboxIcon />
  </EmptyMedia>
  <EmptyTitle>No messages</EmptyTitle>
  <EmptyDescription>You don't have any messages yet.</EmptyDescription>
  <EmptyContent>
    <Button>Send a message</Button>
  </EmptyContent>
</Empty>
No Projects Yet
You haven't created any projects yet. Get started by creating your first project.
Learn More
import { IconFolderCode } from "@tabler/icons-react"
import { ArrowUpRightIcon } from "lucide-react"

You can use it with avatars:

LR
User Offline
This user is currently offline. You can leave a message to notify them or try again later.
import {
  Avatar,
  AvatarFallback,

Or with input groups for things like search results or email subscriptions:

404 - Not Found
The page you're looking for doesn't exist. Try searching for what you need below.
/
Need help? Contact support
import { SearchIcon } from "lucide-react"

import {

That's it. Seven new components. Works with all your libraries. Ready for your projects.