Main/React & Next.js/Blueprints/Design Systems & UI/Модульная дизайн-система на Radix UI Primitives, Tailwind CSS v4 и TypeScript
Back to «Design Systems & UI» cards
Design Systems & UI(ТЗ 3 из 10)
ТЗ: Модульная дизайн-система на Radix UI Primitives, Tailwind CSS v4 и TypeScript
Design Systems & UI1 шагов • +5 XP

ТЗ: Модульная дизайн-система на Radix UI Primitives, Tailwind CSS v4 и TypeScript

Инженерная спецификация создания масштабируемой библиотеки UI-компонентов: паттерн Compound Components, слот-компонент asChild, стилизация по data-атрибутам в Tailwind v4 и стандарты доступности WCAG 2.2 AA.

~12 ч работыReact 19Radix UI PrimitivesTailwind CSS v4 @themeSlot Pattern (asChild)Compound ComponentsTypeScript GenericsWCAG 2.2 AA
+5 XP за шаг
Выполнено 0 из 1 шагов (0%)
Трудозатраты~12 рабочих часов
Фриланс вилка~$540 – $900 (€480 – €840)
Оценка веб-студии~$1,140 – $1,920 (€1080 – €1800)

ТЗ заказчика (исходный бриф):

Нам нужна предсказуемая библиотека компонентов, которую разработчики не будут переписывать под каждый проект. Полная доступность из коробки и гибкая стилизация без переопределения через !important.
Ключевые требования:
  • •Реализация примитивов Button, Dialog, DropdownMenu на базе Radix UI
  • •Поддержка пропа asChild для полиморфного слияния со сторонними компонентами (например, Next.js Link)
  • •Дизайн-токены цветов и радиусов через директиву @theme в CSS
  • •Управление состояниями через data-[state=open], data-[disabled]
Ограничения проекта:
  • •Запрет на использование устаревшего React.cloneElement()
  • •Никаких wrapper <div> при передаче ссылок в кнопки
Критерии приёмки (KPI):
  • 100% прохождение тестов доступности axe-core (0 нарушений)
  • Размер базового бандла компонента кнопки < 3 КБ

Пошаговое практическое руководство по ТЗ

Подробный разбор архитектуры, код реализации и советы эксперта по каждому этапу:

1
Этап 1 из 1

Полиморфный компонент Button с поддержкой Radix Slot (asChild)

~45 минСредний

Практическое руководство:

Разработать компонент Button с поддержкой вариантов (primary, secondary, destructive, ghost), размеров (sm, md, lg) и полиморфизма через @radix-ui/react-slot.

Важные технические нюансы и подводные камни:

Корректно типизируйте пропсы кнопки, чтобы сохранялись нативные атрибуты HTMLButtonElement или полиморфного компонента.

Архитектурная база (Почему выбрано такое решение):

Паттерн Slot объединяет пропсы, обработчики событий и ref переданного дочернего элемента, избавляя от лишних DOM-узлов.

Совет эксперта (Best Practice):

При использовании asChild с Next.js Link стили кнопки применяются напрямую к тегу <a>, исключая ошибки валидации HTML.

Ожидаемый артефакт этапа:

Типобезопасный полиморфный компонент Button с поддержкой asChild.

TYPESCRIPT
// components/ui/Button.tsx
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'

export const buttonVariants = cva(
  'inline-flex items-center justify-center gap-2 rounded-xl text-sm font-semibold transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-violet/50 disabled:pointer-events-none disabled:opacity-50 cursor-pointer active:scale-98',
  {
    variants: {
      variant: {
        primary: 'bg-accent-violet hover:bg-accent-violet/90 text-white shadow-md shadow-accent-violet/20',
        secondary: 'bg-bg-elevated hover:bg-bg-elevated/80 text-text-primary border border-border',
        destructive: 'bg-rose-500 hover:bg-rose-600 text-white shadow-sm',
        ghost: 'hover:bg-bg-elevated text-text-muted hover:text-text-primary'
      },
      size: {
        sm: 'h-8 px-3 text-xs',
        md: 'h-10 px-4 py-2',
        lg: 'h-12 px-6 text-base'
      }
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md'
    }
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button'
    return (
      <Comp
        className={buttonVariants({ variant, size, className })}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = 'Button'

Типичные грабли на этом ТЗ

1 антипаттерна

Что сдаём клиенту (Deliverables)

Чек-лист сдачи
Полиморфная кнопка со слотом asChild
Компоненты диалогов и дропдаунов с focus trap
Конфигурация токенов Tailwind CSS v4 @theme