---
url: https://nsh.dev/ru/elements/tabs
description: "Tab и Tabs: вкладки списка вкладок и сам список. Каждая вкладка отвечает за нажатия, активацию с клавиатуры, отключённое состояние, а также за свою роль и состояние выбора для программ чтения с экрана; список представляет собой набор вкладок с индикатором, который нативно следует за выбранной вкладкой; внешний вид, значение и содержимое каждой вкладки определяет приложение."
demo: https://nsh.dev/play/index.html#el-tabs
---

# Tabs

Вкладки переключают разделы одного места: обзор и активность проекта, представления документа.
Каждый `Tab` отвечает за поведение одной вкладки: нажатия, активацию с клавиатуры, отключённое состояние, а также роль вкладки и её выбранное состояние для программ чтения с экрана. `Tabs` вокруг них — это список вкладок, который программы чтения с экрана объявляют как единый набор. Внешний вид определяет приложение: у вкладки нет собственных цветов, размеров или подписи. Её дочерние элементы — это подпись приложения, а классы `selected:` на вкладке или классы `group-selected:` на том, что внутри неё, следуют её состоянию. Содержимое каждого раздела тоже за приложением: оно показывает раздел, который назван значением вкладки, рядом со списком. Индикатор списка, подчёркивание или «таблетка», стилизуемые приложением, находится на выбранной вкладке и переходит к следующей нативно ([Индикатор](#indicator)).

```rust
use nsh_base::{tab, tabs, theme};
use nsh_core::prelude::*;

fn sections() -> impl IntoElement {
    let section = signal("overview");
    let item = move |value: &'static str, label: &'static str| {
        let selected = move || section.get() == value;
        tab()
            .selected(selected)
            .on_press(move |_: &()| section.set(value))
            .rounded(move || theme().radius)
            .px_3()
            .py_2()
            .child(text(label).text_sm().text_color(move || {
                if selected() { theme().foreground } else { theme().muted_foreground }
            }))
    };
    view()
        .gap_4()
        .child(
            tabs()
                .flex_row()
                .self_start()
                .gap_1()
                .p_1()
                .rounded(move || theme().radius)
                .bg(move || theme().muted)
                .indicator_style(|s| {
                    s.top_1()
                        .bottom_1()
                        .rounded(move || theme().radius)
                        .bg(move || theme().background)
                })
                .child(item("overview", "Overview"))
                .child(item("activity", "Activity")),
        )
        .child(text(move || {
            if section.get() == "overview" {
                "Visitors, sign-ups and conversion this week."
            } else {
                "Deploys, invoices and new members, newest first."
            }
        }).text_color(move || theme().foreground))
}
```

```tsx
import { mount, signal, Text, View } from "nsh";
import { Tab, Tabs } from "nsh/base";

function App() {
  const section = signal("overview");
  const item = (value: string, label: string) => (
    <Tab
      selected={section() === value}
      onPress={() => section.set(value)}
      class="group rounded-(--radius) px-3 py-2"
    >
      <Text class="text-sm text-muted-foreground group-selected:text-foreground">{label}</Text>
    </Tab>
  );
  return (
    <View class="gap-4">
      <Tabs
        class="flex-row self-start gap-1 rounded-(--radius-lg) bg-muted p-1"
        indicatorClass="inset-y-1 rounded-(--radius) bg-background"
      >
        {item("overview", "Overview")}
        {item("activity", "Activity")}
      </Tabs>
      <Text class="text-foreground">
        {section() === "overview"
          ? "Visitors, sign-ups and conversion this week."
          : "Deploys, invoices and new members, newest first."}
      </Text>
    </View>
  );
}

mount(App);
```

`Tab` и `Tabs` берутся из [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), крейта `nsh-base` на Rust. Значение принадлежит приложению: список его не хранит. Каждой вкладке сообщается, выбрана ли она; нажатие сообщает `onPress`, приложение задаёт своё значение и показывает этот раздел, а каждая вкладка показывает `selected`, которое возвращает приложение. Скругления списка и вкладок концентричны: отступ списка (`p-1`) равен разности `radius.lg` и `radius`. Заливка выбранной вкладки — это индикатор списка (`indicatorClass`, внутри этого отступа: `inset-y-1`), который пружинно переходит к вкладке, выбранной приложением. В Rust их создают `tab()` и `tabs()`, с сеттером на каждое свойство (`.selected(..)`), слушателем на каждое событие (`.on_press(..)`), типизированными методами стилей и `.child(..)` для их содержимого.

<a id="props"></a>

## Свойства

### Tab

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `selected` | `.selected(..)` | `boolean` | `false` | Текущая вкладка своего списка: программы чтения с экрана сообщают, что она выбрана, и применяются стили `selected:`. Нажатие сообщает `onPress`; вкладка показывает значение, которое возвращает приложение. |
| `disabled` | `.disabled(..)` | `boolean` | `false` | Нет нажатий и отклика на нажатие; применяются стили `disabled:`, а программы чтения с экрана сообщают, что вкладка недоступна. |
| `selectedStyle` | `.selected_style(\|s\| ..)` | `StyleProp<ViewStyle>` | нет | Стиль вкладки, пока она выбрана и не отключена, в виде классов `selected:`. |
| `accessibilityLabel` | `.accessibility_label(..)` | `string` | нет | То, что произносят программы чтения с экрана: название вкладки, если в её дочерних элементах нет текста, который его задаёт (значок). |
| `children` | `.child(..)` | `JSX.Element` | нет | Подпись, значок или бейдж приложения, которые озвучивают программы чтения с экрана. |
| `class`, `style` | методы стилей | `string`, `StyleProp<ViewStyle>` | нет | Сама вкладка: поверхность вокруг её подписи. |
| `activeStyle`, `hoverStyle`, `focusStyle`, `disabledStyle` | `.active(\|s\| ..)`, `.hover(..)`, `.focus(..)`, `.disabled_style(..)` | `StyleProp<ViewStyle>` | нет | Стиль вкладки при нажатии, наведении, фокусе или в отключённом состоянии, в виде классов `<state>:`. |

<a id="tabs-1"></a>

### Tabs

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `indicatorClass` | `.indicator_class(..)` | `string` | нет | Классы Tailwind индикатора на выбранной вкладке: его вид и положение поперёк списка (`bottom-0 h-0.5` для подчёркивания в строке, `inset-y-1` для «таблетки»). Вдоль списка он охватывает выбранную вкладку внутри собственных отступов по этой оси. С `indicatorClass` или `indicatorStyle` у списка есть индикатор. |
| `indicatorStyle` | `.indicator_style(\|s\| ..)` | `StyleProp<ViewStyle>` | нет | Стиль индикатора поверх `indicatorClass`. |
| `children` | `.child(..)` | `JSX.Element` | нет | Вкладки списка и всё, что приложение размещает между ними. |
| `class`, `style` | методы стилей | `string`, `StyleProp<ViewStyle>` | нет | Раскладка и внешний вид списка: по умолчанию колонка, `flex-row` для строки. |

<a id="events"></a>

## События

| Событие | Rust | Описание |
|---|---|---|
| `onPress` (`Tab`) | `.on_press(\|_: &()\| ..)` | Вкладку нажали: касание, клик, Space или Return, пока у неё фокус клавиатуры, либо активация программой чтения с экрана; или стрелка, Home или End её списка переместили на неё фокус клавиатуры. Срабатывает до каких-либо изменений: вкладка показывает `selected`, которое возвращает приложение. |

Выбранная вкладка тоже сообщает о своих нажатиях, поэтому приложение может отреагировать на второе нажатие (например, прокрутить её раздел к началу).
Отключённая вкладка ничего не сообщает.

<a id="parts"></a>

## Части

| Часть | Внутри | Стилизуется через | Состояния |
|---|---|---|---|
| Список | | `class`, `style` у `Tabs` | |
| Индикатор | список, позади вкладок | `indicatorClass`, `indicatorStyle` | на выбранной вкладке |
| Вкладка | список | `class`, `style` у `Tab` | `selected:`, `disabled:`, `active:`, `hover:`, `focus:` (или `selectedStyle`, `disabledStyle`, ...) |
| Подпись | вкладка | собственные элементы (`children`) | `group-selected:`, `group-disabled:` и другие варианты `group-*:` с `group` на `class` вкладки |
| Содержимое раздела | рядом со списком | элементы приложения | значение приложения |

Показывайте выбранную вкладку через её собственную поверхность: заливку, более выразительную подпись, более жирное начертание (см.
[Руководства по дизайну](https://nsh.dev/translations/ru/docs/design-guides.md#interaction-states)).

<a id="indicator"></a>

## Индикатор

Подчёркивание под выбранной вкладкой, «таблетка» позади неё или приподнятый сегмент сегментированного элемента управления — это индикатор списка: представление, которое приложение стилизует с помощью `indicatorClass` и `indicatorStyle`, рисуется позади вкладок и абсолютно позиционируется в списке. Native Shell помещает его на выбранную вкладку после каждой раскладки. Вдоль списка (ширина строки, высота колонки) он охватывает эту вкладку внутри собственных отступов индикатора по этой оси (`mx-3` для подчёркивания короче вкладки); поперёк списка его размещают его классы:

```tsx
// An underline at the bottom of a row of tabs.
<Tabs class="flex-row gap-4 border-b border-border" indicatorClass="bottom-0 h-0.5 bg-foreground">
  {item("overview", "Overview")}
  {item("activity", "Activity")}
</Tabs>

// A pill behind the selected tab, inside the list's padding.
<Tabs class="flex-row self-start gap-1 rounded-(--radius-lg) bg-muted p-1" indicatorClass="inset-y-1 rounded-(--radius) bg-background shadow-sm">
  {item("overview", "Overview")}
  {item("activity", "Activity")}
</Tabs>
```

Когда приложение выбирает другую вкладку, индикатор пружинно переходит к ней нативно, как индикатор панели вкладок GPUI Kit; при сниженной анимации он перемещается мгновенно. Пока ни одна вкладка не выбрана, он скрыт; если выбраны несколько, он находится на первой. Изменение размера или ширины вкладки перемещает его в том же кадре, что и вкладки. Индикатору не нужны события раскладки и код приложения: выбранная вкладка — та, у которой `selected` истинно. В списке с прокруткой представление прокрутки охватывает `Tabs`, а индикатор прокручивается вместе с вкладками.

<a id="disabled"></a>

## Отключённое состояние

Отключённая вкладка не принимает нажатий, а программы чтения с экрана объявляют её недоступной. Её классы `disabled:` применяются поверх классов `selected:`, а классы `group-disabled:` внутри неё следуют за ними:

```tsx
<Tab
  selected={section() === "billing"}
  disabled={!isOwner()}
  onPress={() => section.set("billing")}
  class="group rounded-(--radius) px-3 py-2 selected:bg-background disabled:opacity-50"
>
  <Text class="text-sm text-muted-foreground group-selected:text-foreground">Billing</Text>
</Tab>
```

<a id="accessibility"></a>

## Доступность

- Вкладка — один элемент для программ чтения с экрана с ролью `"tab"` и состоянием выбора. Её подпись — текст её дочерних элементов или `accessibilityLabel`.
- `Tabs` имеет роль `"tablist"`: программы чтения с экрана воспринимают вкладки внутри него как один набор, при этом каждая остаётся отдельным элементом.
- `disabled` объявляется так, как это делает платформа (VoiceOver, TalkBack, UI Automation, дерево доступности браузера).
- На macOS и в вебе список — одна остановка фокуса клавиатуры, а стрелки перемещают фокус между его вкладками (см. ниже).
- Индикатор скрыт от программ чтения с экрана и не принимает фокус: элементами списка являются только вкладки.

О подписях, ролях и объявлениях в целом см. [Доступность](https://nsh.dev/translations/ru/docs/accessibility.md).

<a id="keyboard"></a>

## Клавиатура

На macOS и в вебе, где Tab переходит к представлениям, принимающим нажатия, список `Tabs` следует [паттерну WAI-ARIA для вкладок](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/):

| Клавиша | Действие |
|---|---|
| Tab, Shift+Tab | Входит в список на выбранной вкладке (на первой неотключённой, если ни одна не выбрана) и покидает его за один шаг. |
| Right, Down | Перемещает фокус на следующую вкладку, с последней на первую. |
| Left, Up | Перемещает фокус на предыдущую вкладку, с первой на последнюю. |
| Home, End | Перемещает фокус на первую или последнюю вкладку. |
| Space, Return | Нажимает вкладку, на которой находится фокус. |

Стрелки пропускают отключённые вкладки, а вкладка, на которую они переходят, сообщает `onPress`, поэтому приложение выбирает её по мере перемещения фокуса: раздел следует за фокусом. Работают обе пары стрелок независимо от раскладки списка, которая задаётся классом приложения. В раскладке справа налево строка идёт справа, поэтому Left и Right меняются местами: Left переходит к следующей вкладке, а Right — к предыдущей; Up, Down, Home и End остаются прежними. Стили `focus:` показывают, где находится фокус. `Tab` вне `Tabs` — отдельная остановка.

<a id="platform-notes"></a>

## Особенности платформ

- iOS, Android и Mac Catalyst: нажатие — это касание, заканчивающееся на вкладке, включая её подпись.
- Настольные платформы (macOS, Windows, Linux, GPUI) и веб: нажатие — это клик. На macOS и в вебе Space и Return также нажимают вкладку, у которой фокус клавиатуры, а стрелки, Home и End перемещают его ([Клавиатура](#keyboard)).
- На каждой платформе рисуется то, что стилизует приложение: вкладки, поверхность выбранной вкладки, индикатор и содержимое разделов. Перемещение индикатора — нативная анимация раскладки на каждой платформе.
