# VisuallyHidden: Руководства

## Паттерны

### Подпись элемента управления с одной иконкой

Когда использовать:

- элемент управления визуально состоит только из иконки, но ему всё равно нужно доступное имя

Заметки о композиции:

- делайте скрытую подпись краткой
- предпочитайте одну ясную скрытую фразу

Компромиссы:

- сохраняет компактный внешний вид
- видимая ясность по-прежнему зависит от узнаваемости иконки

```tsx
import { Icon, IconButton, VisuallyHidden } from '@vega-ui/react';
import { BellIcon } from '@vega-ui/icons';
import { useState } from 'react';

export default function IconOnlyControlLabel() {
  const [unread, setUnread] = useState(4);

  return (
    <IconButton type='button' variant='secondary' appearance='ghost' onClick={() => setUnread(0)} style={{ position: 'relative' }}>
      <Icon><BellIcon /></Icon>
      <VisuallyHidden asChild>
        <span>Notifications, {unread} unread</span>
      </VisuallyHidden>
      {unread > 0 && (
        <span
          aria-hidden='true'
          style={{
            position: 'absolute',
            insetBlockStart: 'var(--spacing-1)',
            insetInlineEnd: 'var(--spacing-1)',
            minWidth: 'var(--spacing-8)',
            paddingInline: 'var(--spacing-2)',
            boxSizing: 'border-box',
            borderRadius: 999,
            background: 'var(--color-error)',
            color: 'var(--color-white)',
            fontSize: 'var(--font-size-1)',
            lineHeight: 'var(--spacing-8)',
            textAlign: 'center',
            fontVariantNumeric: 'tabular-nums',
          }}
        >
          {unread}
        </span>
      )}
    </IconButton>
  );
}
```

### Скрытое ARIA-описание

Когда использовать:

- полю нужен дополнительный контекст только для вспомогательных технологий

Заметки о композиции:

- задайте скрытому узлу id
- сошлитесь на него из элемента управления через `aria-describedby`

Компромиссы:

- гибко и переиспользуемо
- легко забыть обновить скрытый текст

```tsx
import { Button, Label, TextField, TextFieldInput, VisuallyHidden } from '@vega-ui/react';
import { FormEvent, useState } from 'react';

export default function HiddenHelperDescription() {
  const [budget, setBudget] = useState('');

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setBudget(String(new FormData(event.currentTarget).get('budget')));
  };

  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%' }}>
      <Label htmlFor='monthly-budget'>Monthly budget</Label>
      <TextField style={{ paddingInlineStart: 'var(--spacing-8)', gap: 'var(--spacing-2)' }}>
        <span aria-hidden='true' style={{ color: 'var(--label-secondary)' }}>$</span>
        <TextFieldInput
          id='monthly-budget'
          name='budget'
          type='number'
          min={0}
          step={50}
          defaultValue={500}
          aria-describedby='monthly-budget-currency'
          required
          style={{ paddingInlineStart: 0, fontVariantNumeric: 'tabular-nums' }}
        />
      </TextField>
      <VisuallyHidden asChild>
        <span id='monthly-budget-currency'>Amount in US dollars</span>
      </VisuallyHidden>
      <Button type='submit' style={{ alignSelf: 'flex-start', marginBlockStart: 'var(--spacing-9)' }}>Save budget</Button>
      <output style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums' }}>
        {budget === '' ? '' : `Budget set to $${budget}`}
      </output>
    </form>
  );
}
```

### Скрытый текст статуса

Когда использовать:

- визуальному индикатору загрузки или статуса нужен текст только для вспомогательных технологий

Заметки о композиции:

- держите текст синхронизированным с реальным состоянием
- не дублируйте вводящие в заблуждение устаревшие сообщения

Компромиссы:

- улучшает ясность для невизуального восприятия
- состояние легко рассинхронизировать, если логика родителя небрежна

```tsx
import { Button, Icon, VisuallyHidden } from '@vega-ui/react';
import { ShoppingCartIcon } from '@vega-ui/icons';
import { useState } from 'react';

const products = ['Linen shirt', 'Canvas tote', 'Wool socks'];

export default function HiddenStatusText() {
  const [cart, setCart] = useState<string[]>([]);
  const [announcement, setAnnouncement] = useState('');

  const add = (product: string) => {
    const next = [...cart, product];
    setCart(next);
    setAnnouncement(`${product} added. Cart has ${next.length} items.`);
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-8)', width: '100%' }}>
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-4)' }}>
        {products.map((product) => (
          <Button key={product} type='button' size='sm' variant='secondary' appearance='ghost' onClick={() => add(product)}>
            Add {product}
          </Button>
        ))}
      </div>
      <span aria-hidden='true' style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-3)', fontVariantNumeric: 'tabular-nums' }}>
        <Icon size='sm'><ShoppingCartIcon /></Icon>
        {cart.length}
      </span>
      <VisuallyHidden asChild>
        <p role='status'>{announcement}</p>
      </VisuallyHidden>
    </div>
  );
}
```

## Сравнение

### Быстрое правило выбора

Используйте `VisuallyHidden`, когда содержимое должно оставаться доступным для вспомогательных технологий, но не отображаться визуально. Используйте обычный текст, когда информация должна быть видна всем.

### `VisuallyHidden` или видимый вспомогательный текст

- Используйте `VisuallyHidden` для содержимого, предназначенного только для доступности.
- Используйте видимый вспомогательный текст, когда подсказка полезна всем пользователям.

### `VisuallyHidden` или `aria-label`

- Используйте `VisuallyHidden`, когда нужно реальное содержимое в DOM, на которое можно сослаться или которое можно компоновать.
- Используйте `aria-label` для коротких прямых подписей, когда отдельный узел с содержимым не нужен.

### Выбирайте этот компонент, когда

- содержимое должно оставаться доступным, но не видимым
- полезен скрытый узел с описанием или подписью

### Не выбирайте этот компонент, когда

- информация должна быть видна в обычном интерфейсе
- скрытым текстом пытаются обойти исправление видимого UX

## Решение проблем

### Скрытый текст звучит неверно или устарел

#### Симптом

Пользователи вспомогательных технологий получают текст, который больше не соответствует видимому интерфейсу.

#### Вероятная причина

Скрытый текст не обновили вместе с видимым состоянием.

#### Как проверить

- сравните скрытую строку с текущими переходами видимого состояния

#### Исправление

Формируйте скрытое содержимое из того же состояния, что и видимый интерфейс.

### Интерфейс остаётся неясным даже со скрытым текстом

#### Симптом

Доступность немного улучшилась, но видимый интерфейс по-прежнему слабый или двусмысленный.

#### Вероятная причина

`VisuallyHidden` используется, чтобы компенсировать плохие видимые подписи.

#### Как проверить

- оцените интерфейс без содержимого, предназначенного только для вспомогательных технологий

#### Исправление

Сначала улучшите видимые подписи, а скрытый текст оставьте только там, где он действительно полезен.

### Скрытое содержимое неожиданно появляется снова

#### Симптом

Содержимое, которое должно быть скрыто, становится видимым в некоторых раскладках или после переопределений.

#### Вероятная причина

CSS визуального скрытия был переопределён.

#### Как проверить

- проверьте вычисленные стили на правила обрезки и позиционирования

#### Исправление

Сохраняйте стандартный CSS-контракт визуального скрытия и не переопределяйте его без необходимости.
