Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/ui/content/docs/(root)/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ title: Changelog
description: Breaking changes, migration guides, and notable updates.
---

## August 31, 2026

### Existing component fixes

**`Sidebar`** now writes its state cookie through `document.cookie` — the previous Cookie Store API call is unavailable in Firefox, Safari, and non-secure contexts — and its mobile breakpoint now matches Tailwind's `md` (768px) instead of 800px. **`ToggleGroup`** no longer forces `variant="default"` / `size="default"` into item context, so per-item `variant` and `size` props are honored. **`Menu`** and **`Context Menu`** items no longer emit `data-inset="false"`, which matched Tailwind's presence selector and incorrectly applied inset padding.

Also in this batch: `AlertDialog`'s popup no longer shows a focus outline and its Header/Footer support the `render` prop; `ComboboxInput` merges consumer `className` from `triggerProps`/`clearProps` instead of letting the spread drop its positioning classes; `Toolbar` stacks vertically with `orientation="vertical"`; `Button` keeps `type="button"` when `render={<button />}` and uses `aria-disabled` (focusable, click-blocked) for non-button renders; `Autocomplete` and `Popover` gained enter/exit animations; `AccordionPanel` className now lands on the panel element; missing `data-slot` attributes were added (`checkbox-group`, `meter`, `scroll-area`, `field-control`, `field-validity`); `GroupSeparator` flips to a horizontal line inside vertical groups; `SheetCreateHandle` and `PreviewCardCreateHandle` were added; `PreviewCardPopup` exposes `side` and uses the `preview-card-popup` data-slot; `EmptyMedia` no longer applies props to two elements; `RadioGroup` aligns items to the start like `CheckboxGroup`; and dead utility classes were removed from `Command`, `Drawer`, `Sheet`, and `OTP Field`.

## August 19, 2026

### Tabs sizing and segmented control styles
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/registry/default/hooks/use-media-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const BREAKPOINTS = {
"3xl": 1600,
"4xl": 2000,
lg: 1024,
md: 800,
md: 768,
sm: 640,
xl: 1280,
} as const;
Expand Down
7 changes: 5 additions & 2 deletions apps/ui/registry/default/ui/accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ export function AccordionPanel({
}: AccordionPrimitive.Panel.Props): React.ReactElement {
return (
<AccordionPrimitive.Panel
className="h-(--accordion-panel-height) overflow-hidden text-muted-foreground text-sm transition-[height] duration-200 ease-in-out data-ending-style:h-0 data-starting-style:h-0"
className={cn(
"h-(--accordion-panel-height) overflow-hidden text-muted-foreground text-sm transition-[height] duration-200 ease-in-out data-ending-style:h-0 data-starting-style:h-0",
className,
)}
data-slot="accordion-panel"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
<div className="pt-0 pb-4">{children}</div>
</AccordionPrimitive.Panel>
);
}
Expand Down
60 changes: 35 additions & 25 deletions apps/ui/registry/default/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import type React from "react";
import { cn } from "@/registry/default/lib/utils";

Expand Down Expand Up @@ -73,7 +75,7 @@ export function AlertDialogPopup({
>
<AlertDialogPrimitive.Popup
className={cn(
"relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg origin-center flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 sm:scale-[calc(1-0.1*var(--nested-dialogs))] sm:data-ending-style:scale-98 sm:data-starting-style:scale-98 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
"relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg origin-center flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-var(--nested-dialogs))] shadow-lg/5 outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 sm:scale-[calc(1-0.1*var(--nested-dialogs))] sm:data-ending-style:scale-98 sm:data-starting-style:scale-98 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
bottomStickOnMobile &&
"max-sm:max-w-none max-sm:origin-bottom max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none",
className,
Expand All @@ -88,39 +90,47 @@ export function AlertDialogPopup({

export function AlertDialogHeader({
className,
render,
...props
}: React.ComponentProps<"div">): React.ReactElement {
return (
<div
className={cn(
"flex flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left",
className,
)}
data-slot="alert-dialog-header"
{...props}
/>
);
}: useRender.ComponentProps<"div">): React.ReactElement {
const defaultProps = {
className: cn(
"flex flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left",
className,
),
"data-slot": "alert-dialog-header",
};

return useRender({
defaultTagName: "div",
props: mergeProps<"div">(defaultProps, props),
render,
});
}

export function AlertDialogFooter({
className,
variant = "default",
render,
...props
}: React.ComponentProps<"div"> & {
variant?: "default" | "bare";
}): React.ReactElement {
return (
<div
className={cn(
"flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
variant === "default" && "border-t bg-muted/72 py-4",
variant === "bare" && "pb-6",
className,
)}
data-slot="alert-dialog-footer"
{...props}
/>
);
} & useRender.ComponentProps<"div">): React.ReactElement {
const defaultProps = {
className: cn(
"flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
variant === "default" && "border-t bg-muted/72 py-4",
variant === "bare" && "pb-6",
className,
),
"data-slot": "alert-dialog-footer",
};

return useRender({
defaultTagName: "div",
props: mergeProps<"div">(defaultProps, props),
render,
});
}

export function AlertDialogTitle({
Expand Down
16 changes: 6 additions & 10 deletions apps/ui/registry/default/ui/autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,16 @@ export function AutocompletePopup({
side={side}
sideOffset={sideOffset}
>
<span
<AutocompletePrimitive.Popup
className={cn(
"relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
"relative flex max-h-[min(var(--available-height),23rem)] w-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) flex-col rounded-lg border bg-popover not-dark:bg-clip-padding text-foreground shadow-lg/5 outline-none transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
className,
)}
data-slot="autocomplete-popup"
{...props}
>
<AutocompletePrimitive.Popup
className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground"
data-slot="autocomplete-popup"
{...props}
>
{children}
</AutocompletePrimitive.Popup>
</span>
{children}
</AutocompletePrimitive.Popup>
</AutocompletePrimitive.Positioner>
</AutocompletePrimitive.Portal>
);
Expand Down
11 changes: 7 additions & 4 deletions apps/ui/registry/default/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { isValidElement } from "react";
import { cn } from "@/registry/default/lib/utils";
import { Spinner } from "@/registry/default/ui/spinner";

export const buttonVariants = cva(
"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-loading:select-none data-loading:text-transparent sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0",
"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 data-loading:pointer-events-none data-loading:select-none data-loading:text-transparent sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0",
{
defaultVariants: {
size: "default",
Expand Down Expand Up @@ -65,8 +66,10 @@ export function Button({
...props
}: ButtonProps): React.ReactElement {
const isDisabled: boolean = Boolean(loading || disabledProp);
const rendersNativeButton: boolean =
!render || (isValidElement(render) && render.type === "button");
const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
render ? undefined : "button";
rendersNativeButton ? "button" : undefined;

const defaultProps = {
children: (
Expand All @@ -81,10 +84,10 @@ export function Button({
</>
),
className: cn(buttonVariants({ className, size, variant })),
"aria-disabled": loading || undefined,
"aria-disabled": isDisabled && !rendersNativeButton ? true : undefined,
"data-loading": loading ? "" : undefined,
"data-slot": "button",
disabled: isDisabled,
disabled: rendersNativeButton && isDisabled ? true : undefined,
type: typeValue,
};

Expand Down
1 change: 1 addition & 0 deletions apps/ui/registry/default/ui/checkbox-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function CheckboxGroup({
return (
<CheckboxGroupPrimitive
className={cn("flex flex-col items-start gap-3", className)}
data-slot="checkbox-group"
{...props}
/>
);
Expand Down
6 changes: 4 additions & 2 deletions apps/ui/registry/default/ui/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,12 @@ export function ComboboxInput({
/>
{showTrigger && (
<ComboboxTrigger
{...triggerProps}
className={cn(
"absolute top-1/2 inline-flex size-8 shrink-0 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
sizeValue === "sm" ? "end-0" : "end-0.5",
triggerProps?.className,
)}
{...triggerProps}
>
<ComboboxPrimitive.Icon data-slot="combobox-icon">
<ChevronsUpDownIcon />
Expand All @@ -122,11 +123,12 @@ export function ComboboxInput({
)}
{showClear && (
<ComboboxClear
{...clearProps}
className={cn(
"absolute top-1/2 inline-flex size-8 shrink-0 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
sizeValue === "sm" ? "end-0" : "end-0.5",
clearProps?.className,
)}
{...clearProps}
>
<XIcon />
</ComboboxClear>
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/registry/default/ui/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function CommandDialogPopup({
<CommandDialogViewport>
<CommandDialogPrimitive.Popup
className={cn(
"relative row-start-2 flex max-h-105 min-h-0 w-full min-w-0 max-w-xl -translate-y-[calc(1.25rem*var(--nested-dialogs))] scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
"relative flex max-h-105 min-h-0 w-full min-w-0 max-w-xl -translate-y-[calc(1.25rem*var(--nested-dialogs))] scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
className,
)}
data-slot="command-dialog-popup"
Expand Down
8 changes: 4 additions & 4 deletions apps/ui/registry/default/ui/context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function ContextMenuItem({
"flex min-h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not([class*='opacity-'])]:opacity-80 [&>svg:not([class*='size-'])]:size-4.5 sm:[&>svg:not([class*='size-'])]:size-4 [&>svg]:pointer-events-none [&>svg]:-mx-0.5 [&>svg]:shrink-0",
className,
)}
data-inset={inset}
data-inset={inset || undefined}
data-slot="context-menu-item"
data-variant={variant}
{...props}
Expand All @@ -121,7 +121,7 @@ export function ContextMenuLinkItem({
className,
)}
closeOnClick={closeOnClick}
data-inset={inset}
data-inset={inset || undefined}
data-slot="context-menu-link-item"
data-variant={variant}
{...props}
Expand Down Expand Up @@ -245,7 +245,7 @@ export function ContextMenuGroupLabel({
"px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8",
className,
)}
data-inset={inset}
data-inset={inset || undefined}
data-slot="context-menu-label"
{...props}
/>
Expand Down Expand Up @@ -303,7 +303,7 @@ export function ContextMenuSubTrigger({
"flex min-h-8 items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not(:last-child)]:-mx-0.5 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className,
)}
data-inset={inset}
data-inset={inset || undefined}
data-slot="context-menu-sub-trigger"
{...props}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/registry/default/ui/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export function DrawerPopup({
position === "left" &&
"data-starting-style:transform-[translateX(calc(-100%-var(--inset)))] data-ending-style:transform-[translateX(calc(-100%-var(--inset)))] transform-[translateX(var(--drawer-swipe-movement-x))] w-[calc(100%-(--spacing(12)))] max-w-md border-e after:inset-y-0 after:end-full after:w-(--bleed) has-data-[slot=drawer-bar]:pe-2",
position === "right" &&
"transform-[translateX(var(--drawer-swipe-movement-x))] data-ending-style:transform-[translateX(calc(100%+var(--inset)))] data-starting-style:transform-[translateX(calc(100%+var(--inset)))] col-start-2 w-[calc(100%-(--spacing(12)))] max-w-md border-s after:inset-y-0 after:start-full after:w-(--bleed) has-data-[slot=drawer-bar]:ps-2",
"transform-[translateX(var(--drawer-swipe-movement-x))] data-ending-style:transform-[translateX(calc(100%+var(--inset)))] data-starting-style:transform-[translateX(calc(100%+var(--inset)))] w-[calc(100%-(--spacing(12)))] max-w-md border-s after:inset-y-0 after:start-full after:w-(--bleed) has-data-[slot=drawer-bar]:ps-2",
variant !== "straight" &&
cn(
position === "bottom" && "rounded-t-2xl",
Expand Down
5 changes: 1 addition & 4 deletions apps/ui/registry/default/ui/empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,7 @@ export function EmptyMedia({
/>
</>
)}
<div
className={cn(emptyMediaVariants({ className, variant }))}
{...props}
/>
<div className={cn(emptyMediaVariants({ className, variant }))} />
</div>
);
}
Expand Down
15 changes: 11 additions & 4 deletions apps/ui/registry/default/ui/field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,16 @@ export function FieldError({
);
}

export const FieldControl: typeof FieldPrimitive.Control =
FieldPrimitive.Control;
export const FieldValidity: typeof FieldPrimitive.Validity =
FieldPrimitive.Validity;
export function FieldControl(
props: FieldPrimitive.Control.Props,
): React.ReactElement {
return <FieldPrimitive.Control data-slot="field-control" {...props} />;
}

export function FieldValidity(
props: FieldPrimitive.Validity.Props,
): React.ReactElement {
return <FieldPrimitive.Validity data-slot="field-validity" {...props} />;
}

export { FieldPrimitive };
Loading
Loading