Components

DhPillTabs

Segmented pill control for switching between sibling views, built on Material3 TabRow — the segments share one rounded track and a variant-filled thumb floats to the selected segment as selection changes. Same selectedIndex / variant contract as DhTabs, plus a pageContent overload that bundles a swipeable HorizontalPager whose drag the thumb follows.

Basic — primary variant

The defaults: the segments share one rounded track, and a --color-primary thumb floats to the selected segment, sliding between segments as selection changes.

Light dh-pill-tabs pill, tabs, primary, label, stretch (light theme)
Dark dh-pill-tabs pill, tabs, primary, label, stretch (dark theme)
var current by remember { mutableStateOf(0) }

DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = listOf(
        DhTabs.Item("Upcoming"),
        DhTabs.Item("Completed"),
        DhTabs.Item("Overdue")
    )
)

Variant — secondary

The floating thumb fills with --color-secondary.

Light dh-pill-tabs pill, tabs, secondary, label, stretch (light theme)
Dark dh-pill-tabs pill, tabs, secondary, label, stretch (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = items,
    variant = DhTabs.Variant.Secondary
)

Variant — tertiary

The floating thumb fills with --color-tertiary.

Light dh-pill-tabs pill, tabs, tertiary, label, stretch (light theme)
Dark dh-pill-tabs pill, tabs, tertiary, label, stretch (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = items,
    variant = DhTabs.Variant.Tertiary
)

Variant — error

The floating thumb fills with --color-error. Useful for highlighting destructive-flow filters.

Light dh-pill-tabs pill, tabs, error, label, stretch (light theme)
Dark dh-pill-tabs pill, tabs, error, label, stretch (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = items,
    variant = DhTabs.Variant.Error
)

Icon-only configuration

Pass only the icon slot on each Item. Provide contentDescription for screen readers.

Light dh-pill-tabs pill, tabs, primary, icon, stretch (light theme)
Dark dh-pill-tabs pill, tabs, primary, icon, stretch (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = listOf(
        DhTabs.Item(icon = { DhIcon(DhIcons.Star, null, decorative = true) }, contentDescription = "Highlights"),
        DhTabs.Item(icon = { DhIcon(DhIcons.Heart, null, decorative = true) }, contentDescription = "Favourites"),
        DhTabs.Item(icon = { DhIcon(DhIcons.Search, null, decorative = true) }, contentDescription = "Browse")
    )
)

Label + icon configuration

Pass both label and icon on each Item; the chip lays them out inline.

Light dh-pill-tabs pill, tabs, primary, label, icon, stretch (light theme)
Dark dh-pill-tabs pill, tabs, primary, label, icon, stretch (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = listOf(
        DhTabs.Item("Highlights", icon = { DhIcon(DhIcons.Star, null, decorative = true) }),
        DhTabs.Item("Favourites", icon = { DhIcon(DhIcons.Heart, null, decorative = true) }),
        DhTabs.Item("Browse", icon = { DhIcon(DhIcons.Search, null, decorative = true) })
    )
)

With badges

Pass a badge on any Item; it rides the segment label via BadgedBox and stays inside the capsule on the floating thumb.

Light dh-pill-tabs pill, tabs, primary, badge, stretch (light theme)
Dark dh-pill-tabs pill, tabs, primary, badge, stretch (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = listOf(
        DhTabs.Item("Inbox", badge = "12"),
        DhTabs.Item("Drafts"),
        DhTabs.Item("Sent", badge = "99+")
    )
)

Scrollable overflow

When the segments are wider than the track, scrollable=true lays them out at their intrinsic width in a 90.dp-min horizontal scroller.

Light dh-pill-tabs pill, tabs, primary, scrollable, overflow (light theme)
Dark dh-pill-tabs pill, tabs, primary, scrollable, overflow (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = manyTabs,
    scrollable = true
)

Swipeable (HorizontalPager)

The content-hosting overload drops selectedIndex / onTabSelected and takes a pageContent lambda. It bundles a HorizontalPager: tapping a segment animates to that page, swiping moves the selection, and the floating thumb follows the swipe gesture frame by frame. (No preview — the swipe animation and the hosted page content can't be shown in a static snapshot; run the demo to see it.)

// Content-hosting overload: no selectedIndex / onTabSelected —
// the bundled HorizontalPager owns the selection, and a swipe
// moves both the page and the floating thumb.
DhPillTabs(
    tabs = listOf(
        DhTabs.Item("Day"),
        DhTabs.Item("Week"),
        DhTabs.Item("Month")
    )
) { page ->
    when (page) {
        0 -> DayContent()
        1 -> WeekContent()
        else -> MonthContent()
    }
}

Disabled chip

Tabs gated behind a permission or feature flag.

Light dh-pill-tabs pill, tabs, primary, disabled (light theme)
Dark dh-pill-tabs pill, tabs, primary, disabled (dark theme)
DhPillTabs(
    selectedIndex = current,
    onTabSelected = { current = it },
    tabs = listOf(
        DhTabs.Item("Active"),
        DhTabs.Item("Archive"),
        DhTabs.Item("Premium", enabled = false)
    )
)