Docs
Search

Tabs

This page is currently under construction
Lism UI (@lism-css/ui) is still in preparation.

A component for creating tab elements.
Only interaction behavior is provided — visual styling is intentionally left for you to customize.

Overview

Preview

Tab 01: Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut.

<Tabs.Root>
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: "/>
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>

Only <Tabs.Tab> and <Tabs.Panel> are recognized as children of <Tabs.Item>. Both must always be used together.

Styles

The Tabs base styles are defined in the following CSS.

_style.css
@layer lism-block {
  /* l--grid との併用が前提 */
  .b--tabs {
    grid: 'list' 'panel' / 100%;
    gap: var(--s20);
  }
  .b--tabs_list {
    grid-area: list;
    display: flex;
    gap: 0.25em;
    overflow-x: auto;
  }
  .b--tabs_tab {
    --hl: var(--hl--s);
    font-size: var(--fz--s);
    padding: 0.375em 0.5em;
  }
  .b--tabs_tab[aria-selected='true'] {
    --_notSelected: ;
  }
  .b--tabs_tab[aria-selected='false'] {
    --_isSelected: ;
  }
  .b--tabs_panel {
    grid-area: panel;
    width: 100%;
  }

  /* 既定バリアントの装飾。variant指定時は適用されないため、打ち消し不要で独自スタイルを組める */
  .b--tabs--default .b--tabs_tab {
    border-radius: var(--bdrs--10);
    color: var(--_notSelected, var(--text-2));
    background-color: var(--_notSelected, var(--base-2));
    box-shadow: var(--_isSelected, inset 0 0 0 1px currentColor);
  }

  /* line バリアント: 選択中のタブを下線で表示 */
  .b--tabs--line > .b--tabs_list {
    /* Memo: ov-x='auto' の時に margin がネガティブだと崩れるので、box-shadowで表現する。 */
    box-shadow: inset 0 -2px 0 var(--divider);
  }
  .b--tabs--line .b--tabs_tab {
    box-shadow: var(--_isSelected, inset 0 -2px 0 0 currentColor);
  }
}

The full source code is available on GitHub.

Usage

Provided as Tabs from the @lism-css/ui package.

Import

import { Tabs } from '@lism-css/ui/react';

Props

PropDescription
<Tabs.Root>
variant
Outputs the b--tabs--{variant} class. Defaults to default, and line is also provided out of the box. When you specify your own variant, the default variant’s decoration (b--tabs--default) no longer applies, so you can build your own styles without resetting the initial ones.
<Tabs.Root>
tabId
A string ID used to generate each tab button’s id ({tabId}-{index}-tab), each panel’s id ({tabId}-{index}), and the value of aria-controls. If omitted, an ID is generated automatically.
<Tabs.Root>
defaultIndex
Specifies which tab to open initially. Values out of range (e.g. a number greater than the number of tabs) fall back to 1.
<Tabs.Root>
listProps
Props passed to the list element (b--tabs_list) wrapping the tab buttons.

Keyboard interactions

When a tab button has focus, the following keyboard interactions are available.

KeyAction
Moves focus to the previous or next tab and selects it, looping to the opposite end when it reaches the first or last tab.
HomeMoves to and selects the first tab.
EndMoves to and selects the last tab.

For a vertical tab list, pass aria-orientation="vertical" through listProps. The arrow keys then switch to and .

<Tabs.Root listProps={{ 'aria-orientation': 'vertical' }}>

A horizontal tab list (the default) deliberately ignores and so that those keys keep their normal browser scrolling behavior.

Note that aria-orientation is an HTML attribute, so its value cannot change with the viewport width. For layouts that switch between horizontal and vertical responsively, keep the default (horizontal, ).

Examples

variant=“line”

Specify variant="line" for a line-style tab list that underlines the selected tab.

variant='line'

Tab 01: Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut.

<Tabs.Root variant="line">
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>

Default open tab

Use defaultIndex on <Tabs.Root> to specify which tab is open by default.
(Index numbers start from 1.)

Example

Tab 02: Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut. Aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint.

<Tabs.Root defaultIndex={2}>
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: "/>
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>

Specify the initial tab via URL parameter

Opening a page with ?lism-tab={tabId}-{index} in the URL displays the specified tab as selected from the start.

To use this feature, explicitly set tabId on <Tabs.Root>.
(If tabId is omitted, an ID is generated automatically, so it can’t be targeted from the URL.)

<Tabs.Root tabId="sample-tabs">
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>...</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>...</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>...</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>

Visiting a page with the tabs above using ?lism-tab=sample-tabs-2 displays the second tab as selected.

Vertical tabs

Setting grid-template:'list panel' auto / auto 1fr; on <Tabs.Root> (b--tabs) places the tab list and content side by side.

At the same time, changing the tab list (b--tabs_list) buttons to stack vertically (flex-direction:column;) achieves the following layout.

Example: side-by-side layout on @sm and above

Tab 01: Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut.

Resize
<Tabs.Root gt={[null, '"list panel" auto / auto 1fr']} listProps={{ fxd: [null, 'column'] }}>
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: "/>
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>

Opt-in

The following are example style variations you can build yourself.

b--tabs_tab exposes --_isSelected and --_notSelected variables, which are useful for styling. (They can be used in the same way as --_isHov and --_notHov from set--hov.)

variant=“emboss”

variant='emboss'

Tab 01: Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut.

<Tabs.Root variant="emboss">
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>
Required additional CSS
@layer lism-block {
  .b--tabs--emboss > .b--tabs_list {
    justify-self: center;
    padding: 4px;
    gap: 2px;
    background-color: var(--base-2);
    border-radius: var(--bdrs--20);
  }
  .b--tabs--emboss .b--tabs_tab {
    padding: 0.375em 0.75em;
    border-radius: calc(var(--bdrs--20) - 2px); /* 親の bdrs - (親のpadding / 2) */
    background-color: var(--_isSelected, var(--base));
    box-shadow: var(--_isSelected, var(--bxsh--10));
  }
}

variant=“folder”

variant='folder'

Tab 01: Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut.

<Tabs.Root variant="folder">
<Tabs.Item>
<Tabs.Tab>Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab>Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>

This creates a folder-style variation where the tab buttons appear connected to the panel.

Required additional CSS
@layer lism-block {
  .b--tabs--folder {
    --folder-bgc: var(--base-2);

    row-gap: 0;
  }
  .b--tabs--folder .b--tabs_tab {
    padding: 0.375em 1em;
    text-align: center;
    border-radius: var(--bdrs--20) var(--bdrs--20) 0 0;
    color: var(--_notSelected, var(--text-2));
    background-color: var(--_isSelected, var(--folder-bgc)) var(--_notSelected, transparent);
  }
  .b--tabs--folder > .b--tabs_panel {
    padding: var(--s25) var(--s30);
    background-color: var(--folder-bgc);
    border-radius: var(--bdrs--20);
    border-start-start-radius: 0;
  }
}

Fixed height

An example of how to fix the overall tabs height to match the tallest content panel.

Example (height is fixed to Tab 2)

Tab 01: Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut.

<Tabs.Root data-tabs-keep-height g="15">
<Tabs.Item>
<Tabs.Tab px="10">Tab 1</Tabs.Tab>
<Tabs.Panel>
<DummyText pre="Tab 01: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab px="10">Tab 2</Tabs.Tab>
<Tabs.Panel>
<DummyText length="xl" offset={1} pre="Tab 02: " />
</Tabs.Panel>
</Tabs.Item>
<Tabs.Item>
<Tabs.Tab px="10">Tab 3</Tabs.Tab>
<Tabs.Panel>
<DummyText length="l" offset={2} pre="Tab 03: " />
</Tabs.Panel>
</Tabs.Item>
</Tabs.Root>
Required additional CSS
@layer lism-custom {
  [data-tabs-keep-height] > .b--tabs_panel[hidden] {
    display: block;
    visibility: hidden;
    opacity: 0;
  }
}

© 2026 Lism CSS.