Tabs

Organizes content into distinct sections, allowing users to switch between them.

Prague

06:05

3h 30m

Malaga

06:05


	<script lang="ts">
  import { Tabs } from "bits-ui";
  import Airplane from "phosphor-svelte/lib/Airplane";
</script>
 
<div class="pt-6">
  <Tabs.Root
    value="outbound"
    class="w-[390px] rounded-card border border-muted bg-background-alt p-3 shadow-card"
  >
    <Tabs.List
      class="grid w-full grid-cols-2 gap-1 rounded-9px bg-dark-10 p-1 text-sm font-semibold leading-[0.01em] shadow-mini-inset dark:border dark:border-neutral-600/30 dark:bg-background"
    >
      <Tabs.Trigger
        value="outbound"
        class="h-8 rounded-[7px] bg-transparent py-2 data-[state=active]:bg-white data-[state=active]:shadow-mini dark:data-[state=active]:bg-muted"
        >Outbound</Tabs.Trigger
      >
      <Tabs.Trigger
        value="inbound"
        class="h-8 rounded-[7px] bg-transparent py-2 data-[state=active]:bg-white data-[state=active]:shadow-mini dark:data-[state=active]:bg-muted"
        >Inbound</Tabs.Trigger
      >
    </Tabs.List>
    <Tabs.Content value="outbound" class="select-none pt-3">
      <div class="grid grid-cols-3 grid-rows-2 gap-0 p-4 pb-1">
        <div class="text-left">
          <h4
            class="mb-2 text-[20px] font-semibold leading-none tracking-[-0.01em]"
          >
            Prague
          </h4>
          <p class="text-sm font-medium text-muted-foreground">06:05</p>
        </div>
        <div class="self-end text-center">
          <p class="text-sm font-medium text-muted-foreground">3h 30m</p>
        </div>
        <div class="text-right">
          <h4
            class="mb-2 text-[20px] font-semibold leading-none tracking-[-0.01em]"
          >
            Malaga
          </h4>
          <p class="text-sm font-medium text-muted-foreground">06:05</p>
        </div>
        <div class="relative col-span-3">
          <hr
            class="border-1 relative top-4 h-px border-dashed border-border-input"
          />
 
          <div class="absolute left-1/2 -translate-x-1/2 bg-background-alt p-1">
            <Airplane class="size-6 rotate-90 text-muted-foreground" />
          </div>
        </div>
      </div>
    </Tabs.Content>
    <Tabs.Content value="inbound" class="select-none pt-3">
      <div class="grid grid-cols-3 grid-rows-2 gap-0 p-4 pb-1">
        <div class="text-left">
          <h4
            class="mb-2 text-[20px] font-semibold leading-none tracking-[-0.01em]"
          >
            Malaga
          </h4>
          <p class="text-sm font-medium text-muted-foreground">07:25</p>
        </div>
        <div class="self-end text-center">
          <p class="text-sm font-medium text-muted-foreground">3h 20m</p>
        </div>
        <div class="text-right">
          <h4
            class="mb-2 text-[20px] font-semibold leading-none tracking-[-0.01em]"
          >
            Prague
          </h4>
          <p class="text-sm font-medium text-muted-foreground">10:45</p>
        </div>
        <div class="relative col-span-3">
          <hr
            class="border-1 relative top-4 h-px border-dashed border-border-input"
          />
 
          <div class="absolute left-1/2 -translate-x-1/2 bg-background-alt p-1">
            <Airplane class="size-6 rotate-90 text-muted-foreground" />
          </div>
        </div>
      </div>
    </Tabs.Content>
  </Tabs.Root>
</div>

Structure

	<script lang="ts">
	import { Tabs } from "bits-ui";
</script>
 
<Tabs.Root>
	<Tabs.List>
		<Tabs.Trigger />
	</Tabs.List>
	<Tabs.Content />
</Tabs.Root>

Managing Value State

Bits UI offers several approaches to manage and synchronize the component's value state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:value directive. This method automatically keeps your local state in sync with the component's internal state.

	<script lang="ts">
	import { Tabs } from "bits-ui";
	let myValue = $state("");
</script>
 
<button onclick={() => (myValue = "tab-1")}> Activate tab 1 </button>
 
<Tabs.Root bind:value={myValue}>
	<!-- -->
</Tabs.Root>

Key Benefits

  • Simplifies state management
  • Automatically updates myValue when the internal state changes (e.g., via clicking on an tab's trigger)
  • Allows external control (e.g., switching tabs via a separate button)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onValueChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import { Tabs } from "bits-ui";
	let myValue = $state("");
</script>
 
<Tabs.Root
	value={myValue}
	onValueChange={(v) => {
		myValue = v;
		// additional logic here.
	}}
>
	<!-- ... -->
</Tabs.Root>

Use Cases

  • Implementing custom behaviors on value change
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the component's value state, use the controlledValue prop. This approach requires you to manually manage the value state, giving you full control over when and how the component responds to value change events.

To implement controlled state:

  1. Set the controlledValue prop to true on the Tabs.Root component.
  2. Provide a value prop to Tabs.Root, which should be a variable holding the current state.
  3. Implement an onValueChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { Tabs } from "bits-ui";
	let myValue = $state("");
</script>
 
<Tabs.Root controlledValue value={myValue} onValueChange={(v) => (myValue = v)}>
	<!-- ... -->
</Tabs.Root>

When to Use

  • Implementing complex logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

Orientation

The orientation prop is used to determine the orientation of the Tabs component, which influences how keyboard navigation will work.

When the orientation is set to 'horizontal', the ArrowLeft and ArrowRight keys will move the focus to the previous and next tab, respectively. When the orientation is set to 'vertical', the ArrowUp and ArrowDown keys will move the focus to the previous and next tab, respectively.

	<Tabs.Root orientation="horizontal">
	<!-- ... -->
</Tabs.Root>
 
<Tabs.Root orientation="vertical">
	<!-- ... -->
</Tabs.Root>

Activation Mode

By default, the Tabs component will automatically activate the tab associated with a trigger when that trigger is focused. This behavior can be disabled by setting the activationMode prop to 'manual'.

When set to 'manual', the user will need to activate the tab by pressing the trigger.

	<Tabs.Root activationMode="manual">
	<!-- ... -->
</Tabs.Root>

API Reference

Tabs.Root

The root tabs component which contains the other tab components.

Property Type Description
value $bindable
string

The active tab value.

Default: undefined
onValueChange
function

A callback function called when the active tab value changes.

Default: undefined
controlledValue
boolean

Whether or not the value is controlled or not. If true, the component will not update the value state internally, instead it will call onValueChange when it would have otherwise, and it is up to you to update the value prop that is passed to the component.

Default: false
activationMode
enum

How the activation of tabs should be handled. If set to 'automatic', the tab will be activated when the trigger is focused. If set to 'manual', the tab will be activated when the trigger is pressed.

Default: 'automatic'
disabled
boolean

Whether or not the tabs are disabled.

Default: false
loop
boolean

Whether or not the tabs should loop when navigating with the keyboard.

Default: true
orientation
enum

The orientation of the tabs.

Default: horizontal
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-orientation
enum

The orientation of the tabs.

data-tabs-root
''

Present on the root element.

Tabs.List

The component containing the tab triggers.

Property Type Description
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-orientation
enum

The orientation of the tabs.

data-tabs-list
''

Present on the list element.

Tabs.Trigger

The trigger for a tab.

Property Type Description
value required
string

The value of the tab this trigger represents.

Default: undefined
disabled
boolean

Whether or not the tab is disabled.

Default: false
ref $bindable
HTMLButtonElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-state
enum

The state of the tab trigger.

data-value
''

The value of the tab this trigger represents.

data-orientation
enum

The orientation of the tabs.

data-disabled
''

Present when the tab trigger is disabled.

data-tabs-trigger
''

Present on the trigger elements.

Tabs.Content

The panel containing the contents of a tab.

Property Type Description
value required
string

The value of the tab this content represents.

Default: undefined
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tabs-content
''

Present on the content elements.