Урок 12. Примеры: интерактив без JavaScript

📁 Раздел: Состояния и темы ⏱️ Время изучения: ~50 мин 🎯 Сложность: Средняя

⚡ Кратко

Шесть интерактивных примеров без единой строки JavaScript: кнопки, карточка-ссылка, поле с иконкой, выбор тарифа, аккордеон и меню.

1. Полный набор состояний кнопки

Ссылка
<div class="flex flex-wrap items-center gap-3">
  <button class="rounded-md bg-slate-900 px-4 py-2 text-sm font-medium text-white transition-colors
                 hover:bg-slate-700 active:bg-slate-800
                 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-900
                 disabled:cursor-not-allowed disabled:bg-slate-300">Обычная</button>
  <button class="rounded-md bg-slate-900 px-4 py-2 text-sm font-medium text-white disabled:cursor-not-allowed disabled:bg-slate-300" disabled>Отключена</button>
  <a href="#" class="rounded-md px-4 py-2 text-sm font-medium text-sky-700 underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-sky-600">Ссылка</a>
</div>

2. Карточка-ссылка целиком

<a href="#" class="group block max-w-sm rounded-xl border border-slate-200 bg-white p-4 transition-colors hover:border-slate-300 hover:bg-slate-50
                   focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-600">
  <div class="flex items-center justify-between">
    <h3 class="text-sm font-semibold text-slate-900">Ежемесячный отчёт</h3>
    <span class="text-slate-300 transition-transform group-hover:translate-x-1 group-hover:text-slate-500">→</span>
  </div>
  <p class="mt-1 text-sm text-slate-600">Кликабельна вся карточка, стрелка реагирует на наведение.</p>
</a>

3. Поле с иконкой и подсветкой обёртки

🔍
<div class="flex max-w-sm items-center gap-2 rounded-md border border-slate-300 px-3 py-2 transition-colors
            focus-within:border-sky-500 focus-within:ring-2 focus-within:ring-sky-500/30">
  <span class="text-slate-400">🔍</span>
  <input class="w-full text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none" placeholder="Поиск по документам">
</div>

focus-within позволяет подсветить всю обёртку, когда фокус попадает в поле внутри. Иначе рамка появлялась бы только у самого input, а иконка оставалась «снаружи».

4. Выбор тарифа карточками

<div class="grid gap-3 sm:grid-cols-3">
  <label class="cursor-pointer rounded-xl border border-slate-200 p-4 transition-colors has-checked:border-indigo-500 has-checked:bg-indigo-50 has-checked:ring-1 has-checked:ring-indigo-500">
    <input type="radio" name="tariff" class="sr-only">
    <p class="text-sm font-semibold text-slate-900">Старт</p>
    <p class="mt-1 text-xs text-slate-600">0 ₽ в месяц</p>
  </label>
  <label class="cursor-pointer rounded-xl border border-slate-200 p-4 transition-colors has-checked:border-indigo-500 has-checked:bg-indigo-50 has-checked:ring-1 has-checked:ring-indigo-500">
    <input type="radio" name="tariff" class="sr-only" checked>
    <p class="text-sm font-semibold text-slate-900">Команда</p>
    <p class="mt-1 text-xs text-slate-600">990 ₽ в месяц</p>
  </label>
  <label class="cursor-pointer rounded-xl border border-slate-200 p-4 transition-colors has-checked:border-indigo-500 has-checked:bg-indigo-50 has-checked:ring-1 has-checked:ring-indigo-500">
    <input type="radio" name="tariff" class="sr-only">
    <p class="text-sm font-semibold text-slate-900">Компания</p>
    <p class="mt-1 text-xs text-slate-600">по договору</p>
  </label>
</div>

Настоящий input скрыт классом sr-only: он остаётся доступным для клавиатуры и скринридера, но не виден. Всё оформление — на карточке через has-checked:.

5. Аккордеон на теге details

Как работает аккордеон без JavaScript? ▾

Тег details хранит состояние сам, а вариант group-open позволяет оформить содержимое и стрелку в зависимости от того, открыт блок или нет.

<details class="group max-w-md rounded-lg border border-slate-200 bg-white p-4">
  <summary class="flex cursor-pointer list-none items-center justify-between text-sm font-medium text-slate-900">
    Как работает аккордеон без JavaScript?
    <span class="text-slate-400 transition-transform group-open:rotate-180">▾</span>
  </summary>
  <p class="mt-3 text-sm text-slate-600">
    Тег details хранит состояние сам, а вариант group-open позволяет оформить
    содержимое и стрелку в зависимости от того, открыт блок или нет.
  </p>
</details>

6. Меню с состоянием в атрибуте

<div class="max-w-xs rounded-lg border border-slate-200 bg-white p-1">
  <button data-state="open" aria-expanded="true"
          class="flex w-full items-center justify-between rounded-md px-3 py-2 text-sm text-slate-700
                 aria-expanded:bg-slate-100 aria-expanded:font-medium aria-expanded:text-slate-900">
    Настройки
    <span class="text-slate-400 transition-transform data-[state=open]:rotate-180" data-state="open">▾</span>
  </button>
  <div class="mt-1 space-y-1 ps-3">
    <a class="block rounded-md px-3 py-1.5 text-sm text-slate-600 hover:bg-slate-100" href="#">Профиль</a>
    <a class="block rounded-md px-3 py-1.5 text-sm text-slate-600 hover:bg-slate-100" href="#">Уведомления</a>
  </div>
</div>

Приложение переключает атрибут, а внешний вид описан классами. Это тот же принцип, по которому устроены компоненты shadcn/ui.