Кастомизация темы Ant Design¶
Уровень: L3 (deep-dive) | Вверх: README.md | Раздел: ../README.md
Обзор¶
Ant Design 5 использует Design Token System — многоуровневую систему токенов для кастомизации внешнего вида. В проекте «Плечики» тема настроена через объект ThemeConfig, передаваемый в <ConfigProvider theme={antdTheme}> в App.tsx.
Кастомизация затрагивает: - Global Tokens — базовые цвета, радиусы, шрифты - Component Tokens — настройки конкретных компонентов (Button, Input, Card и др.)
Подключение¶
Файл: App.tsx
import { ConfigProvider } from 'antd';
import ruRU from 'antd/locale/ru_RU';
import { antdTheme } from './theme/antdTheme';
function App() {
return (
<ConfigProvider theme={antdTheme} locale={ruRU}>
{/* ... */}
</ConfigProvider>
);
}
Полный код antdTheme.ts¶
Файл: frontend/src/theme/antdTheme.ts (113 строк)
import type { ThemeConfig } from 'antd';
import { tokens } from './tokens';
import { semanticTokens } from './semanticTokens';
export const antdTheme: ThemeConfig = {
token: {
// ── Цвета ──
colorPrimary: semanticTokens.colorAccent, // #D18A62
colorBgBase: semanticTokens.colorBg, // #F7F4EF
colorTextBase: semanticTokens.colorText, // #1F1B16
colorBgContainer: semanticTokens.colorSurface, // #FFFDFB
colorBorder: semanticTokens.colorBorder, // rgba(31,27,22,0.08)
colorBorderSecondary: semanticTokens.colorBorder,
colorText: semanticTokens.colorText,
colorTextSecondary: semanticTokens.colorTextSecondary,
colorTextTertiary: semanticTokens.colorTextMuted,
colorTextQuaternary: semanticTokens.colorTextMuted,
colorLink: semanticTokens.colorAccent,
// ── Радиусы ──
borderRadius: parseInt(tokens.radius.sm), // 16
borderRadiusSM: parseInt(tokens.radius.sm), // 16
borderRadiusLG: parseInt(tokens.radius.md), // 18
// ── Шрифт ──
fontFamily: tokens.font.body, // 'Geist', sans-serif
// ── Тени ──
boxShadow: tokens.shadow.card,
boxShadowSecondary: tokens.shadow.floating,
// ── Размеры ──
controlHeight: parseInt(tokens.control.md), // 52
controlHeightLG: parseInt(tokens.control.input), // 54
controlHeightSM: parseInt(tokens.control.sm), // 38
},
components: {
// ── Layout ──
Layout: {
bodyBg: semanticTokens.colorBg,
},
// ── Button ──
// Pill-shaped кнопки без тени
Button: {
borderRadius: 999, // Полное скругление (pill)
borderRadiusLG: 999,
borderRadiusSM: 999,
primaryShadow: 'none', // Без тени у primary
dangerShadow: 'none',
defaultShadow: 'none',
fontWeight: 700,
paddingInlineLG: 28,
controlHeightLG: parseInt(tokens.control.md), // 52
},
// ── Input ──
// Мягкий фон, акцентная обводка при фокусе
Input: {
activeBg: tokens.color.surfaceAlt, // #F2ECE3
hoverBg: tokens.color.surfaceAlt,
activeBorderColor: tokens.color.accent, // #D18A62
hoverBorderColor: tokens.color.accentHover, // #C47D56
activeShadow: `0 0 0 2px ${tokens.color.accentSoft}`, // Мягкое свечение
controlHeightLG: parseInt(tokens.control.input), // 54
borderRadius: parseInt(tokens.radius.sm), // 16
borderRadiusLG: parseInt(tokens.radius.sm),
},
// ── Select ──
Select: {
controlHeightLG: parseInt(tokens.control.input),
borderRadius: parseInt(tokens.radius.sm),
borderRadiusLG: parseInt(tokens.radius.sm),
},
// ── Card ──
Card: {
borderRadiusLG: parseInt(tokens.radius.card), // 22
boxShadow: tokens.shadow.card,
paddingLG: parseInt(tokens.space[4]), // 16
},
// ── Drawer (BottomSheet) ──
Drawer: {
borderRadius: parseInt(tokens.radius.sheet), // 28
paddingLG: parseInt(tokens.space[6]), // 24
},
// ── Modal ──
Modal: {
borderRadiusLG: parseInt(tokens.radius.card),
},
// ── Tag ──
Tag: {
borderRadiusSM: parseInt(tokens.radius.pill),
},
// ── Typography ──
Typography: {
fontFamilyCode: tokens.font.data, // 'Geist Mono', monospace
},
},
};
Что изменено относительно дефолта Ant Design¶
| Аспект | Ant Design default | Плечики | Причина |
|---|---|---|---|
| Primary color | #1677ff (синий) |
#D18A62 (терракотовый) |
Фирменный цвет бренда |
| Background | #ffffff (белый) |
#F7F4EF (бежевый) |
Тёплая палитра |
| Border radius | 6px | 16px | Mobile-first: крупные скругления |
| Button radius | 6px | 999px (pill) | Дизайн-система: pill-кнопки |
| Button shadow | 0 2px 0 rgba(...) |
none |
Чистый flat-дизайн |
| Button font weight | 400 | 700 | Акцент на CTA |
| Control height | 32px | 52px | Мобильные тач-таргеты |
| Input height | 32px | 54px | Увеличенная зона нажатия |
| Input background | white | #F2ECE3 |
Мягкий контраст с фоном |
| Card radius | 8px | 22px | Современный mobile-дизайн |
| Drawer radius | 0 | 28px | Rounded bottom sheet |
| Font family | -apple-system, ... |
'Geist', sans-serif |
Кастомный шрифт |
Полный код semanticTokens.ts¶
Файл: frontend/src/theme/semanticTokens.ts (39 строк)
import { tokens } from './tokens';
export const semanticTokens = {
// Цвета приложения
colorBg: tokens.color.bg,
colorSurface: tokens.color.surface,
colorSurfaceAlt: tokens.color.surfaceAlt,
colorSurfaceOverlay: tokens.color.surfaceOverlay,
colorSurfaceInverse: tokens.color.surfaceInverse,
colorText: tokens.color.text,
colorTextSecondary: tokens.color.textSecondary,
colorTextMuted: tokens.color.textMuted,
colorTextInverse: tokens.color.textInverse,
colorAccent: tokens.color.accent,
colorAccentSoft: tokens.color.accentSoft,
colorAccentHover: tokens.color.accentHover,
colorBorder: tokens.color.border,
colorSuccess: tokens.color.success,
colorWarning: tokens.color.warning,
colorDanger: tokens.color.danger,
colorDangerSoft: tokens.color.dangerSoft,
// Chip dot colors
chipDot: '#A7B29A',
chipDotSoft: '#E4EADE',
// Aliases
radius: tokens.radius,
space: tokens.space,
control: tokens.control,
layout: tokens.layout,
shadow: tokens.shadow,
z: tokens.z,
};
Полный код typography.ts¶
Файл: frontend/src/theme/typography.ts (61 строка)
import type { CSSProperties } from 'react';
import { tokens } from './tokens';
export const typography: Record<string, CSSProperties> = {
display: {
fontFamily: tokens.font.display,
fontSize: tokens.fontSize.display, // 28px
fontWeight: tokens.fontWeight.bold, // 700
lineHeight: tokens.lineHeight.display, // 1.1
color: tokens.color.text,
},
pageTitle: {
fontFamily: tokens.font.display,
fontSize: tokens.fontSize.pageTitle, // 24px
fontWeight: tokens.fontWeight.bold,
lineHeight: tokens.lineHeight.title, // 1.15
color: tokens.color.text,
},
sectionTitle: {
fontFamily: tokens.font.display,
fontSize: tokens.fontSize.sectionTitle, // 20px
fontWeight: tokens.fontWeight.bold,
lineHeight: tokens.lineHeight.title,
color: tokens.color.text,
},
body: {
fontFamily: tokens.font.body,
fontSize: tokens.fontSize.body, // 15px
fontWeight: tokens.fontWeight.regular, // 400
lineHeight: tokens.lineHeight.body, // 1.4
color: tokens.color.text,
},
caption: {
fontFamily: tokens.font.body,
fontSize: tokens.fontSize.caption, // 13px
fontWeight: tokens.fontWeight.medium, // 500
lineHeight: tokens.lineHeight.caption, // 1.25
color: tokens.color.textMuted,
},
meta: {
fontFamily: tokens.font.body,
fontSize: tokens.fontSize.meta, // 11px
fontWeight: tokens.fontWeight.medium,
lineHeight: tokens.lineHeight.caption,
color: tokens.color.textMuted,
},
data: {
fontFamily: tokens.font.data,
fontSize: tokens.fontSize.caption,
fontWeight: tokens.fontWeight.medium,
lineHeight: tokens.lineHeight.caption,
color: tokens.color.text,
},
action: {
fontFamily: tokens.font.body,
fontSize: '14px',
fontWeight: tokens.fontWeight.semibold, // 600
lineHeight: tokens.lineHeight.caption,
color: tokens.color.accent,
},
};