- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Icon
Примитив иконки на основе Tabler Icons с размерами, заимствованными из Claude, и тонкой контурной обводкой.
Шкала размеров
Толщина обводки (по умолчанию в DS — 1.5)
Контурные и заливные
Декоративные (по умолчанию)
Значимые — самостоятельный статус (с названием, без aria-hidden)
import {
IconAlertTriangle,
IconBolt,
IconCircleCheck,
IconFolder,
IconHeart,
IconHeartFilled,
IconLoader2,
IconSearch,
IconSparkles,
IconWorld,
} from "@tabler/icons-react";
import { Icon, type IconSize } from "@/components/ui/icon";
const sizes: { size: IconSize; px: string }[] = [
{ px: "14", size: "xs" },
{ px: "16", size: "sm" },
{ px: "18", size: "md" },
{ px: "20", size: "lg" },
{ px: "32", size: "xl" },
];
const strokes = [1, 1.5, 2];
export default function Particle() {
return (
<div className="flex w-full flex-col gap-8">
<div className="flex flex-col gap-3">
<p className="text-muted-foreground text-xs">Шкала размеров</p>
<div className="flex flex-wrap items-end gap-6">
{sizes.map(({ size, px }) => (
<div className="flex flex-col items-center gap-2" key={size}>
<Icon
className="text-foreground"
icon={IconSparkles}
size={size}
/>
<span className="text-muted-foreground text-xs">
{size} · {px}px
</span>
</div>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-muted-foreground text-xs">
Толщина обводки (по умолчанию в DS — 1.5)
</p>
<div className="flex flex-wrap items-end gap-6">
{strokes.map((s) => (
<div className="flex flex-col items-center gap-2" key={s}>
<Icon
className="text-foreground"
icon={IconBolt}
size="xl"
stroke={s}
/>
<span className="text-muted-foreground text-xs">
{s}
{s === 1.5 ? " · по умолчанию" : ""}
</span>
</div>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-muted-foreground text-xs">Контурные и заливные</p>
<div className="flex flex-wrap items-end gap-6">
<div className="flex flex-col items-center gap-2">
<Icon className="text-foreground" icon={IconHeart} size="xl" />
<span className="text-muted-foreground text-xs">IconHeart</span>
</div>
<div className="flex flex-col items-center gap-2">
<Icon
className="text-foreground"
icon={IconHeartFilled}
size="xl"
/>
<span className="text-muted-foreground text-xs">
IconHeartFilled
</span>
</div>
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-muted-foreground text-xs">
Декоративные (по умолчанию)
</p>
<div className="flex items-center gap-4 text-muted-foreground">
<Icon icon={IconSearch} />
<Icon icon={IconFolder} />
<Icon icon={IconWorld} />
<Icon className="text-foreground" icon={IconSparkles} />
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-muted-foreground text-xs">
Значимые — самостоятельный статус (с названием, без aria-hidden)
</p>
<div className="flex items-center gap-4">
<Icon
aria-hidden={false}
aria-label="Загрузка"
className="animate-spin text-muted-foreground"
icon={IconLoader2}
role="img"
/>
<Icon
aria-hidden={false}
aria-label="Готово"
className="text-success"
icon={IconCircleCheck}
role="img"
/>
<Icon
aria-hidden={false}
aria-label="Предупреждение"
className="text-warning"
icon={IconAlertTriangle}
role="img"
/>
</div>
</div>
</div>
);
}
Все иконки
Полный набор Tabler Icons, доступный в DS (контур + залитые). Введите запрос для поиска, нажмите на иконку — скопируется готовый импорт.
"use client";
import * as TablerIcons from "@tabler/icons-react";
import { type ElementType, useMemo, useState } from "react";
import { Input } from "@/components/ui/input";
// Every Tabler icon export (outline + filled), built once. Tabler icons are
// forwardRef components (objects with `$$typeof`); plain/type exports are filtered out.
const ALL_ICONS: [string, ElementType][] = Object.entries(
TablerIcons as Record<string, unknown>,
)
.filter(
([name, value]) =>
/^Icon[A-Z]/.test(name) &&
(typeof value === "function" ||
(typeof value === "object" && value !== null && "$$typeof" in value)),
)
.sort((a, b) => a[0].localeCompare(b[0])) as [string, ElementType][];
export default function Particle() {
const [query, setQuery] = useState("");
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return ALL_ICONS;
return ALL_ICONS.filter(([name]) => name.toLowerCase().includes(q));
}, [query]);
return (
<div className="flex w-full max-w-3xl flex-col gap-4">
<div className="flex items-center justify-between gap-3">
<Input
aria-label="Поиск иконок"
className="max-w-xs"
onChange={(event) => setQuery(event.target.value)}
placeholder="Поиск иконок…"
type="search"
value={query}
/>
<span className="shrink-0 text-muted-foreground text-sm tabular-nums">
{filtered.length} из {ALL_ICONS.length}
</span>
</div>
<div className="grid grid-cols-[repeat(auto-fill,minmax(76px,1fr))] gap-2">
{filtered.map(([name, IconComponent]) => (
<button
className="flex flex-col items-center gap-1.5 rounded-lg border-[0.5px] p-2 text-center transition-colors [contain-intrinsic-size:auto_76px] [content-visibility:auto] hover:bg-accent"
key={name}
onClick={() =>
navigator.clipboard?.writeText(
`import { ${name} } from "@tabler/icons-react";`,
)
}
title={`${name} — нажмите, чтобы скопировать импорт`}
type="button"
>
<IconComponent className="size-6 text-foreground" stroke={1.5} />
<span className="w-full truncate text-muted-foreground text-xs">
{name.replace(/^Icon/, "")}
</span>
</button>
))}
</div>
</div>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/icon
Эта команда устанавливает примитив Icon и добавляет @tabler/icons-react в зависимости.
Использование
import { Icon } from "@/components/ui/icon";
import { IconSearch } from "@tabler/icons-react";
<Icon icon={IconSearch} />;Передайте любую иконку Tabler в свойство icon. Цвет
наследуется от currentColor, поэтому задавайте его текстовым токеном на самой
иконке или на родительском элементе — никогда не используйте жёстко заданный цвет:
<span className="text-muted-foreground">
<Icon icon={IconSearch} size="sm" />
</span>Размеры
Шкала размеров снята 1:1 с claude.ai (20px — преобладающий размер строчной иконки).
По умолчанию используется lg (20px).
| Токен | Размер | Класс |
|---|---|---|
xs | 14px | size-3.5 |
sm | 16px | size-4 |
md | 18px | size-4.5 |
lg | 20px | size-5 (по умолчанию) |
xl | 32px | size-8 |
<Icon icon={IconSearch} size="sm" />
<Icon icon={IconSearch} size="xl" />Обводка и типы
Самый светлый контур claude.ai визуально близок к обводке в 1px, но буквальное
значение 1 отрисовывается слишком бледно на малых размерах, а собственное
значение Tabler по умолчанию (2) слишком жирное. Поэтому DS по умолчанию
использует 1.5 и предоставляет свойство stroke для настройки толщины. Для
совместимости с Tabler stroke принимает string | number.
<Icon icon={IconBolt} stroke={1} />
<Icon icon={IconBolt} stroke={1.5} /> {/* default */}
<Icon icon={IconBolt} stroke="2" />Залитые варианты — это обычные иконки Tabler; импортируйте компонент *Filled:
import { IconHeart, IconHeartFilled } from "@tabler/icons-react";
<Icon icon={IconHeart} /> {/* outline */}
<Icon icon={IconHeartFilled} /> {/* filled */}Декоративные и смысловые иконки
Icon по умолчанию декоративен: он задаёт aria-hidden="true", поэтому
вспомогательные технологии пропускают его, а смысл несёт окружающий текст или
метка родительского элемента управления. Это правильно для распространённого
случая — иконка рядом с текстовой меткой или иконка внутри кнопки, у которой уже
есть aria-label.
{/* Decorative: the visible text is the accessible name. */}
<button>
<Icon icon={IconSearch} size="sm" />
Search
</button>Когда иконка является единственным доступным содержимым — например,
самостоятельный индикатор статуса или единственный дочерний элемент кнопки только
с иконкой — ей нужно задать имя. Либо пометьте родительский элемент управления,
либо сделайте саму иконку смысловой, переопределив значение aria-hidden по
умолчанию и добавив role="img" вместе с aria-label:
{/* Option A — label the parent, keep the icon decorative. */}
<button aria-label="Search">
<Icon icon={IconSearch} />
</button>
{/* Option B — make the icon itself the accessible content. */}
<Icon
icon={IconCircleCheck}
aria-hidden={false}
role="img"
aria-label="Verified"
className="text-success"
/>Избегайте третьего состояния, когда иконка является единственным содержимым и при этом ничего не помечено — это создаёт элемент управления без метки и без озвучивания.
Состояния загрузки и анимации
Иконки могут выражать переходные состояния с помощью служебных анимаций.
Каноническое состояние загрузки — это спиннер: IconLoader2 с animate-spin.
Вращающийся загрузчик несёт смысл (он сообщает «загрузка»), поэтому задайте ему
имя, а не скрывайте его:
import { IconLoader2 } from "@tabler/icons-react";
<Icon
icon={IconLoader2}
className="animate-spin text-muted-foreground"
aria-hidden={false}
role="img"
aria-label="Loading"
/>Остальные иконки статуса следуют той же схеме — например, animate-pulse на
индикаторе ожидания или сочетание токена text-success / text-warning /
text-destructive с IconCircleCheck / IconAlertTriangle / IconX.