Skip to content

refactor(segmented-control): ship as a render-prop component - #907

Merged
pasqualevitiello merged 1 commit into
mainfrom
refactor/segmented-control-component
Oct 21, 2026
Merged

pasqualevitiello merged 1 commit into
mainfrom
refactor/segmented-control-component

Conversation

@pasqualevitiello

Copy link
Copy Markdown
Contributor

Summary

Segmented control becomes a component instead of a styling library. The element passed to render still decides the behavior, so the “choose the primitive first” guidance holds.

 apps/ui/registry/default/
 ├── lib/
-│   ├── segmented-control.ts            # cva recipe + class strings
-│   └── segmented-control-indicator.tsx # client component in lib/
 └── ui/
+    ├── segmented-control.tsx           # SegmentedControl, SegmentedControlItem, built-in indicator
     └── tabs.tsx                        # imports shared sizes/surface from ui/segmented-control
- <RadioGroupPrimitive className={segmentedControlRootClassName} defaultValue="monthly">
-   <RadioPrimitive.Root className={itemClassName} value="monthly">Monthly</RadioPrimitive.Root>
-   <SegmentedControlIndicator />
- </RadioGroupPrimitive>
+ <SegmentedControl render={<RadioGroupPrimitive defaultValue="monthly" />}>
+   <SegmentedControlItem render={<RadioPrimitive.Root value="monthly" />}>Monthly</SegmentedControlItem>
+ </SegmentedControl>

- <nav><div className={segmentedControlRootClassName}><a className={itemClassName} …/></div></nav>
+ <SegmentedControl render={<nav />}>
+   <SegmentedControlItem render={<a … />}>…</SegmentedControlItem>
+ </SegmentedControl>
  • SegmentedControl renders the indicator after its children and shares size through context, like TabsList. Items can override size.
  • One item recipe styles data-checked, aria-current="page", and data-pressed, so the state option is gone.
  • Before hydration the active item paints a static pill, and the indicator takes over once it is visible.
  • Registry: @coss/segmented-control is now registry:ui, and @coss/segmented-control-indicator is removed (its stale public/r JSON is deleted). @coss/tabs keeps @coss/segmented-control as its dependency.
  • Particles p-radio-group-7/8/9 and p-navigation-1/2/3 are migrated, and navigation renders the <nav> as the root, matching the radio examples.
  • Docs: the segmented control page uses the standard component layout (installation, usage, API reference, examples, changelog link), the Tabs manual install is updated, and the skill reference moves to references/primitives/segmented-control.md.
  • Changelog: an Oct 20, 2026 entry with migration diffs and an agent migration prompt.

Evidence

Checked in the browser on the docs page:

  • All 7 indicators sit on their active item to within 0.01px at sm, default, and lg, in light and dark mode. The item’s own pill is suppressed while the indicator is visible.
  • Clicking a radio or a link slides the pill, and the links navigate.
  • The Toggle Group composition from the docs (tried with a temporary particle) hides the pill when nothing is pressed, slides between items, and hides it again when the item is cleared.
  • In server-rendered HTML all 7 indicators are hidden, and the 4 checked radios and 3 current links carry their state attributes, so the static pill shows before hydration.
  • registry:validate-deps, registry:build, ui:sync, and tsc --noEmit in apps/ui and packages/ui pass. Biome is clean apart from informational notes.

Merge Danger

Door: two-way

The registry change can be reverted, but anyone who updates in the meantime must follow the migration.

Blast Radius: consumers

Projects with a local lib/segmented-control.ts keep working until they update. Updating @coss/tabs through the CLI now pulls in components/ui/segmented-control.tsx. Custom compositions that use the removed exports (segmentedControlRootClassName, segmentedControlItemVariants, SegmentedControlIndicator) must migrate following the changelog entry. @coss/segmented-control-indicator no longer resolves.

- Add SegmentedControl and SegmentedControlItem in ui/segmented-control.tsx;
  the rendered element (Radio Group, nav and links, Toggle Group) provides
  the behavior
- Render the sliding indicator inside SegmentedControl and share size via
  context like TabsList
- Remove lib/segmented-control.ts and lib/segmented-control-indicator.tsx;
  @coss/segmented-control is now a registry:ui item and
  @coss/segmented-control-indicator is removed
- Tabs imports the shared sizes, layout and indicator surface from the
  component
- Migrate the radio group and navigation particles, docs, skill references,
  and add a changelog entry with an agent migration prompt

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
coss-com Ready Ready Preview Oct 20, 2026 2:58pm UTC
coss-com-ui Ready Ready Preview Oct 20, 2026 2:58pm UTC
coss-examples-calcom Ready Ready Preview Oct 20, 2026 2:58pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
coss-com-origin Skipped Skipped Oct 20, 2026 2:58pm UTC

Request Review

@pasqualevitiello
pasqualevitiello merged commit 7e7381e into main Oct 21, 2026
13 checks passed
@pasqualevitiello
pasqualevitiello deleted the refactor/segmented-control-component branch October 21, 2026 07:59
@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown

ENG-6575

This branch was successfully deployed

3 active and 1 inactive deployments
Preview – coss-com-ui — d697c535 Deployed Oct 20, 2026 by vercel[bot]
Preview – coss-com — d697c535 Deployed Oct 20, 2026 by vercel[bot]
Preview – coss-examples-calcom — d697c535 Deployed Oct 20, 2026 by vercel[bot]
Preview – coss-com-origin — d697c535 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants