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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 6 additions & 4 deletions .agents/skills/migrate-radix-to-base/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ transforming, and record gaps in the report.
fetch base variants directly by URL instead
(`https://ui.shadcn.com/r/styles/base-<style>/<component>.json`).
3. PRISTINE wrappers, whole-project mode: `shadcn add <component>
--overwrite` delivers the base variant with the project's exact
--overwrite` delivers the base variant with the project's exact
icon/font/preset resolution. Never bulk `--all --overwrite`; go
component by component, or you drown in unrelated registry version
drift. PROGRESSIVE mode: never use `--overwrite` (it destroys the
Expand All @@ -59,9 +59,9 @@ transforming, and record gaps in the report.
file. The registry sometimes reorders functions between variants, which
makes three-way merges report zero conflicts while leaving stale radix
hunks in place. A clean merge is NOT proof of a clean file.
This is more reliable than reconstructing transforms; use it whenever the
pair exists. Consumer/app code has no CLI mechanism: always hand-migrate it
against `consumer-props.md`.
This is more reliable than reconstructing transforms; use it whenever the
pair exists. Consumer/app code has no CLI mechanism: always hand-migrate it
against `consumer-props.md`.
- **Legacy styles (new-york, new-york-v4, default): classification only, no
replay.** These have no base counterpart (there is no base-new-york), and
retargeting onto a base-<style> variant would restyle the user's app. Use
Expand All @@ -83,6 +83,7 @@ transforming, and record gaps in the report.
## Modes

**Progressive (default).** "Migrate accordion" = one component, strangler-fig:

1. Detect in-progress state first: an existing `<component>-base.tsx`,
consumers split between old/new imports. The files ARE the state; resume,
never restart.
Expand Down Expand Up @@ -126,6 +127,7 @@ Typecheck per file, build per batch, full build at the end vs the baseline.
Reports live in a `.migration/` directory at the project root, ONE FILE PER
COMPONENT: `.migration/<component>.md` (e.g. `.migration/accordion.md`).
Rules:

- Each run writes (or fully overwrites) the file for each component it
migrated. Re-running a component replaces its report; never touch other
components' files.
Expand Down
44 changes: 22 additions & 22 deletions .agents/skills/migrate-radix-to-base/class-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ including app code. They are safe, mechanical rewrites.

## Data-attribute selectors

| Radix pattern | Base UI pattern |
|---|---|
| `data-[state=open]:` | `data-open:` |
| `data-[state=closed]:` | `data-closed:` |
| `data-[state=checked]:` | `data-checked:` |
| `data-[state=unchecked]:` | `data-unchecked:` |
| `data-[state=active]:` (tabs) | `data-active:` |
| `data-[state=on]:` (toggle) | `data-pressed:` |
| `data-[highlighted]:` | `data-highlighted:` (unchanged) |
| `data-[disabled]:` | `data-disabled:` (unchanged) |
| `data-[side=...]:` | `data-[side=...]:` (unchanged, still parameterized) |
| `group-data-[state=open]` / `peer-data-[state=open]` | `group-data-open` / `peer-data-open` |
| submenu trigger open marker `data-[state=open]:` | `data-popup-open:` |
| Radix pattern | Base UI pattern |
| ---------------------------------------------------- | --------------------------------------------------- |
| `data-[state=open]:` | `data-open:` |
| `data-[state=closed]:` | `data-closed:` |
| `data-[state=checked]:` | `data-checked:` |
| `data-[state=unchecked]:` | `data-unchecked:` |
| `data-[state=active]:` (tabs) | `data-active:` |
| `data-[state=on]:` (toggle) | `data-pressed:` |
| `data-[highlighted]:` | `data-highlighted:` (unchanged) |
| `data-[disabled]:` | `data-disabled:` (unchanged) |
| `data-[side=...]:` | `data-[side=...]:` (unchanged, still parameterized) |
| `group-data-[state=open]` / `peer-data-[state=open]` | `group-data-open` / `peer-data-open` |
| submenu trigger open marker `data-[state=open]:` | `data-popup-open:` |

## Animation idiom

Expand All @@ -34,15 +34,15 @@ uses per-side slide classes, keep the `data-[side=...]` or

## CSS variables

| Radix var | Base UI var |
|---|---|
| `--radix-<comp>-content-transform-origin` | `--transform-origin` |
| `--radix-<comp>-content-available-height` | `--available-height` |
| `--radix-<comp>-content-available-width` | `--available-width` |
| `--radix-<comp>-trigger-width` | `--anchor-width` |
| `--radix-<comp>-trigger-height` | `--anchor-height` |
| `--radix-accordion-content-height` | `--accordion-panel-height` |
| `--radix-collapsible-content-height` | `--collapsible-panel-height` |
| Radix var | Base UI var |
| ----------------------------------------------- | -------------------------------------------- |
| `--radix-<comp>-content-transform-origin` | `--transform-origin` |
| `--radix-<comp>-content-available-height` | `--available-height` |
| `--radix-<comp>-content-available-width` | `--available-width` |
| `--radix-<comp>-trigger-width` | `--anchor-width` |
| `--radix-<comp>-trigger-height` | `--anchor-height` |
| `--radix-accordion-content-height` | `--accordion-panel-height` |
| `--radix-collapsible-content-height` | `--collapsible-panel-height` |
| `--radix-navigation-menu-viewport-height/width` | `--positioner-height` / `--positioner-width` |

## Element changes kill pseudo-class variants
Expand Down
58 changes: 29 additions & 29 deletions .agents/skills/migrate-radix-to-base/consumer-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,39 @@ check node_modules/@base-ui/react/**/*.d.ts, never guess.

## Universal

| Radix | Base UI | Call-site action |
|---|---|---|
| Radix | Base UI | Call-site action |
| ----------------------- | ------------- | --------------------------------------------------------------------------- |
| `asChild` (any wrapper) | `render` prop | `<Trigger asChild><Button/></Trigger>` -> `<Trigger render={<Button/>}>...` |

## Per component

| Component | Radix prop | Base UI fate | Call-site action |
|---|---|---|---|
| Accordion | `type="single"\|"multiple"` + `collapsible` | dropped; `value`/`defaultValue` are ALWAYS arrays; multiple-open via `multiple` | `type="single" collapsible` -> remove both; wrap values in arrays; `type="multiple"` -> `multiple` |
| Tabs | `activationMode="manual"` | dropped; Base UI defaults to MANUAL activation | remove prop; near-equivalent opt-in is `Tabs.List activateOnFocus` (behavior delta: flag, do not auto-add) |
| Select | `position="popper"\|"item-aligned"` | `alignItemWithTrigger` boolean (on Positioner; wrappers expose it) | `position="popper"` -> `alignItemWithTrigger={false}`; `item-aligned` -> `alignItemWithTrigger` (default) |
| TooltipProvider | `delayDuration`, `skipDelayDuration` | `delay`; skip-delay concept dropped | rename / remove |
| Tooltip | `disableHoverableContent` | NO equivalent | remove; FLAG the behavior change in the report |
| Avatar.Image | `delayMs` | `delay` | rename |
| ScrollArea | `type="always"\|"scroll"\|...` | dropped | remove |
| Separator | `decorative` | dropped | remove |
| Checkbox | `checked="indeterminate"` | `indeterminate` is a SEPARATE boolean prop | `checked="indeterminate"` -> `indeterminate` + boolean `checked` |
| Slider | `onValueChange(value)` | signature gains event details; also `inverted` REMOVED | check handler arity; remove `inverted` (flag vertical-inverted usage) |
| Select | `onValueChange(value: string)` | widens to `(value: Value \| null, eventDetails)` | `useState<string>` + `onValueChange={setState}` breaks: widen state to `string \| null` or wrap the setter |
| Slider | `onValueCommit` | `onValueCommitted` | rename |
| ToggleGroup | `type="single"\|"multiple"` | `multiple` boolean; value shape arrays | same treatment as Accordion |
| ToggleGroup / Toolbar | `rovingFocus={false}` | dropped (roving focus always on); `loop` -> `loopFocus` | remove / rename |
| Menubar | `value`/`onValueChange` (active menu) | dropped; control per Menu.Root `open` | restructure if used; usually unused |
| Menubar | `loop` | `loopFocus` | rename |
| ContextMenu.Root | `modal` | REMOVED | remove |
| ContextMenu.Trigger | `disabled` | REMOVED | remove; gate the trigger yourself |
| DropdownMenu/ContextMenu items | (Radix closed menu on select) | `closeOnClick` defaults FALSE on CheckboxItem/RadioItem | behavior delta: flag; add `closeOnClick` only if the user asks |
| NavigationMenu | `delayDuration`(200), `skipDelayDuration`, `viewport` | `delay`(50) + `closeDelay`; viewport prop gone (Positioner handles it) | rename/remove; flag the 200->50 hover-delay feel change |
| Popover / HoverCard | `openDelay`/`closeDelay` on Root | move to TRIGGER as `delay`/`closeDelay` | relocate props Root -> Trigger |
| Dialog / AlertDialog | `onOpenAutoFocus` | `initialFocus` (element/ref-based, not event-based) | restructure: pass target instead of preventDefault handler |
| Dialog / AlertDialog | `onCloseAutoFocus` | `finalFocus` | same restructure |
| Dialog family | `onEscapeKeyDown`, `onPointerDownOutside`, `onInteractOutside` | consolidated; see the overlays reference for exact per-part signatures | consult overlays.md; do not guess |
| DirectionProvider | `dir` | `direction` | rename |
| Component | Radix prop | Base UI fate | Call-site action |
| ------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Accordion | `type="single"\|"multiple"` + `collapsible` | dropped; `value`/`defaultValue` are ALWAYS arrays; multiple-open via `multiple` | `type="single" collapsible` -> remove both; wrap values in arrays; `type="multiple"` -> `multiple` |
| Tabs | `activationMode="manual"` | dropped; Base UI defaults to MANUAL activation | remove prop; near-equivalent opt-in is `Tabs.List activateOnFocus` (behavior delta: flag, do not auto-add) |
| Select | `position="popper"\|"item-aligned"` | `alignItemWithTrigger` boolean (on Positioner; wrappers expose it) | `position="popper"` -> `alignItemWithTrigger={false}`; `item-aligned` -> `alignItemWithTrigger` (default) |
| TooltipProvider | `delayDuration`, `skipDelayDuration` | `delay`; skip-delay concept dropped | rename / remove |
| Tooltip | `disableHoverableContent` | NO equivalent | remove; FLAG the behavior change in the report |
| Avatar.Image | `delayMs` | `delay` | rename |
| ScrollArea | `type="always"\|"scroll"\|...` | dropped | remove |
| Separator | `decorative` | dropped | remove |
| Checkbox | `checked="indeterminate"` | `indeterminate` is a SEPARATE boolean prop | `checked="indeterminate"` -> `indeterminate` + boolean `checked` |
| Slider | `onValueChange(value)` | signature gains event details; also `inverted` REMOVED | check handler arity; remove `inverted` (flag vertical-inverted usage) |
| Select | `onValueChange(value: string)` | widens to `(value: Value \| null, eventDetails)` | `useState<string>` + `onValueChange={setState}` breaks: widen state to `string \| null` or wrap the setter |
| Slider | `onValueCommit` | `onValueCommitted` | rename |
| ToggleGroup | `type="single"\|"multiple"` | `multiple` boolean; value shape arrays | same treatment as Accordion |
| ToggleGroup / Toolbar | `rovingFocus={false}` | dropped (roving focus always on); `loop` -> `loopFocus` | remove / rename |
| Menubar | `value`/`onValueChange` (active menu) | dropped; control per Menu.Root `open` | restructure if used; usually unused |
| Menubar | `loop` | `loopFocus` | rename |
| ContextMenu.Root | `modal` | REMOVED | remove |
| ContextMenu.Trigger | `disabled` | REMOVED | remove; gate the trigger yourself |
| DropdownMenu/ContextMenu items | (Radix closed menu on select) | `closeOnClick` defaults FALSE on CheckboxItem/RadioItem | behavior delta: flag; add `closeOnClick` only if the user asks |
| NavigationMenu | `delayDuration`(200), `skipDelayDuration`, `viewport` | `delay`(50) + `closeDelay`; viewport prop gone (Positioner handles it) | rename/remove; flag the 200->50 hover-delay feel change |
| Popover / HoverCard | `openDelay`/`closeDelay` on Root | move to TRIGGER as `delay`/`closeDelay` | relocate props Root -> Trigger |
| Dialog / AlertDialog | `onOpenAutoFocus` | `initialFocus` (element/ref-based, not event-based) | restructure: pass target instead of preventDefault handler |
| Dialog / AlertDialog | `onCloseAutoFocus` | `finalFocus` | same restructure |
| Dialog family | `onEscapeKeyDown`, `onPointerDownOutside`, `onInteractOutside` | consolidated; see the overlays reference for exact per-part signatures | consult overlays.md; do not guess |
| DirectionProvider | `dir` | `direction` | rename |

## Callback signature rule

Expand Down
Loading