Display

Pressed Tile Button

Плитка-кнопка с честным нажатым состоянием: включённость объявлена через aria-pressed и показана утопленной тенью, а не одним цветом.

  • icontile
  • toggle
  • button
  • pressed

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.

размести это в шапке: https://vibeui.ru/c/icontile-006

Показать полную инструкцию

То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

# Установка и размещение «icontile-006» (Pressed Tile Button) из VibeUI

## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/icontile-006.json

Registry item: https://vibeui.ru/r/icontile-006.json
Ставится в: components/vibeui/icontile-006.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.

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

## 2. Что это
Плитка-кнопка с честным нажатым состоянием: включённость объявлена через aria-pressed и показана утопленной тенью, а не одним цветом.

Плитка-переключатель: aria-pressed для состояния, утопленная тень для видимости нажатия. Ноль зависимостей, один файл, состояние в useState.

## 3. Как использовать
import { Icontile006 } from "@/components/vibeui/icontile-006"

<Icontile006 glyph="★" label="В избранное" defaultPressed />

Полный список пропсов — в установленном файле.

## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.

Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)

## 5. Сохранить как установлено
- локальную палитру --vibeui-icontile-006-* и не подменять её токенами вашей темы
- aria-pressed вместо класса: без него скринридер не скажет, включено состояние или нет
- два признака нажатия сразу — цвет и утопленную тень: цвет один не работает в чёрно-белом режиме
- aria-label на кнопке: плитка со знаком без имени бесполезна для скринридера
- видимую обводку фокуса — плитку часто ставят в ряд и переключают с клавиатуры
- светлую заливку в покое: тёмная кнопка на тёмном фоне каталога пропадает

## 6. Можно менять
- знак кнопки через glyph и её имя через label
- начальное состояние через defaultPressed
- оттенок нажатого состояния через переменную --vibeui-icontile-006-hue
- размер через переменную --vibeui-icontile-006-size

## 7. Правила
- Состояние внутреннее: для управляемого варианта поднимите его в родителя и уберите useState.
- Кнопка-переключатель не заменяет чекбокс в форме — она ничего не отправляет.
- Сдвиг на пиксель при нажатии отключается в prefers-reduced-motion вместе с переходом.
- Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.

## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.

Для разработчика

npx shadcn@latest add https://vibeui.ru/r/icontile-006.json
https://vibeui.ru/r/icontile-006.json

Клиентский компонент: держит состояние в useState, поэтому в файле есть «use client». Состояние объявлено атрибутом aria-pressed, а не классом — без него скринридер сообщит просто «кнопка» и не скажет, включено ли. Визуально нажатие показано двумя признаками сразу: заливка меняется и плитка утоплена inset-тенью со сдвигом на пиксель, поэтому включённость видна и в чёрно-белом режиме. Доступное имя берётся из label, знак помечен aria-hidden. Заливка в покое светлая, так что кнопка читается на тёмной подложке каталога.

Исходник компонента

Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

"use client"

import { useState } from "react"
import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Icontile006Props = Omit<
  ComponentPropsWithoutRef<"button">,
  "children"
> & {
  glyph?: string
  label?: string
  defaultPressed?: boolean
}

// Идея компонента: плитка-переключатель с честным нажатым состоянием.
// Состояние объявлено через aria-pressed, а не подкрашено классом: без него
// скринридер сообщит просто «кнопка» и не скажет, включено ли. Нажатие видно
// не одним цветом — плитка утоплена внутренней тенью и сдвинута на пиксель,
// поэтому включённость читается и в чёрно-белом режиме.
const STYLES = `
:where([data-vibeui-block="icontile-006"]){
--vibeui-icontile-006-size:2.75rem;
--vibeui-icontile-006-hue:262;
--vibeui-icontile-006-idle:oklch(0.96 0.004 265);
--vibeui-icontile-006-fg:oklch(0.36 0.014 265);
--vibeui-icontile-006-border:oklch(0.89 0.006 265);
--vibeui-icontile-006-ring:oklch(0.55 0.17 262 / 60%);
--vibeui-icontile-006-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="icontile-006"]{
appearance:none;cursor:pointer;
display:inline-grid;place-items:center;flex:none;box-sizing:border-box;
width:var(--vibeui-icontile-006-size);height:var(--vibeui-icontile-006-size);
padding:0;border-radius:0.875rem;
background:var(--vibeui-icontile-006-idle);
border:1px solid var(--vibeui-icontile-006-border);
color:var(--vibeui-icontile-006-fg);
font-family:var(--vibeui-icontile-006-font);
font-size:calc(var(--vibeui-icontile-006-size) * 0.44);line-height:1;
transition:background-color .16s ease,color .16s ease,transform .1s ease,box-shadow .16s ease;
}
[data-vibeui-block="icontile-006"]:hover{background:oklch(0.93 0.01 var(--vibeui-icontile-006-hue))}
[data-vibeui-block="icontile-006"]:focus-visible{outline:2px solid var(--vibeui-icontile-006-ring);outline-offset:2px}
/* Нажатое состояние — не только цвет: утопленная тень видна и без цвета. */
[data-vibeui-block="icontile-006"][aria-pressed="true"]{
background:oklch(0.55 0.17 var(--vibeui-icontile-006-hue));
border-color:oklch(0.47 0.17 var(--vibeui-icontile-006-hue));
color:oklch(0.99 0 0);
box-shadow:inset 0 2px 4px oklch(0.25 0.1 var(--vibeui-icontile-006-hue) / 45%);
transform:translateY(1px);
}
[data-vibeui-block="icontile-006"]:active{transform:translateY(1px)}
[data-vibeui-block="icontile-006"]:disabled{cursor:not-allowed;opacity:.5}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="icontile-006"]{animation:none!important;transition:none!important}}
`

/**
 * Плитка-кнопка с нажатым состоянием на aria-pressed.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Icontile006({
  glyph = "★",
  label = "В избранное",
  defaultPressed = false,
  type = "button",
  onClick,
  className,
  style,
  ...props
}: Icontile006Props) {
  const [pressed, setPressed] = useState(defaultPressed)

  return (
    <>
      <style href="vibeui-icontile-006" precedence="medium">
        {STYLES}
      </style>
      <button
        {...props}
        type={type}
        data-vibeui-block="icontile-006"
        aria-pressed={pressed}
        aria-label={label}
        onClick={(event) => {
          setPressed((value) => !value)
          onClick?.(event)
        }}
        className={className}
        style={style as CSSProperties}
      >
        <span aria-hidden="true">{glyph}</span>
      </button>
    </>
  )
}