Segmented controls
When Cal.com uses a pill row — and which primitive or composite to pick.
A segmented control is the compact pill row: one selected segment, shared height, muted track. In Cal.com the look is shared; the primitive is not. Pick from intent, then apply the shared styles.
Primitive APIs for Tabs and Radio Group live on coss.com/ui. This page covers which control to pick and the compositions we ship.
Choosing a control
| Intent | Use | Why |
|---|---|---|
| Exclusive value, always one selected | Segmented Radio Group | Filters, form values, view modes. Radio Group semantics. |
URL / ?type= destination | Segmented Nav | Real links + aria-current="page". |
| Associated content panels | Tabs | TabsList / TabsTab / TabsPanel stay together. |
| Underline page or profile nav | Horizontal Tabs | Listing profiles, mobile event-type page nav. |
| Exclusive filter that can be cleared | Toggle Group on coss.com | No Cal composite yet — none of the current call sites can clear. |
Always-on value, no URL change → SegmentedRadioGroup
Each segment is a destination → SegmentedNav
Panels switch with the selection → Tabs
Underline listing / page nav → HorizontalTabs
Do not restyle a TabsList to look like a segmented control when there are no panels. That is how the current call sites used to be wired, and it announced filters as tabs.
Shared styles
Sizes, the selected-item recipe and the sliding indicator live in @coss/ui/components/segmented-control. Segmented Nav and Segmented Radio Group render through SegmentedControl / SegmentedControlItem; Tabs (default variant) reuses its sizes and indicator surface. Do not hand-roll h-7.5 sm:h-6.5 overlays to fake size="sm".
import type { SegmentedControlSize } from "@coss/ui/components/segmented-control";| Size | When we use it |
|---|---|
default | Out of office, availability, billing period, admin OAuth status, org-new billing, event location mode |
sm | Bookings status, bookings view toggle, Insights wrong-routing, Form Builder email/phone, booking details drawer tabs |
lg | Exported; no product call site yet |
Segmented Nav
Destination chips. The <nav> landmark is the pill surface itself, so className on SegmentedNav (mb-4, hidden md:flex) lands on the track.
Real call sites of this shape: out of office, availability (desktop), routing form header (destinations; dirty forms still block with the info-lost dialog).
Segmented Radio Group
Always-on exclusive value. Text, text + badge, or icon-only — same composite.
Real call sites of this shape: billing period, org-new billing, event location mode, bookings view, admin OAuth / events / Discover filters, Insights wrong-routing, Form Builder email/phone.
Tabs
Keep Tabs when the selected value has an associated TabsPanel — booking details, member invite, attribute sync, org/system blocklists, agent configuration. Default Tabs pick up the shared sizes; they keep their own animated indicator.
<Tabs className="contents" value={activeTab} onValueChange={setActiveTab}>
<SheetHeader>
<TabsList>
<TabsTrigger value="outgoingCalls">{t("outgoing_calls")}</TabsTrigger>
<TabsTrigger value="phoneNumber">{t("phone_number")}</TabsTrigger>
<TabsTrigger value="incomingCalls">{t("incoming_calls")}</TabsTrigger>
</TabsList>
</SheetHeader>
<TabsPanel value="outgoingCalls">{/* … */}</TabsPanel>
</Tabs>Horizontal Tabs
Underline row for profile/category listing and for mobile event-type / event editor page nav. Not the pill segmented control.
See Horizontal Tabs. Call sites include /event-types, /apps, and the mobile tab bar on /event-types/<id> and /events/<id>.