Lynx/Modules/Zero/Tabs
@sigx/lynx-zero · Beta · Component library

Tabs#

A row of tabs, each showing one panel. A tap activates a tab. On Lynx, the active-tab indicator measures and positions itself, and an inactive panel is unmounted rather than hidden.

The anatomy is zero's tabs scope, shared with the web. See Tabs in @sigx/zero for the contract. This page covers what is different on Lynx.

Import#

TSX
import { Tabs } from '@sigx/lynx-zero';

Tabs is a compound: Tabs.Root, Tabs.List, Tabs.Tab, Tabs.Indicator and Tabs.Panel.

Usage#

TSX
import { component } from '@sigx/lynx';
import { Tabs } from '@sigx/lynx-zero';

export const Mailbox = component(({ signal }) => {
    const state = signal({ tab: 'inbox' });
    return () => (
        <Tabs.Root model={() => state.tab} variant="border" color="primary">
            <Tabs.List>
                <Tabs.Tab value="inbox"><text>Inbox</text></Tabs.Tab>
                <Tabs.Tab value="sent"><text>Sent</text></Tabs.Tab>
                <Tabs.Tab value="archive" disabled><text>Archive</text></Tabs.Tab>
            </Tabs.List>
            <Tabs.Panel value="inbox"><text>No new mail.</text></Tabs.Panel>
            <Tabs.Panel value="sent"><text>Sent items.</text></Tabs.Panel>
            <Tabs.Panel value="archive"><text>Archived items.</text></Tabs.Panel>
        </Tabs.Root>
    );
});

Bind the active tab with model, or leave the model off and pass defaultValue. valueChange fires either way. A tab and its panel pair up by value.

What the platform changes#

Inactive panels unmount#

Lynx has no hidden attribute and no attribute selectors, so an inactive panel is not rendered at all. That is the Lynx form of the anatomy's hiddenIn: ['inactive']. A panel's state does not survive a tab switch. Lynx always behaves like the web's unmountOnExit, so the web's lazyMount and unmountOnExit props are not taken. Keep state that must outlive a switch above Tabs.Root.

The indicator positions itself#

The indicator part is a mark over the active tab. On the web, the part publishes --tabs-indicator-* custom properties and the recipe positions it. Lynx cannot do that, so here the indicator is an absolute box over the active tab. It is measured from the tab's and the list's rects, and the skin only paints it. A press scale on the tab does not shrink it, because its size comes from the tab's layout frame, which ignores transforms.

Tabs.List renders an indicator on its own. On Lynx that is where daisy's active underline lives, because the web draws it with a ::before that Lynx drops. To give the indicator a class, or to order it among the list's children, place a Tabs.Indicator in the list. The list then skips its own:

TSX
<Tabs.List>
    <Tabs.Tab value="inbox"><text>Inbox</text></Tabs.Tab>
    <Tabs.Tab value="sent"><text>Sent</text></Tabs.Tab>
    <Tabs.Indicator class="my-indicator" />
</Tabs.List>

The indicator is decoration and is hidden from the reader, because the tabs carry the semantics. Tabs are measured only while an indicator is mounted. A list with a border would offset the indicator by that border's width. No shipped skin borders the list.

Tapping, not arrow keys#

There is no keyboard, so a tab is activated by a tap and zero's list controller runs in registration order: render order is visual order. Each tab is announced as a button, selected while active. A touch scales the tab on the main thread and sets its pressed flag. See Press feedback. A disabled tab, or any tab inside a disabled Field.Root, ignores the tap.

Props#

Tabs.Root#

PropTypeDefaultDescription
modelstring—Two-way binding of the active tab's value.
defaultValuestring—The initial active tab when there is no model.
orientation'horizontal' | 'vertical''horizontal'Stamped on the root, list, tabs, indicator and panel.
color / size / variantstringskin defaultThe design system's variant axes. daisy's variants include border, box and lift.
classstring—Extra classes.
EventPayloadDescription
valueChange (onValueChange)stringThe active tab changed.

Tabs.List#

PropTypeDescription
classstringExtra classes.

Renders its own indicator unless a Tabs.Indicator is placed inside it.

Tabs.Tab#

PropTypeDefaultDescription
valuestringrequiredThis tab's value. Matches a Tabs.Panel.
disabledbooleanfalseIgnores taps and shows no press. ORed with an enclosing Field's.
classstring—Extra classes.

Tabs.Indicator#

PropTypeDescription
classstringExtra classes.

Tabs.Panel#

PropTypeDefaultDescription
valuestringrequiredThe tab this panel belongs to. Rendered only while that tab is active.
classstring—Extra classes.

Anatomy on Lynx#

PartElementStatesFlags
rootview——
listview——
tabview (button trait, selected while active)active, inactivedisabled, pressed
indicatorview (hidden from the reader, positioned inline)——
panelview (only while active)active—

See also#