Skip to content
Open
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
2 changes: 1 addition & 1 deletion components/ui/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from "react";

import { Tabs as TabsPrimitive } from "@base-ui-components/react/tabs";
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

@base-ui/react version 1.0.0 breaking changes from @base-ui-components/react beta

💡 Result:

Quick summary of breaking changes to expect when moving from the @base-ui-components/react beta series to @base-ui/react v1.0.0:

  • Package rename: package moved from @base-ui-components/react → @base-ui/react (must change imports). [1]
  • Event callback signature: many callbacks now receive an eventDetails object (e.g., onOpenChange(open, eventDetails) instead of onOpenChange(open, event, reason)). [1][2]
  • Focus/prop renames:
    • loop → loopFocus. [3]
    • trackAnchor (boolean) → disableAnchorTracking (invert semantics). [3]
  • Checkbox / form behavior changes:
    • Checkbox unchecked value behavior changed (unchecked value no longer submitted unless using new uncheckedValue prop in some releases / checked-state handling refinements). [1][2]
    • Checkbox Group: submission behavior changed (use value on Checkbox.Root for grouped submission). [2]
  • Accordion API:
    • Items must have explicit value (no longer infer by DOM index). openMultiple renamed to multiple. [1]
  • initialFocus / finalFocus: initialFocus now must return element directly (support for functions; finalFocus added as function support) — change in how focus refs are supplied. [2]
  • Event reason names refined (e.g., 'hover' → 'trigger-hover', 'click' → 'trigger-press', 'focus' → 'trigger-focus'). [2]
  • Other component-level breaking changes introduced across beta releases (Collapsible render handling, Menu/Combobox prop changes, default tabIndex on parts, and accessibility/id behavior) — see changelogs for component-specific items you use. [1][2][3]

If you want, tell me which components/props you use and I’ll extract the exact migration steps and code diffs for them.

Sources: Base UI changelogs and release notes. [1] Base UI releases (v1.0.0 and changelog). [2] @base-ui-components/react beta changelogs (beta.3 / beta.0). [3] v1.0.0-beta.5 release notes.


🌐 Web query:

@base-ui/react Tabs API documentation stable release

💡 Result:

The stable Tabs API docs are on the official Base UI site: https://base-ui.com/react/components/tabs. See the component API reference there. [1][2]

Sources:

  1. Base UI — Tabs API reference. [1]
  2. Base UI — Quick start / Releases (package and release info). [2]

🌐 Web query:

@base-ui/react migration guide from beta to 1.0.0

💡 Result:

Summary migration checklist — beta → v1.0.0

  1. Update package name
  • Replace old package (e.g. @base-ui-components/react) with @base-ui/react and reinstall. [1][2]
  1. Event API changes
  • Callbacks now receive BaseUIChangeEventDetails (with event, reason, helpers). Update usages like onOpenChange(open, event) → onOpenChange(open, eventDetails). Review custom event handlers and types. [1]
  1. Rename/replace props
  • trackAnchor → disableAnchorTracking (invert boolean semantics if you used trackAnchor={false}). [1]
  • loop → loopFocus. [1]
  1. Component-specific breaking changes to check and update
  • Accordion: items must have explicit value; fallback to composite index removed. If you relied on index inference, add explicit value props. openMultiple → multiple. [1]
  • Autocomplete: onItemHighlighted now uses reason (not type); cols → grid. [1]
  • Combobox / Select: itemToStringValue now applied to onFormSubmit; Select accepts null as a value. Verify form submit behavior. [1]
  • Menu/Popover/Tooltip/NavigationMenu/Tabs/Toast/ToggleGroup: check release notes for fixed behaviors (focus, transitions, indicator positioning, falsy values handling) and adjust any code that relied on previous quirks. [1]
  1. Types and refs
  • Some ref types and event/detail types were refined — update TypeScript types/imports to the new exported types (BaseUIChangeEventDetails / BaseUIGenericEventDetails). Run type-check and fix mismatches. [1]
  1. Styling/portal behavior
  • Portals and portal root guidance unchanged, but review Quick Start notes (ensure your layout isolation for portals). [2]
  1. Tests and QA
  • Run full test suite and accessibility checks; pay special attention to keyboard/focus interactions in Dialog, Menu, Popover, Tooltip and any components you heavily use (several focus/transition fixes were made). [1]
  1. Search changelogs for detailed per-component changes
  • Review the 1.0.0 changelog and prior beta changelogs for any other breaking edits that affect components you use. Address each item in your codebase. [1]

Useful links

  • Releases / changelog (v1.0.0 and betas) — use this to inspect component-by-component breaking changes. [1]
  • Quick start / install (@base-ui/react) — installation and portal guidance. [2]

If you want, tell me which Base UI components you use and I’ll produce a focused diff/patch list to update your code.


Update code to handle @base-ui/react v1.0.0 breaking changes.

The package migration from @base-ui-components/react to @base-ui/react v1.0.0 includes significant breaking changes beyond the import path update:

  • Event callback signatures changed: Callbacks now receive an eventDetails object instead of separate event and reason parameters. Update any Tabs event handlers (e.g., onOpenChange(open, eventDetails) instead of onOpenChange(open, event, reason)).
  • Prop renames: looploopFocus, trackAnchordisableAnchorTracking (with inverted boolean semantics).
  • Event reason names refined: Check if custom event reason handling exists; names like 'hover' may have changed.

Review the official Base UI v1.0.0 migration guide and Tabs API documentation for the complete list of changes and update all Tabs usage accordingly.

🤖 Prompt for AI Agents
In components/ui/tabs.tsx around line 5, the import was updated but the
component usage still assumes pre-v1.0.0 Tabs API; update Tabs props and
handlers to match @base-ui/react v1.0.0: change any event handler signatures to
accept (open, eventDetails) instead of (open, event, reason) and adapt code to
read eventDetails.reason if needed; rename prop loop to loopFocus; replace
trackAnchor with disableAnchorTracking and invert its boolean semantics (e.g.,
trackAnchor={true} → disableAnchorTracking={false}); review any custom logic
that checks event reason strings and adjust to the new reason names from the
v1.0.0 migration guide.

import posthog from "posthog-js";

import { cn } from "@/lib/utils";
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache"
},
"dependencies": {
"@base-ui-components/react": "1.0.0-beta.2",
"@base-ui/react": "^1.0.0",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.6",
Expand Down
79 changes: 57 additions & 22 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 17 additions & 71 deletions registry/base-ui/scroll-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@

import * as React from "react";

import { ScrollArea as ScrollAreaPrimitive } from "@base-ui-components/react/scroll-area";
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";

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

import { useTouchPrimary } from "@/hooks/use-has-primary-touch";

type Mask = {
top: boolean;
bottom: boolean;
left: boolean;
right: boolean;
};

export type ScrollAreaContextProps = {
isTouch: boolean;
type: "auto" | "always" | "scroll" | "hover";
Expand All @@ -39,54 +32,9 @@ const ScrollArea = React.forwardRef<
maskClassName?: string;
}
>(({ className, children, type = "hover", maskHeight = 30, maskClassName, viewportClassName, ...props }, ref) => {
const [showMask, setShowMask] = React.useState<Mask>({
top: false,
bottom: false,
left: false,
right: false,
});

const viewportRef = React.useRef<HTMLDivElement>(null);
const isTouch = useTouchPrimary();

const checkScrollability = React.useCallback(() => {
const element = viewportRef.current;
if (!element) return;

const { scrollTop, scrollLeft, scrollWidth, clientWidth, scrollHeight, clientHeight } = element;
setShowMask((prev) => ({
...prev,
top: scrollTop > 0,
bottom: scrollTop + clientHeight < scrollHeight - 1,
left: scrollLeft > 0,
right: scrollLeft + clientWidth < scrollWidth - 1,
}));
}, []);

React.useEffect(() => {
if (typeof window === "undefined") return;

const element = viewportRef.current;
if (!element) return;

const controller = new AbortController();
const { signal } = controller;

const resizeObserver = new ResizeObserver(checkScrollability);
resizeObserver.observe(element);

element.addEventListener("scroll", checkScrollability, { signal });
window.addEventListener("resize", checkScrollability, { signal });

// Run an initial check whenever dependencies change (including pointer mode)
checkScrollability();

return () => {
controller.abort();
resizeObserver.disconnect();
};
}, [checkScrollability, isTouch]);

return (
<ScrollAreaContext.Provider value={{ isTouch, type }}>
{isTouch ? (
Expand All @@ -96,18 +44,18 @@ const ScrollArea = React.forwardRef<
role="group"
data-slot="scroll-area"
aria-roledescription="scroll area"
className={cn("relative overflow-hidden", className)}
className={cn("group relative overflow-hidden", className)}
>
<div ref={viewportRef} className={cn("size-full overflow-auto", viewportClassName)} tabIndex={0}>
{children}
</div>
{maskHeight > 0 && <ScrollMask showMask={showMask} className={maskClassName} maskHeight={maskHeight} />}
{maskHeight > 0 && <ScrollMask className={maskClassName} size={maskHeight} />}
</div>
) : (
<ScrollAreaPrimitive.Root
ref={ref}
data-slot="scroll-area"
className={cn("relative overflow-hidden", viewportClassName, className)}
className={cn("group relative overflow-hidden", viewportClassName, className)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove incorrectly placed viewportClassName from Root.

The viewportClassName prop is applied to the Root component's className, but it should only be used on the Viewport component (line 64). This creates confusion about which element receives the viewport styles.

🔎 Proposed fix
-          className={cn("group relative overflow-hidden", viewportClassName, className)}
+          className={cn("group relative overflow-hidden", className)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={cn("group relative overflow-hidden", viewportClassName, className)}
className={cn("group relative overflow-hidden", className)}
🤖 Prompt for AI Agents
In registry/base-ui/scroll-area.tsx around line 58, the Root element is
mistakenly receiving the viewportClassName prop in its className; remove
viewportClassName from the Root className expression so Root only uses "group
relative overflow-hidden" plus className, and ensure the Viewport component
(around line 64) receives viewportClassName (merged via cn with any existing
Viewport classes) so viewport-specific styles apply only to the Viewport
element.

{...props}
>
<ScrollAreaPrimitive.Viewport
Expand All @@ -119,7 +67,7 @@ const ScrollArea = React.forwardRef<
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
{maskHeight > 0 && <ScrollMask showMask={showMask} className={maskClassName} maskHeight={maskHeight} />}
{maskHeight > 0 && <ScrollMask className={maskClassName} size={maskHeight} />}
</ScrollAreaPrimitive.Root>
)}
</ScrollAreaContext.Provider>
Expand Down Expand Up @@ -166,13 +114,11 @@ const ScrollBar = React.forwardRef<
ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName;

const ScrollMask = ({
showMask,
maskHeight,
size,
className,
...props
}: React.ComponentProps<"div"> & {
showMask: Mask;
maskHeight: number;
size: number;
}) => {
return (
<>
Expand All @@ -181,17 +127,17 @@ const ScrollMask = ({
aria-hidden="true"
style={
{
"--top-fade-height": showMask.top ? `${maskHeight}px` : "0px",
"--bottom-fade-height": showMask.bottom ? `${maskHeight}px` : "0px",
"--vertical-mask-height": `${size}px`,
} as React.CSSProperties
}
className={cn(
"pointer-events-none absolute inset-0 z-10",
"before:absolute before:inset-x-0 before:top-0 before:transition-[height,opacity] before:duration-300 before:content-['']",
"after:absolute after:inset-x-0 after:bottom-0 after:transition-[height,opacity] after:duration-300 after:content-['']",
"before:h-(--top-fade-height) after:h-(--bottom-fade-height)",
showMask.top ? "before:opacity-100" : "before:opacity-0",
showMask.bottom ? "after:opacity-100" : "after:opacity-0",
"before:h-0 group-data-overflow-y-start:before:h-(--vertical-mask-height)",
"after:h-0 group-data-overflow-y-end:after:h-(--vertical-mask-height)",
"before:opacity-0 group-data-overflow-y-start:before:opacity-100",
"after:opacity-0 group-data-overflow-y-end:after:opacity-100",
"before:from-background before:bg-gradient-to-b before:to-transparent",
"after:from-background after:bg-gradient-to-t after:to-transparent",
className
Expand All @@ -202,17 +148,17 @@ const ScrollMask = ({
aria-hidden="true"
style={
{
"--left-fade-width": showMask.left ? `${maskHeight}px` : "0px",
"--right-fade-width": showMask.right ? `${maskHeight}px` : "0px",
"--horizontal-mask-width": `${size}px`,
} as React.CSSProperties
}
className={cn(
"pointer-events-none absolute inset-0 z-10",
"before:absolute before:inset-y-0 before:left-0 before:transition-[width,opacity] before:duration-300 before:content-['']",
"after:absolute after:inset-y-0 after:right-0 after:transition-[width,opacity] after:duration-300 after:content-['']",
"before:w-(--left-fade-width) after:w-(--right-fade-width)",
showMask.left ? "before:opacity-100" : "before:opacity-0",
showMask.right ? "after:opacity-100" : "after:opacity-0",
"before:w-0 group-data-overflow-x-start:before:w-(--horizontal-mask-width)",
"after:w-0 group-data-overflow-x-end:after:w-(--horizontal-mask-width)",
"before:opacity-0 group-data-overflow-x-start:before:opacity-100",
"after:opacity-0 group-data-overflow-x-end:after:opacity-100",
"before:from-background before:bg-gradient-to-r before:to-transparent",
"after:from-background after:bg-gradient-to-l after:to-transparent",
className
Expand Down