- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Button
- Calendar
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- ComposerNew
- Command
- Context Menu
- Date Picker
- Dialog
- Drawer
- Empty
- Field
- Fieldset
- Form
- Frame
- Group
- Input
- Input Group
- Kbd
- Label
- Menu
- Meter
- Number Field
- OTP Field
- Pagination
- Popover
- Preview Card
- Progress
- Radio Group
- Scroll Area
- Select
- Segmented Control
- Separator
- Sheet
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
Changelog
Breaking changes, migration guides, and notable updates.
October 20, 2026
Segmented Control component
@coss/segmented-control is now a component at components/ui/segmented-control.tsx instead of a styling library at lib/segmented-control.ts. It exports SegmentedControl and SegmentedControlItem. The element you pass to render still decides the behavior — a Radio Group, links, or a Toggle Group — so the semantics stay those of the primitive you choose.
<SegmentedControl
aria-label="Billing period"
render={<RadioGroupPrimitive defaultValue="monthly" />}
>
<SegmentedControlItem render={<RadioPrimitive.Root value="monthly" />}>
Monthly
</SegmentedControlItem>
<SegmentedControlItem render={<RadioPrimitive.Root value="yearly" />}>
Yearly
</SegmentedControlItem>
</SegmentedControl>The sliding indicator is built in. SegmentedControl renders it after its children, so there is nothing to add or order by hand, and before hydration the active item paints its own static pill. size is set once on SegmentedControl and inherited by its items; an item can still override it. One item recipe now covers data-checked, aria-current="page", and data-pressed, so there is no state option.
These exports were removed: segmentedControlRootClassName, segmentedControlItemVariants, segmentedControlIndicatorClassName, and SegmentedControlIndicator. The @coss/segmented-control-indicator registry item no longer exists. SegmentedControlSize, segmentedControlItemSizeClassNames, segmentedControlItemLayoutClassName, and segmentedControlIndicatorSurfaceClassName are still exported, from the component file. Tabs imports them from there, and @coss/tabs declares the component as a registry dependency.
Migration:
Install the component, then update Tabs so it no longer imports from the library:
pnpm dlx shadcn@latest add @coss/segmented-control @coss/tabs
If you maintain a local Tabs, change its import instead of overwriting it:
- } from "@/lib/segmented-control"
+ } from "@/components/ui/segmented-control"Replace class-based compositions. Move the root class and the indicator into SegmentedControl, and each item recipe into SegmentedControlItem, keeping behavior props on the rendered element:
- <RadioGroupPrimitive
- aria-label="Billing period"
- className={segmentedControlRootClassName}
- defaultValue="monthly"
- >
- <RadioPrimitive.Root className={itemClassName} value="monthly">
+ <SegmentedControl
+ aria-label="Billing period"
+ render={<RadioGroupPrimitive defaultValue="monthly" />}
+ >
+ <SegmentedControlItem render={<RadioPrimitive.Root value="monthly" />}>
Monthly
- </RadioPrimitive.Root>
- <SegmentedControlIndicator />
- </RadioGroupPrimitive>
+ </SegmentedControlItem>
+ </SegmentedControl>Navigation follows the same shape, with the <nav> as the rendered root and the link on each item:
- <nav aria-label="Project sections">
- <div className={segmentedControlRootClassName}>
- <a aria-current="page" className={itemClassName} href="/overview">
- Overview
- </a>
- </div>
- </nav>
+ <SegmentedControl aria-label="Project sections" render={<nav />}>
+ <SegmentedControlItem render={<a aria-current="page" href="/overview" />}>
+ Overview
+ </SegmentedControlItem>
+ </SegmentedControl>Extra classes from segmentedControlItemVariants({ className }) move to className on SegmentedControlItem. Once nothing imports @/lib/segmented-control or @/lib/segmented-control-indicator, delete both files.
Agent migration prompt:
Migrate my codebase from the coss segmented-control styling library to the Segmented Control component. Install the component with `npx shadcn@latest add @coss/segmented-control`; it is written to components/ui/segmented-control.tsx and exports SegmentedControl, SegmentedControlItem, SegmentedControlSize, segmentedControlItemSizeClassNames, segmentedControlItemLayoutClassName, and segmentedControlIndicatorSurfaceClassName. Update the local Tabs component first: either reinstall it with `npx shadcn@latest add @coss/tabs`, or, if it has project-specific changes, only change its segmented-control import from @/lib/segmented-control to @/components/ui/segmented-control. Then find every file that imports segmentedControlRootClassName, segmentedControlItemVariants, segmentedControlIndicatorClassName, or SegmentedControlIndicator. For each composition, replace the element that has segmentedControlRootClassName with <SegmentedControl render={<ThatElement {...itsBehaviorProps} />}>: Radio Group roots become render={<RadioGroupPrimitive defaultValue=… />}, Toggle Group roots become render={<ToggleGroupPrimitive … />}, and a plain wrapper div inside a <nav> collapses so the nav itself is the rendered root via render={<nav />}. Keep accessibility props such as aria-label on SegmentedControl. Replace each item that used segmentedControlItemVariants with <SegmentedControlItem render={<ThatItem {...itsBehaviorProps} />}>, for example render={<RadioPrimitive.Root value="monthly" />} or render={<a href="/overview" aria-current={…} />}; put value, href, onClick, aria-current, and other behavior props on the rendered element, and move any className passed to segmentedControlItemVariants onto SegmentedControlItem. Drop the state option entirely. Move a shared size option to the size prop on SegmentedControl, and keep a per-item size prop only where items genuinely differ. Delete every <SegmentedControlIndicator /> element, because SegmentedControl renders the indicator itself. Do not convert Tabs call sites; Tabs keeps its own component. When nothing imports @/lib/segmented-control or @/lib/segmented-control-indicator any more, delete those files. Run formatting and typechecking, then visually check each segmented control in light and dark mode: the pill should sit exactly on the active item and slide when the selection changes.October 18, 2026
Avatar group clipping and inherited sizing
AvatarGroup and AvatarGroupCount are new. Overlapping avatars are separated by masking each one with its neighbour's circle, so the gap between them is transparent rather than a coloured ring. A stack now keeps its shape on gradients, photographs and translucent panels, where a ring tinted with the page background does not.
<AvatarGroup>
<Avatar>…</Avatar>
<Avatar>…</Avatar>
<AvatarGroupCount>+2</AvatarGroupCount>
</AvatarGroup>topmost picks which end of the stack stays a whole circle — "first" by default, "last" for the conventional order.
Avatar now takes every dimension from one inherited custom property, --avatar-size (default 32px). Font size scales with it, and a group derives its overlap, gap and initials nudge from it. Set it on the avatar, the group, or any ancestor:
<div className="[--avatar-size:--spacing(10)]">{/* every avatar below is 40px */}</div>--avatar-overlap, --avatar-gap and --avatar-nudge tune a group and inherit the same way.
Two defaults changed: AvatarFallback uses bg-border instead of bg-muted, which is twice the contrast, and Avatar no longer paints bg-background behind its content. AvatarImage and AvatarFallback now follow the root radius through rounded-[inherit], so a rounded avatar no longer has to repeat its radius on the fallback.
Browsers without mask-composite (Chrome and Edge 111–119) fall back to a plain overlap with no gap. Nothing else degrades.
Migration:
Existing Avatar JSX requires no changes. A size-* class still resizes a single avatar, because tailwind-merge replaces the base size utility — but it leaves --avatar-size untouched, so the font size and any group geometry stay at the inherited value. Prefer the property:
- <Avatar className="size-10">
+ <Avatar className="[--avatar-size:--spacing(10)]">Once an avatar is sized this way, a hand-tuned font size on its fallback is usually redundant:
- <AvatarFallback className="text-[10px]">JD</AvatarFallback>
+ <AvatarFallback>JD</AvatarFallback>Replacing a hand-rolled ring stack with AvatarGroup means dropping the negative spacing and the ring utilities:
- <div className="flex -space-x-2">
- <Avatar className="ring-2 ring-background">…</Avatar>
- </div>
+ <AvatarGroup>
+ <Avatar>…</Avatar>
+ </AvatarGroup>If you relied on the lighter bg-muted fallback or on bg-background behind a transparent image, set those explicitly on your own instances.
pnpm dlx shadcn@latest add @coss/avatar
Agent migration prompt:
Update the local coss Avatar component to the version with AvatarGroup, then migrate call sites. Install with `npx shadcn@latest add @coss/avatar`. Avatar now derives its box, font size and group geometry from one inherited custom property, --avatar-size (default 32px); --avatar-overlap, --avatar-gap and --avatar-nudge tune groups and inherit the same way. Replace size-* classes on Avatar with [--avatar-size:--spacing(N)] using the same N, and set it on a shared ancestor when several avatars share a size. After converting, delete hand-tuned text-* classes on AvatarFallback unless the design needs a size other than the derived one — the derived size is --avatar-size * 0.375. Replace hand-rolled overlapping stacks (flex with -space-x-* plus ring-2 ring-background on each avatar) with AvatarGroup, removing the negative spacing and ring utilities; add topmost="last" if the original kept the last avatar whole. Use AvatarGroupCount for a trailing "+N" chip instead of an Avatar with a fallback. Inside a group every avatar must be the same size, so set --avatar-size on the group, never on individual avatars. A status dot or badge on a clipped avatar is clipped with it; keep those on the avatar that stays whole, and place them in a relative wrapper as a sibling of Avatar rather than inside it. Note two visual default changes to review: AvatarFallback uses bg-border instead of bg-muted, and Avatar no longer paints bg-background. Run formatting and typechecking, then visually check avatars at every size the app uses, including any on gradients or images.August 19, 2026
Tabs sizing and segmented control styles
TabsList now supports a size prop with "sm", "default", and "lg" values. The size propagates to its TabsTab children, and an individual tab can override the inherited size when necessary.
The default Tabs appearance now uses the same optical item sizing as the Segmented Control pattern. The default rendered height is slightly smaller than before so the inset active indicator remains visually balanced beside a Button of the corresponding size.
Tabs now imports its size map and segmentedControlItemLayoutClassName from the @coss/segmented-control registry library. That class covers the shared icon gap, size, and opacity treatment. segmentedControlItemVariants includes it, and TabsTab uses it instead of inlining the same utilities. @coss/tabs declares this as a registry dependency, so the shadcn CLI installs it automatically with a fresh installation or CLI-managed update.
Migration:
Existing Tabs JSX requires no changes because "default" remains the default size. If you replace your local Tabs component through the CLI, review any local customizations before accepting the overwrite:
pnpm dlx shadcn@latest add @coss/tabs
If you update tabs.tsx manually, install the shared library first and then merge the latest Tabs implementation. Copying only the new Tabs file without this dependency results in an unresolved import.
pnpm dlx shadcn@latest add @coss/segmented-control
After updating, remove local height or horizontal-padding utilities from TabsList and TabsTab only where they unintentionally override the new size API. Keep intentional product-specific overrides. If you maintain a local copy of the library or Tabs, also merge segmentedControlItemLayoutClassName so icons stay optically aligned with Button.
Agent migration prompt:
Update the local coss Tabs component to the latest segmented-control sizing and item layout while preserving project-specific customizations. Install the shared registry dependency with `npx shadcn@latest add @coss/segmented-control`, then merge the latest @coss/tabs implementation. TabsList now accepts size="sm" | "default" | "lg" (default: "default") and propagates it to TabsTab; a TabsTab size prop overrides the inherited value. Replace the old hard-coded TabsTab height and horizontal-padding classes with segmentedControlItemSizeClassNames from the shared library, and replace inlined icon gap/size/opacity utilities with segmentedControlItemLayoutClassName. Existing Tabs JSX does not need a size prop. Audit local h-* and px-* overrides on TabsList/TabsTab and remove only those that unintentionally conflict with the new size API. Run formatting and typechecking, then visually verify default and underline Tabs in every size used by the app.July 31, 2026
Scroll Area — overscroll contain
ScrollArea no longer applies overscroll-behavior: contain by default. Use the new optional overscrollContain prop (default false) to opt in.
Nested surfaces that should not chain scroll to the page already pass it: DialogPanel, SheetPanel, DrawerPanel, ComboboxList, AutocompleteList, and SidebarContent.
Migration:
No change required for those composed parts after upgrading. For custom nested scroll areas (command menus, calendar time lists, vendored sidebar.tsx, etc.), opt in explicitly:
- <ScrollArea scrollbarGutter scrollFade>
+ <ScrollArea overscrollContain scrollbarGutter scrollFade>Agent migration prompt:
After upgrading @coss/scroll-area, ScrollArea no longer applies overscroll-contain by default. Add overscrollContain only on nested scroll surfaces where wheel/touch should not chain to a parent scroller (dialogs, sheets, drawers, popups, sidebars, command panels). DialogPanel, SheetPanel, DrawerPanel, ComboboxList, AutocompleteList, and SidebarContent already pass overscrollContain—update local copies of those components if you vendor them.July 26, 2026
React DayPicker 10
coss ui now targets @daypicker/react@10. The legacy react-day-picker package name remains available for compatibility, but DayPicker recommends the scoped package for v10 and newer.
If you import DayPicker APIs directly, update the dependency and imports:
- import { DayPicker, type DateRange } from "react-day-picker"
+ import { DayPicker, type DateRange } from "@daypicker/react"DayPicker v10 removes APIs deprecated in v9, including legacy navigation props, aliases, component slots, and classNames / styles keys. Applications with custom calendar wrappers should follow the DayPicker v10 migration guide.
Agent migration prompt:
Upgrade React DayPicker to v10. Replace the react-day-picker dependency with @daypicker/react and update all imports, including style and locale imports. Audit custom Calendar and DatePicker wrappers for APIs removed in v10: replace deprecated navigation and focus props, aliases, component slots, and classNames/styles keys according to https://daypicker.dev/upgrading. If the app uses a non-Gregorian calendar, install the corresponding @daypicker/* add-on and update its imports. Run the typecheck and test affected date-picker screens after migrating.June 22, 2026
Base UI 1.6.0
coss ui now targets @base-ui/react@1.6.0.
Breaking change covered in this bump: Base UI renamed the OTP namespace export from OTPFieldPreview to OTPField. coss has been updated accordingly in the OTP field implementation and docs.
If you import Base UI OTP primitives directly in your app, update:
- import { OTPFieldPreview } from "@base-ui/react/otp-field"
+ import { OTPField } from "@base-ui/react/otp-field"June 11, 2026
Context Menu
@coss/context-menu is a new registry component for right-click and long-press action menus anchored to the pointer. See the Context Menu docs for installation and examples.
Install:
pnpm dlx shadcn@latest add @coss/context-menu
No migration from an existing coss primitive—opt in where you need pointer-triggered menus instead of (or alongside) Menu.
Menu & Context Menu — link items
MenuLinkItem and ContextMenuLinkItem are styled wrappers for Base UI LinkItem. They match MenuItem / ContextMenuItem visually and default closeOnClick to true so the menu closes on navigation.
LinkItem renders a native <a> when you pass href. If you were already using render={<Link href="..." />} (or another router link), keep render on the link item — swap the component name only, do not drop render for href alone.
Use href only for plain anchor links with no router Link.
Migration:
import {
Menu,
- MenuItem,
+ MenuLinkItem,
MenuPopup,
MenuTrigger,
} from "@/components/ui/menu"- <MenuItem render={<Link href="/docs" />}>Docs</MenuItem>
+ <MenuLinkItem render={<Link href="/docs" />}>Docs</MenuLinkItem>Plain anchor (no router Link):
<MenuLinkItem href="/docs">Docs</MenuLinkItem>For context menus:
- <ContextMenuItem render={<Link href="/docs" />}>Docs</ContextMenuItem>
+ <ContextMenuLinkItem render={<Link href="/docs" />}>Docs</ContextMenuLinkItem>Existing MenuItem render={<Link … />} usage keeps working; migrate when you touch those menus.
Agent migration prompt:
coss now exports MenuLinkItem (menu.tsx) and ContextMenuLinkItem (context-menu.tsx) for navigation rows. Replace MenuItem/ContextMenuItem with render={<Link href="..." />} by changing only the component to MenuLinkItem/ContextMenuLinkItem — keep render={<Link … />}; do not replace render with href when using Next.js or another router Link. Use href on MenuLinkItem/ContextMenuLinkItem only for plain <a> navigation without a router Link. Keep MenuItem/ContextMenuItem for actions (onClick), not navigation. Context Menu is a new @coss/context-menu package—see Context Menu docs.May 29, 2026
Scroll Area
ScrollArea adds an optional fill prop (default false). When fill is enabled, size-full is applied to ScrollArea.Content so inner flex layouts can use the full viewport height (for example, pinning a footer with mt-auto in a sidebar).
Most scroll areas should keep the default. Opt in only when the scroll container must stretch rather than size to its children.
Migration:
No change required for existing ScrollArea usage (lists, dialogs, comboboxes, command menus). For custom sidebars or other flex columns with mt-auto footers inside a scroll area, opt in explicitly:
- <ScrollArea className="flex-1 min-h-0">
+ <ScrollArea className="flex-1 min-h-0" fill>
<div className="flex h-full flex-col">
…
<footer className="mt-auto">…</footer>
</div>
</ScrollArea>SidebarContent from @coss/sidebar already passes fill—update your local sidebar.tsx copy if you vendor the component.
Agent migration prompt:
After upgrading @coss/scroll-area, ScrollArea has an optional fill prop (default false). Do not add fill to every ScrollArea. Add fill only where a flex column inside the scroll area must stretch to the viewport height (e.g. mt-auto footer in a sidebar). Pair fill with flex-1 min-h-0 on the ScrollArea and h-full flex-col on the inner wrapper. SidebarContent in @coss/sidebar already uses fill.Apr 17, 2026
Form
The Form component (form.tsx) no longer applies default layout classes (flex, flex-col, gap-4, w-full). It is a thin wrapper around Base UI only; pass className wherever you need a vertical field stack, width constraints, or dialog/sheet layout.
Migration:
For a typical stacked-field form, restore the previous default explicitly:
- <Form onSubmit={…}>
+ <Form className="flex w-full flex-col gap-4" onSubmit={…}>For dialogs, sheets, and drawers, keep DialogHeader (or SheetHeader, DrawerHeader) outside the form. Wrap DialogPanel and DialogFooter (or sheet/drawer equivalents) in <Form className="contents"> or a native <form className="contents">. display: contents keeps header, panel, and footer as the direct flex children of the popup’s column layout so DialogPanel scroll and height constraints work correctly.
Agent migration prompt:
The coss Form component no longer adds default Tailwind layout. Add className="flex w-full flex-col gap-4" to Form where you relied on the old stacked-field layout. For dialog/sheet/drawer: put header outside the form; wrap only panel+footer in Form (or native form) with className="contents". Merge any existing Form className with flex utilities where needed (e.g. max-w-64 becomes flex w-full max-w-64 flex-col gap-4).Apr 14, 2026
OTP Field
The input-otp.tsx registry component has been removed in favor of otp-field.tsx (@coss/otp-field), which wraps Base UI OTP Field. See the OTP Field docs and migration guide for API details.
Migration:
- npx shadcn@latest add @coss/input-otp
+ npx shadcn@latest add @coss/otp-field- import { ... } from "@/components/ui/input-otp"
+ import { ... } from "@/components/ui/otp-field"Agent migration prompt:
Replace the removed @coss/input-otp / components/ui/input-otp.tsx with @coss/otp-field / components/ui/otp-field.tsx. Follow the OTP Field docs: use OTPField, OTPFieldInput, and OTPFieldSeparator; use length on the root; remove the old input-otp npm package if present.Apr 12, 2026
Table
Table now supports an optional variant prop: "default" (the default) or "card". Omitting it keeps the same appearance as before. Use variant="card" for the card-style grid (separated rows, rounded cells, updated hover/footer treatment)—including tables inside a Frame or CardFrame as shown in the Table docs.
Migration:
No change required for existing tables. Opt in where you want the card-style table:
<Table variant="card">Agent migration prompt:
After upgrading @coss/table, Table supports an optional variant prop: "default" (default) or "card". Existing Table components require no edits. Add variant="card" to Table where you want the card-style table (e.g. inside Frame or CardFrame). Do not add variant to every table—only where the design calls for the card-style grid.Mar 20, 2026
Toggle Group
Toggle was renamed to ToggleGroupItem in toggle-group.tsx to avoid a naming conflict with the standalone Toggle export.
Migration:
- import { Toggle, ToggleGroup } from "@/components/ui/toggle-group"
+ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" <ToggleGroup>
- <Toggle value="bold">Bold</Toggle>
+ <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
</ToggleGroup>Agent migration prompt:
In my codebase, rename all usages of `Toggle` imported from `toggle-group` to `ToggleGroupItem`. Update both the import statements and JSX usage. Do not change `Toggle` imports from `toggle.tsx` (standalone toggle). Only change files that import `Toggle` from the `toggle-group` module.On This Page