← К материалу
Оригинал ↗
SOURCE DOCUMENT
Raw HTML
Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах · Habr CSS
<!DOCTYPE html>
<html lang="ru">
<head>
<title>Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах / Хабр</title>
<meta property="fb:app_id" content="444736788986613">
<meta property="fb:pages" content="472597926099084">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@habr_com">
<meta property="og:site_name" content="Хабр">
<link href="https://habr.com/ru/rss/post/1080784/?fl=ru" type="application/rss+xml" title rel="alternate" name="rss">
<link href="https://habr.com/ru/articles/1080784/" rel="canonical" data-hid="e3fa780">
<link rel="image_src" href="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/" data-hid="2a79c45">
<link rel="amphtml" href="https://habr.com/ru/amp/publications/1080784/">
<meta property="og:title" content="Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах">
<meta name="twitter:title" content="Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах">
<meta name="aiturec:title" content="Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах">
<meta name="description" content="Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый...">
<meta itemprop="description" content="Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый...">
<meta property="og:description" content="Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый...">
<meta name="twitter:description" content="Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый...">
<meta property="aiturec:description" content="Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый...">
<meta itemprop="image" content="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/">
<meta property="og:image" content="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="aiturec:image" content="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/">
<meta name="twitter:image" content="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/">
<meta property="vk:image" content="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/?format=vk">
<meta property="vk:image" content="https://habr.com/share/publication/1080784/96dc14ecdb4b0d3a351555b37e7e7092/?format=vk?format=vk">
<meta property="aiturec:item_id" content="1080784">
<meta property="aiturec:datetime" content="2026-09-10T10:21:24.000Z">
<meta content="https://habr.com/ru/articles/1080784/" property="og:url">
<meta property="og:type" content="article">
<meta property="og:locale" content="ru_RU">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="keywords" content="javascript, typescript, uikit, ui, ui kit, react, react.js, программирование, фронтенд, фронтенд-разработка">
<script type="application/ld+json" data-hid="1e0f0a2">{"@context":"http:\/\/schema.org","@type":"Article","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/habr.com\/ru\/articles\/1080784\/"},"headline":"Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах","datePublished":"2026-09-10T13:21:24+03:00","dateModified":"2026-09-10T13:21:24+03:00","author":{"@type":"Person","name":null},"publisher":{"@type":"Organization","name":"Habr","logo":{"@type":"ImageObject","url":"https:\/\/habrastorage.org\/webt\/a_\/lk\/9m\/a_lk9mjkccjox-zccjrpfolmkmq.png"}},"description":"Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления...","url":"https:\/\/habr.com\/ru\/articles\/1080784\/#post-content-body","about":["h_javascript","h_reactjs","h_typescript","h_css","f_develop"],"image":["https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/830\/98a\/107\/83098a10775cc2dc835a65d831efd1dc.png","https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/433\/1ca\/c2c\/4331cac2cfbec45de9ab8ab02aecdaa6.PNG","https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/78b\/379\/1bf\/78b3791bf1a89fa0b2956267a51c8a21.png","https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/05a\/23b\/734\/05a23b73461ca67f8f17e3d5d7a9fa56.png","https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/464\/d1e\/13b\/464d1e13bc995cf1a3eadca2e9fd52c1.PNG","https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1c2\/82e\/4e5\/1c282e4e5b43b8e074bd78318c4e71a1.png","https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b3a\/3f2\/6ca\/b3a3f26ca1ef69b5876e5daaa046a185.png"]}</script>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1.0,viewport-fit=cover,minimum-scale=1">
<meta name="referrer" content="unsafe-url">
<link rel="shortcut icon" type="image/png" sizes="16x16" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/favicon-16.png">
<link rel="shortcut icon" type="image/png" sizes="32x32" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/favicon-32.png">
<link rel="apple-touch-icon" type="image/png" sizes="76x76" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/apple-touch-icon-76.png">
<link rel="apple-touch-icon" type="image/png" sizes="120x120" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/apple-touch-icon-120.png">
<link rel="apple-touch-icon" type="image/png" sizes="152x152" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/apple-touch-icon-152.png">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/apple-touch-icon-180.png">
<link rel="apple-touch-icon" type="image/png" sizes="256x256" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/apple-touch-icon-256.png">
<link rel="mask-icon" color="#77a2b6" href="https://assets.habr.com/habr-web/release_2.347.0/client/img/favicons/apple-touch-icon-120.svg">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1136x640.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2436x1125.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1792x828.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_828x1792.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1334x750.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1242x2668.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2208x1242.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1125x2436.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1242x2208.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2732x2048.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2688x1242.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2224x1668.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_750x1334.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2048x2732.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2388x1668.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1668x2224.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_640x1136.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1668x2388.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_2048x1536.png">
<link rel="apple-touch-startup-image"
media="screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="https://assets.habr.com/habr-web/release_2.347.0/client/img/splashes/splash_1536x2048.png">
<style>
.grecaptcha-badge {
visibility: hidden;
}
</style>
<meta name="habr-version" content="2.347.0">
<link rel="preload" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/theme/light-v2.css" as="style" media="(prefers-color-scheme: light)" /><link rel="preload" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/theme/dark-v2.css" as="style" media="(prefers-color-scheme: dark)" /><link id="light-colors" rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/theme/light-v2.css" media="(prefers-color-scheme: light)" /><link id="dark-colors" rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/theme/dark-v2.css" media="(prefers-color-scheme: dark)" />
<script> window.__LOCALE__='ru';</script>
<meta name="apple-mobile-web-app-status-bar-style" content="#303b44">
<meta name="msapplication-TileColor" content="#629FBC">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link crossorigin="use-credentials" href="https://habr.com/manifest.webmanifest" rel="manifest">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900" rel="stylesheet">
<script async src="https://unpkg.com/pwacompat" crossorigin="anonymous"></script>
<script>window.yaContextCb = window.yaContextCb || []</script>
<script>window.__PINIA_STATE__={"features":{"features":{"CODE_EXPLAINER":true,"P_SHAPED_BRANDING":true}},"i18n":{"fl":"ru","hl":"ru"},"location":{"urlStruct":{"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname":null,"hash":null,"query":{},"pathname":"\u002Fru\u002Farticles\u002F1080784\u002F","path":"\u002Fru\u002Farticles\u002F1080784\u002F","href":"\u002Fru\u002Farticles\u002F1080784\u002F"}},"global":{"isPwa":false,"device":"desktop","requestId":"8c4ddb3f6fa9029e5e6e6a379c1d601f","countryCode":"DE"},"articlesList":{"articlesList":{"1080784":{"id":"1080784","timePublished":"2026-09-10T10:21:24+00:00","isCorporative":false,"lang":"ru","titleHtml":"Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах","leadData":{"textHtml":"\u003Cp\u003E\u003Cstrong\u003EМы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003EС чего всё начиналось\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003EКогда в проекте начинает появляться всё больше одинаковых элементов интерфейса, рано или поздно возникает вопрос: зачем каждый раз создавать один и тот же Button, Input или Select?\u003C\u002Fp\u003E\u003Cp\u003EМы рассматривали готовые решения, в том числе Ant Design и Material UI, но они не закрывали наши требования. Нам требовался больший контроль над внешним видом и поведением компонентов, возможность детально адаптировать их под наши задачи и учитывать требования проекта к безопасности.\u003C\u002Fp\u003E","imageUrl":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F41e\u002F089\u002Fd06\u002F41e089d06a122ef383d3471243988948.png","buttonTextHtml":"Читать далее","image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F41e\u002F089\u002Fd06\u002F41e089d06a122ef383d3471243988948.png","fit":"cover","positionY":0,"positionX":0}},"editorVersion":"2.0","postType":"article","postLabels":[{"type":"sandbox","typeOf":"system","title":"Из песочницы","data":{"url":null}}],"author":{"id":"3050247","alias":"Goker1235","fullname":null,"avatarUrl":null,"speciality":null,"deleted":false,"isSeo":false,"scoreStats":{"score":1,"votesCount":1},"rating":0,"relatedData":null,"contacts":[],"authorContacts":[],"paymentDetails":{"paymentYandexMoney":null,"paymentPayPalMe":null,"paymentWebmoney":null},"donationsMethod":null,"isInBlacklist":null,"careerProfile":null,"isShowScores":true,"reach":"2K+"},"statistics":{"commentsCount":0,"favoritesCount":0,"readingCount":241,"score":0,"votesCount":0,"votesCountPlus":0,"votesCountMinus":0,"reach":3651,"readers":193},"hubs":[{"id":"357","alias":"javascript","type":"collective","title":"JavaScript","titleHtml":"JavaScript","isProfiled":true,"relatedData":null},{"id":"19327","alias":"reactjs","type":"collective","title":"ReactJS","titleHtml":"ReactJS","isProfiled":true,"relatedData":null},{"id":"21370","alias":"typescript","type":"collective","title":"TypeScript","titleHtml":"TypeScript","isProfiled":true,"relatedData":null},{"id":"307","alias":"css","type":"collective","title":"CSS","titleHtml":"CSS","isProfiled":true,"relatedData":null}],"flows":[{"id":"1","alias":"develop","title":"Разработка","titleHtml":"Разработка"}],"relatedData":{"vote":null,"unreadCommentsCount":0,"bookmarked":false,"canComment":false,"canEdit":false,"canViewVotes":false,"votePlus":{"canVote":false,"isChargeEnough":false,"isKarmaEnough":false,"isVotingOver":false,"isPublicationLimitEnough":false},"voteMinus":{"canVote":false,"isChargeEnough":false,"isKarmaEnough":false,"isVotingOver":false,"isPublicationLimitEnough":false},"draftReason":null,"lockReason":null,"canModerateComments":false,"trackerSubscribed":false,"emailSubscribed":false},"textHtml":"\u003Cdiv xmlns=\"http:\u002F\u002Fwww.w3.org\u002F1999\u002Fxhtml\"\u003E\u003Cp\u003E\u003Cem\u003EПрактический разбор создания и развития собственной UI-библиотеки\u003C\u002Fem\u003E\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cstrong\u003EКоротко: \u003C\u002Fstrong\u003E\u003Cem\u003EСобственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый план выходят API, состояние, совместимость, зависимости, тестирование, документация и версионирование.\u003C\u002Fem\u003E\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Ch3\u003EС чего всё начиналось\u003C\u002Fh3\u003E\u003Cp\u003EКогда в проекте начинает появляться всё больше одинаковых элементов интерфейса, рано или поздно возникает вопрос: зачем каждый раз создавать один и тот же Button, Input или Select?\u003C\u002Fp\u003E\u003Cp\u003EМы рассматривали готовые решения, в том числе Ant Design и Material UI, но они не закрывали наши требования. Нам требовался больший контроль над внешним видом и поведением компонентов, возможность детально адаптировать их под наши задачи и учитывать требования проекта к безопасности.\u003C\u002Fp\u003E\u003Cp\u003EПоэтому мы решили сделать собственную UI-библиотеку. Первая версия заняла примерно месяц — вместе с макетами, разработкой, настройкой сборки и тестами.\u003C\u002Fp\u003E\u003Ch3\u003EСтек и первая версия\u003C\u002Fh3\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003EReact\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003ETypeScript\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003ECSS Modules\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EWebpack\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EUnit-тесты\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003EНа старте было шесть компонентов:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003EInput\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003ESelect\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EDrawer\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EBadge\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003ETable\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003ELayout — базовая обёртка для построения интерфейса\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003EДля каждого компонента были предусмотрены тесты. Сейчас библиотека выросла до 30+ компонентов. Среди них есть сложные составные элементы — например, фильтр и календарь с выбором дат и времени в разных форматах.\u003C\u002Fp\u003E\u003Cfigure class=\"full-width \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F830\u002F98a\u002F107\u002F83098a10775cc2dc835a65d831efd1dc.png\" width=\"1781\" height=\"737\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F830\u002F98a\u002F107\u002F83098a10775cc2dc835a65d831efd1dc.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F830\u002F98a\u002F107\u002F83098a10775cc2dc835a65d831efd1dc.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Ch3\u003EОт простых компонентов к сложным\u003C\u002Fh3\u003E\u003Cdiv class=\"floating-image\"\u003E\u003Cfigure class=\"float \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F433\u002F1ca\u002Fc2c\u002F4331cac2cfbec45de9ab8ab02aecdaa6.PNG\" width=\"323\" height=\"241\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F433\u002F1ca\u002Fc2c\u002F4331cac2cfbec45de9ab8ab02aecdaa6.PNG 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F433\u002F1ca\u002Fc2c\u002F4331cac2cfbec45de9ab8ab02aecdaa6.PNG 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Cp\u003EПервые компоненты решали достаточно конкретные задачи. Но по мере развития продукта стали появляться элементы, которые объединяют несколько контролов, popup-элементов и собственную логику. В этот момент становится понятно, что UI Kit — уже не просто коллекция компонентов. Это отдельный программный продукт со своим API, тестами, документацией, версиями и жизненным циклом.\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Ch3\u003EОшибка №1. Слишком сложный компонент\u003C\u002Fh3\u003E\u003Cp\u003EОдна из главных ошибок — попытка сделать компонент максимально универсальным. На бумаге кажется логичным: если компонент умеет больше, его можно использовать в большем количестве мест.\u003C\u002Fp\u003E\u003Cp\u003EНа практике часто происходит наоборот. Чем больше логики появляется внутри компонента, тем сложнее его изменить, протестировать и адаптировать под конкретный сценарий.\u003C\u002Fp\u003E\u003Cp\u003EОсобенно это заметно на составных компонентах. Фильтр может включать несколько контролов, собственное состояние, кнопки применения и сброса, popup-элементы и дополнительную логику взаимодействия.\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cstrong\u003EПрактический вывод: \u003C\u002Fstrong\u003E\u003Cem\u003EНе стоит пытаться сделать один компонент решением абсолютно всех задач. Иногда лучше дать разработчику набор небольших компонентов и возможность собрать нужный сценарий самостоятельно.\u003C\u002Fem\u003E\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Ch3\u003EОшибка №2. Состояния разных экземпляров должны быть независимыми\u003C\u002Fh3\u003E\u003Cp\u003EОдна из проблем переиспользуемых компонентов — неправильная организация состояния. Представим, что на разных страницах приложения есть Select с фильтрами. Открытие Select на странице A не должно влиять на Select на странице B.\u003C\u002Fp\u003E\u003Cfigure class=\"full-width \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F78b\u002F379\u002F1bf\u002F78b3791bf1a89fa0b2956267a51c8a21.png\" width=\"1781\" height=\"666\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F78b\u002F379\u002F1bf\u002F78b3791bf1a89fa0b2956267a51c8a21.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F78b\u002F379\u002F1bf\u002F78b3791bf1a89fa0b2956267a51c8a21.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Cp\u003EЕсли разные экземпляры получают общий источник состояния без явной необходимости, пользователь может увидеть ситуацию, когда открытие одного селектора влияет на другой. Выглядит это как случайный баг, но причина архитектурная.\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003Eчто хранится внутри конкретного экземпляра;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eчто может быть контролируемым извне;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eчто действительно должно быть общим;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие состояния не должны пересекаться между экземплярами.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ch3\u003EОшибка №3. Несколько popup внутри одного компонента\u003C\u002Fh3\u003E\u003Cp\u003EЕщё сложнее становится, когда внутри одного сложного компонента находится несколько popup-элементов.\u003C\u002Fp\u003E\u003Cfigure class=\"full-width \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F05a\u002F23b\u002F734\u002F05a23b73461ca67f8f17e3d5d7a9fa56.png\" width=\"1781\" height=\"792\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F05a\u002F23b\u002F734\u002F05a23b73461ca67f8f17e3d5d7a9fa56.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F05a\u002F23b\u002F734\u002F05a23b73461ca67f8f17e3d5d7a9fa56.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Cp\u003EКаждому popup необходимо правильно определить положение относительно элемента, который его открыл. Если механизм позиционирования недостаточно изолирован, один popup может начать влиять на другой.\u003C\u002Fp\u003E\u003Cp\u003EПричём отдельно Select, DatePicker и Dropdown могут работать нормально, а проблема появляется только при их совместном использовании внутри сложного компонента. Поэтому такие сценарии важно тестировать не только по отдельности, но и в комбинациях, близких к реальному использованию.\u003C\u002Fp\u003E\u003Ch3\u003EОшибка №4. «Компонент работает» — ещё не значит, что он готов\u003C\u002Fh3\u003E\u003Cdiv class=\"floating-image\"\u003E\u003Cfigure class=\"float \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F464\u002Fd1e\u002F13b\u002F464d1e13bc995cf1a3eadca2e9fd52c1.PNG\" width=\"347\" height=\"379\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F464\u002Fd1e\u002F13b\u002F464d1e13bc995cf1a3eadca2e9fd52c1.PNG 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F464\u002Fd1e\u002F13b\u002F464d1e13bc995cf1a3eadca2e9fd52c1.PNG 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Cp\u003EНа каждый компонент нашего UI Kit есть тесты. Но наличие теста само по себе не гарантирует отсутствие проблем. Компонент может пройти unit-тесты и при этом визуально сломаться после изменения CSS или вести себя иначе в составе сложного компонента. Поэтому важно разделять две вещи: «код работает» и «компонент корректно работает во всех местах, где его используют».\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Ch3\u003EСамый показательный случай — Drawer\u003C\u002Fh3\u003E\u003Cp\u003EСамый неприятный случай произошёл с Drawer. Мы внесли изменения в компонент, выпустили новую версию UI Kit, после чего обновили её в проектах. После обновления разметка Drawer уехала.\u003C\u002Fp\u003E\u003Cfigure class=\"full-width \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F1c2\u002F82e\u002F4e5\u002F1c282e4e5b43b8e074bd78318c4e71a1.png\" width=\"1781\" height=\"809\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F1c2\u002F82e\u002F4e5\u002F1c282e4e5b43b8e074bd78318c4e71a1.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F1c2\u002F82e\u002F4e5\u002F1c282e4e5b43b8e074bd78318c4e71a1.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cstrong\u003EПочему это больно: \u003C\u002Fstrong\u003E\u003Cem\u003EОшибка в локальном компоненте затрагивает одну страницу.\u003C\u002Fem\u003E\u003Cbr\u002F\u003E\u003Cem\u003EОшибка в общей библиотеке может затронуть сразу несколько проектов после обновления версии.\u003C\u002Fem\u003E\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003EПолучилась цепочка: изменение Drawer → новая версия UI Kit → обновление версии в проектах → изменение поведения Drawer → проблема во всех проектах.\u003C\u002Fp\u003E\u003Cp\u003EИменно здесь особенно хорошо видно отличие UI Kit от обычного компонента внутри приложения. Внутри приложения ошибку можно быстро исправить и задеплоить. В библиотеке ошибка становится частью версии, которую могут использовать несколько независимых приложений.\u003C\u002Fp\u003E\u003Cp\u003EПоэтому любое изменение общего компонента необходимо рассматривать не только с точки зрения самого компонента. Нужно задать вопрос: «Что произойдёт со всеми потребителями этой библиотеки?»\u003C\u002Fp\u003E\u003Ch3\u003EОшибка №5. Недооценили влияние зависимостей\u003C\u002Fh3\u003E\u003Cp\u003EUI Kit не существует в вакууме. У него есть React, TypeScript и другие зависимости. Если несколько приложений используют одну библиотеку, обновление её зависимостей может стать отдельной задачей.\u003C\u002Fp\u003E\u003Cp\u003EОсобенно это заметно в микросервисной архитектуре, где разные приложения могут иметь собственные циклы релизов. Если общий пакет начинает жёстко диктовать версии зависимостей, обновление может превратиться в цепочку изменений сразу в нескольких проектах.\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cstrong\u003EПрактический вывод:\u003C\u002Fstrong\u003E\u003Cem\u003EОбщие пакеты стоит проектировать с учётом границ ответственности за зависимости: какие версии поддерживаются, что является peer dependency, как проверяется совместимость и как выпускаются breaking changes.\u003C\u002Fem\u003E\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Ch3\u003EОшибка №6. Недооценили документацию\u003C\u002Fh3\u003E\u003Cp\u003EКогда компонент пишет его автор, большая часть API кажется очевидной. Но через несколько месяцев библиотекой пользуется другой разработчик.\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E<Button\n variant=\"primary\"\n size=\"large\"\n loading\n >\n Сохранить\n <\u002FButton>\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cp\u003EБез документации разработчику приходится открывать исходный код, чтобы понять варианты, обязательные props, состояния и ограничения.\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003Eчто делает компонент;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие props он принимает;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие значения поддерживаются;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие значения используются по умолчанию;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие есть ограничения;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие есть реальные сценарии использования. \u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cstrong\u003EГлавный принцип: \u003C\u002Fstrong\u003EДокументация — не приложение к UI Kit. Это часть самого продукта.\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Ch3\u003EОшибка №7. Не нужно изобретать велосипед\u003C\u002Fh3\u003E\u003Cp\u003EСоздавая собственную библиотеку, легко попасть в ловушку: «раз это наша библиотека, всё должно быть написано нами с нуля». Но даже если Ant Design или Material UI не подходят целиком, их решения стоит изучать.\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003Eкак похожая задача решена в других библиотеках;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакой API они используют;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкак устроено управление состоянием;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкак решено позиционирование popup;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие edge cases они учитывают;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eкакие ограничения есть у их подхода.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003EНе обязательно копировать реализацию. Гораздо разумнее взять проверенную концепцию и адаптировать её под свои требования.\u003C\u002Fp\u003E\u003Ch3\u003EЧто бы мы выбрали сегодня: Ant Design, MUI или собственный UI Kit?\u003C\u002Fh3\u003E\u003Cp\u003EОднозначного ответа нет. Выбор зависит не от того, какая библиотека «лучше», а от требований конкретного продукта.\u003C\u002Fp\u003E\u003Cdiv\u003E\u003Cdiv class=\"table\"\u003E\u003Ctable\u003E\u003Ctbody\u003E\u003Ctr\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"center\"\u003E\u003Cstrong\u003EКритерий\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"center\"\u003E\u003Cstrong\u003EГотовая библиотека\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"center\"\u003E\u003Cstrong\u003EСобственный UI Kit\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"center\"\u003E\u003Cstrong\u003EЧто важно\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003Ctr\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EСкорость старта\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EВысокая\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EНиже\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EДля MVP и внутренних систем готовое решение часто выгоднее\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003Ctr\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EКонтроль UI\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EЗависит от API\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EМаксимальный\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EСвой дизайн и поведение легче контролировать\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003Ctr\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EПоддержка\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EЕсть экосистема\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EНа команде\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EСвой UI Kit требует постоянного сопровождения\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003Ctr\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EНесколько проектов\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EЗависит от настройки\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EУдобно\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EОсобенно если есть общая дизайн-система\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003Ctr\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EЦена ошибки\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EОбычно ниже\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003EМожет быть высокой\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003Ctd data-colwidth=\"185\" width=\"185\"\u003E\u003Cp align=\"left\"\u003EОбщая библиотека влияет на всех потребителей\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003C\u002Ftbody\u003E\u003C\u002Ftable\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003Cp\u003EЕсли задача — быстро собрать внутреннюю админку, CRM или MVP, я бы в первую очередь посмотрел на готовые решения. Они дают большой набор компонентов и экономят время на базовой инфраструктуре.\u003C\u002Fp\u003E\u003Cp\u003EСобственная библиотека начинает иметь смысл, когда требования проекта заметно выходят за рамки готового решения: нужен полностью свой дизайн, специфическое поведение, единая система компонентов для нескольких проектов или полный контроль над API.\u003C\u002Fp\u003E\u003Cdiv\u003E\u003Cdiv class=\"table\"\u003E\u003Ctable\u003E\u003Ctbody\u003E\u003Ctr\u003E\u003Ctd\u003E\u003Cp align=\"left\"\u003E\u003Cstrong\u003EНаш выбор: \u003C\u002Fstrong\u003E\u003Cem\u003EЕсли требования проекта останутся такими же, мы, скорее всего, снова выберем собственный UI Kit. Но сегодня подошли бы к его созданию осторожнее: заранее продумали бы границы компонентов, состояние, API, зависимости, совместимость, документацию, тестирование и стратегию версионирования.\u003C\u002Fem\u003E\u003C\u002Fp\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E\u003C\u002Ftbody\u003E\u003C\u002Ftable\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003Ch3\u003EКак изменился UI Kit\u003C\u002Fh3\u003E\u003Cfigure class=\"full-width \"\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fb3a\u002F3f2\u002F6ca\u002Fb3a3f26ca1ef69b5876e5daaa046a185.png\" width=\"1781\" height=\"737\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fb3a\u002F3f2\u002F6ca\u002Fb3a3f26ca1ef69b5876e5daaa046a185.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fb3a\u002F3f2\u002F6ca\u002Fb3a3f26ca1ef69b5876e5daaa046a185.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Ffigure\u003E\u003Cp\u003EГлавное изменение произошло даже не в количестве компонентов. Изменилось наше отношение к библиотеке.\u003C\u002Fp\u003E\u003Cp\u003EВ начале мы воспринимали UI Kit как набор переиспользуемых компонентов. Теперь это отдельный продукт, у которого есть API, версии, зависимости, тесты, документация, проекты-потребители, обратная совместимость и собственный жизненный цикл.\u003C\u002Fp\u003E\u003Ch3\u003EЧто бы я сделал иначе сегодня\u003C\u002Fh3\u003E\u003Col\u003E\u003Cli\u003E\u003Cp\u003EСначала изучил бы существующие решения — не нужно изобретать всё самостоятельно.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EНе делал бы компоненты слишком универсальными — компонент должен решать задачу, но не предусматривать все возможные сценарии.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EС самого начала продумал бы состояние — особенно для интерактивных компонентов.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EСчитал бы документацию частью разработки — компонент без понятного API и примеров сложно считать законченным.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EТестировал бы не только компоненты, но и их комбинации — особенно сложные составные компоненты.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EОсторожнее относился бы к breaking changes — если библиотеку используют несколько проектов, изменение одного компонента может затронуть всю систему.\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Ch3\u003EВместо заключения\u003C\u002Fh3\u003E\u003Cp\u003EСоздать первый UI Kit оказалось не так сложно. Первую версию мы сделали примерно за месяц.\u003C\u002Fp\u003E\u003Cp\u003EСамое интересное началось после этого. Шесть компонентов превратились в тридцать с лишним. Появились сложные составные компоненты, собственные сценарии взаимодействия, зависимости, версии и несколько проектов, использующих одну библиотеку.\u003C\u002Fp\u003E\u003Cp\u003EИ вместе с ростом библиотеки изменились сами проблемы.\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cstrong\u003EГлавный урок: \u003C\u002Fstrong\u003EВ начале главный вопрос был: «Как сделать этот компонент?»\u003Cbr\u002F\u003EПозже вопрос стал другим: «Как изменить этот компонент так, чтобы не сломать всё, что уже использует нашу библиотеку?»\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003EUI-библиотека — это не набор красивых компонентов. Это отдельный продукт, который нужно проектировать с расчётом на развитие, совместимость и будущих пользователей.\u003C\u002Fp\u003E\u003Cp\u003EИ чем больше проектов её используют, тем дороже становится каждая архитектурная ошибка.\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E","tags":[{"titleHtml":"javascript"},{"titleHtml":"typescript"},{"titleHtml":"uikit"},{"titleHtml":"ui"},{"titleHtml":"ui kit"},{"titleHtml":"react"},{"titleHtml":"react.js"},{"titleHtml":"программирование"},{"titleHtml":"фронтенд"},{"titleHtml":"фронтенд-разработка"}],"metadata":{"stylesUrls":[],"scriptUrls":[],"shareImageUrl":"https:\u002F\u002Fhabr.com\u002Fshare\u002Fpublication\u002F1080784\u002F96dc14ecdb4b0d3a351555b37e7e7092\u002F","shareImageWidth":1200,"shareImageHeight":630,"vkShareImageUrl":"https:\u002F\u002Fhabr.com\u002Fshare\u002Fpublication\u002F1080784\u002F96dc14ecdb4b0d3a351555b37e7e7092\u002F?format=vk","schemaJsonLd":"{\"@context\":\"http:\\\u002F\\\u002Fschema.org\",\"@type\":\"Article\",\"mainEntityOfPage\":{\"@type\":\"WebPage\",\"@id\":\"https:\\\u002F\\\u002Fhabr.com\\\u002Fru\\\u002Farticles\\\u002F1080784\\\u002F\"},\"headline\":\"Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах\",\"datePublished\":\"2026-09-10T13:21:24+03:00\",\"dateModified\":\"2026-09-10T13:21:24+03:00\",\"author\":{\"@type\":\"Person\",\"name\":null},\"publisher\":{\"@type\":\"Organization\",\"name\":\"Habr\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fwebt\\\u002Fa_\\\u002Flk\\\u002F9m\\\u002Fa_lk9mjkccjox-zccjrpfolmkmq.png\"}},\"description\":\"Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления...\",\"url\":\"https:\\\u002F\\\u002Fhabr.com\\\u002Fru\\\u002Farticles\\\u002F1080784\\\u002F#post-content-body\",\"about\":[\"h_javascript\",\"h_reactjs\",\"h_typescript\",\"h_css\",\"f_develop\"],\"image\":[\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002F830\\\u002F98a\\\u002F107\\\u002F83098a10775cc2dc835a65d831efd1dc.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002F433\\\u002F1ca\\\u002Fc2c\\\u002F4331cac2cfbec45de9ab8ab02aecdaa6.PNG\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002F78b\\\u002F379\\\u002F1bf\\\u002F78b3791bf1a89fa0b2956267a51c8a21.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002F05a\\\u002F23b\\\u002F734\\\u002F05a23b73461ca67f8f17e3d5d7a9fa56.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002F464\\\u002Fd1e\\\u002F13b\\\u002F464d1e13bc995cf1a3eadca2e9fd52c1.PNG\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002F1c2\\\u002F82e\\\u002F4e5\\\u002F1c282e4e5b43b8e074bd78318c4e71a1.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fupload_files\\\u002Fb3a\\\u002F3f2\\\u002F6ca\\\u002Fb3a3f26ca1ef69b5876e5daaa046a185.png\"]}","metaDescription":"Практический разбор создания и развития собственной UI-библиотеки Коротко: Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый...","mainImageUrl":null,"amp":true,"customTrackerLinks":[]},"polls":[],"commentsEnabled":{"status":true,"reason":null},"rulesRemindEnabled":false,"votesEnabled":true,"status":"published","plannedPublishTime":null,"isSeo":false,"checked":null,"hasPinnedComments":false,"format":"case","banner":null,"multiwidget":null,"multiwidgetUuid":null,"readingTime":7,"complexity":"low","isEditorial":false,"flowNew":{"id":"4","title":"Фронтенд","alias":"frontend"},"linkedPostTranslation":null,"hasRegionalRestrictions":false}},"articlesIds":{},"isLoading":false,"pagesCount":{},"route":{},"reasonsList":null,"postReasonsList":null,"view":"cards","oldEditorForm":null,"lastVisitedRoute":{},"ssrCommentsArticleIds":[""],"viewedPosts":[],"myFeedFilter":{"complexity":"all","score":"all","types":["articles","posts","news"]},"myFeedIsApplyFilters":false,"myFeedIsForce":false},"me":{"user":null,"uuid":null,"ppgDemanded":false,"karmaResetInfo":{"canReincarnate":null,"wasReincarnated":null,"currentScore":null},"notes":null,"userUpdates":{"feeds":{"newCount":null},"conversationUnreadCount":0,"trackerUnreadCount":0},"features":null},"banner":{"isArticleStickyPanelVisible":false,"isArticleStickyPanelAtTheBottom":false,"bannerContainer":null,"isHeaderBannerInView":true,"isSponsorBlockPresent":true,"isHeaderBannerVisible":true,"isPBrandingVisible":false},"refs":{"flowsRefs":[{"id":"2","title":"Бэкенд","alias":"backend"},{"id":"4","title":"Фронтенд","alias":"frontend"},{"id":"6","title":"Мобильная разработка","alias":"mobile_development"},{"id":"8","title":"Системное администрирование","alias":"admin"},{"id":"10","title":"Информационная безопасность","alias":"information_security"},{"id":"12","title":"AI и ML","alias":"ai_and_ml"},{"id":"14","title":"Промышленная инженерия","alias":"industrial_engineering"},{"id":"16","title":"Геймдев","alias":"gamedev"},{"id":"18","title":"Тестирование","alias":"quality_assurance"},{"id":"20","title":"Техническая поддержка","alias":"support"},{"id":"22","title":"Системный и бизнес-анализ","alias":"analytics"},{"id":"24","title":"Дизайн","alias":"design"},{"id":"26","title":"Менеджмент","alias":"management"},{"id":"28","title":"Топ-менеджмент","alias":"top_management"},{"id":"30","title":"Маркетинг и контент","alias":"marketing"},{"id":"36","title":"HR","alias":"human_resources"},{"id":"42","title":"Железо и гаджеты","alias":"hardware_and_gadgets"},{"id":"44","title":"DIY","alias":"diy"},{"id":"46","title":"Здоровье","alias":"healthcare"},{"id":"48","title":"Научпоп","alias":"popsci"},{"id":"50","title":"Другое","alias":"other"}],"moderationLockReasons":[],"moderationDraftReasons":[]},"promoData":{"isLoading":false,"hasLoaded":false,"featurer":null,"megaposts":null,"promoLinks":null,"promoPosts":null,"sticker":null,"stories":null,"activities":[],"partnerActivities":[],"isPromoDataAvailable":{"featurer":true,"promoPosts":true,"promoLinks":true,"megaposts":false}},"fixedBanner":{"isLoading":false,"viewEventsSent":[],"imagesLoaded":{}},"companies":{"companyRefs":{"__ALIAS_STORE__":true},"companyIds":{},"companyTopIds":[],"companyRouteStatistics":{},"pagesCount":{},"companyProfiles":{"__ALIAS_STORE__":true},"companiesCategories":[],"companiesCategoriesTotalCount":0,"companiesWidgets":{"__ALIAS_STORE__":true},"companiesWorkers":{"__ALIAS_STORE__":true},"companiesFans":{"__ALIAS_STORE__":true},"multiwidgets":{"__ALIAS_STORE__":true},"route":{},"isLoading":false,"companyWorkersLoading":false,"companyFansLoading":false,"multiwidgetLoading":false,"vacancies":{},"companiesGalleries":{"__ALIAS_STORE__":true},"companiesBanners":{"__ALIAS_STORE__":true},"companiesLandingVacancies":{"__ALIAS_STORE__":true},"companiesTechnologies":{"__ALIAS_STORE__":true},"workplaceInfo":null},"ssr":{"error":null,"isDataLoaded":true,"isDataLoading":false},"flowsMenu":{"flowsMenuData":null,"isLoading":false,"isMenuVisible":false},"hubs":{"hubRefs":{"__ALIAS_STORE__":true},"hubIds":{},"hubRouteStats":{},"pagesCount":{},"isLoading":false,"counters":{"__ALIAS_STORE__":true},"route":{"name":"","params":{},"query":{}}},"adblock":{"hasAcceptableAdsFilter":false,"hasAdblock":false},"readingHistory":{"readingHistory":null},"pullRefresh":{"shouldRefresh":false},"viewport":{"prevScrollY":{},"scrollY":0,"width":0,"pageContentHeight":0},"publicationStats":{"statsInfo":{},"statsFunnels":{},"statsGraph":{},"defaultSuggest":{},"suggest":{},"timeTracker":{},"isUserActive":true,"otherPublicationStats":{}},"users":{"authorRefs":{"__ALIAS_STORE__":true},"authorIds":{},"authorRouteStatistics":{},"pagesCount":{},"authorProfiles":{"__ALIAS_STORE__":true},"userHubs":{"__ALIAS_STORE__":true},"userInvitations":{"__ALIAS_STORE__":true},"authorFollowers":{"__ALIAS_STORE__":true},"authorFollowed":{"__ALIAS_STORE__":true},"userSpecialization":{"__ALIAS_STORE__":true},"karmaStats":[],"statistics":null,"isLoading":false,"isSubmitted":false,"authorFollowersLoading":false,"authorFollowedLoading":false,"userHubsLoading":false,"userInvitationsLoading":false,"route":{}},"feedStatistics":{"feedsStatistics":{}},"similarList":{"similarListIds":[],"similarListRefs":null},"daily-pack":{"_packs":{"72":{"id":72,"type":"daily","status":"actual","packDate":"2026-09-10","packCollection":[{"id":142,"position":1,"type":"standard","items":[{"id":1080212,"isPublished":true,"postType":"article","titleHtml":"Мощный ИИ агент в 16гб VRAM","preview":"\u003Cp\u003EНедавно я обновил свою видеокарту до RTX-5060-TI 16GB. Получив новую карту я решил исследовать, что можно на ней запустить из нейросетей. Традиционно пишут, что нормальные ИИ модели начинаются с rtx3090 и выше, что ниже 24GB VRAM жизни нет. В этой статье я постараюсь развеять этот миф.\u003C\u002Fp\u003E","author":{"alias":"Mashinow","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fc4b\u002Fafa\u002Fd5c\u002Fc4bafad5cfcfa50026978cb196d32dd3.JPG","fit":"cover","positionY":0,"positionX":0},"analyticsZone":1},{"id":1080196,"isPublished":true,"postType":"article","titleHtml":"Видит ли MAX ваш VPN при Split Routing, если VPN не установлен на компьютере?","preview":"\u003Cp\u003EЯ решил проверить, способен ли MAX Desktop обнаружить VPN, если VPN-клиента в Windows вообще нет. В моей сети Split Routing выполняет роутер Keenetic: часть трафика идёт напрямую, а часть — через OpenConnect-туннель на удалённый VPS.\u003C\u002Fp\u003E\u003Cp\u003EВо время эксперимента я фиксировал действия MAX с помощью Process Monitor, TCPView и Wireshark, а трафик на стороне VPN-сервера — через tcpdump. Проверил, какие системные параметры читает MAX, куда подключается и появляются ли признаки целенаправленного поиска VPN-маршрута.\u003C\u002Fp\u003E","author":{"alias":"Slava_B","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fb49\u002Fe83\u002F6c9\u002Fb49e836c9376b6a5335e311b9627677f.png","fit":"cover","positionY":0,"positionX":0},"analyticsZone":2},{"id":1080342,"isPublished":true,"postType":"article","titleHtml":"AmneziaWG: от 2.0 к 3.1, и что творится у вас под капотом","preview":"\u003Cp\u003E\u003Cstrong\u003EAmneziaWG 3.0 зашифровал заголовки пакетов, 3.1 добавил случайные хвосты — и оба раза «просто обновить бинарники» оказалось недостаточно.\u003C\u002Fstrong\u003E Это конспект двух обновлений, прогнанных через собственную панель: кто в связке \u003Ccode\u003Eawg-quick\u003C\u002Fcode\u003E \u002F \u003Ccode\u003Eawg\u003C\u002Fcode\u003E \u002F \u003Ccode\u003Eamneziawg-go\u003C\u002Fcode\u003E за что отвечает, какие параметры обязаны совпасть на сервере и клиенте побайтово, а какие каждая сторона ставит себе сама, почему \u003Ccode\u003ES1\u003C\u002Fcode\u003E—\u003Ccode\u003ES4\u003C\u002Fcode\u003E больше не могут быть меньше 12 и в каком порядке мигрировать живой сервер, чтобы не отключить разом всех клиентов.\u003C\u002Fp\u003E\u003Cp\u003EГлавная особенность всех этих граблей одна: о любой ошибке протокол сообщает вам молчанием. Ни строчки в логе — просто никогда не появляется \u003Ccode\u003Elatest handshake\u003C\u002Fcode\u003E. Ниже карта тех мест, где стоит искать.\u003C\u002Fp\u003E","author":{"alias":"meshroom","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fca9\u002Ffde\u002F70c\u002Fca9fde70c31d31b7049c46ee6d5c711e.png","fit":"cover","positionY":0,"positionX":0},"analyticsZone":3},{"id":1079984,"isPublished":true,"postType":"article","titleHtml":"Как защитить веб‑приложения с помощью Coraza (ModSecurity)","preview":"\u003Cp\u003EПрименение WAF (Web Application Firewall, межсетевой экран уровня приложения) в современном Вебе — стандартная и обязательная практика. В руках злоумышленников — большое количество профессиональных и, главное, доступных инструментов анализа защищенности и взлома. Приходится ответственно подходить к выбору WAF, понимать, где его правильно разместить и как грамотно настроить.\u003C\u002Fp\u003E\u003Cp\u003EСегодня мы познакомимся с решением Coraza, развернем его в инфраструктуре \u003Ca href=\"https:\u002F\u002Fselectel.ru\u002Fservices\u002Fis\u002F?utm_source=habr.com&utm_medium=referral&utm_campaign=is_article_coraza_090926_content\"\u003ESelectel\u003C\u002Fa\u003E, а также разберем, как писать свои правила для выявления вредоносных запросов. Интересно будет всем, кто так или иначе вовлечен в защиту общедоступных ресурсов.\u003C\u002Fp\u003E","author":{"alias":"is113","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F66d\u002Fba2\u002F669\u002F66dba2669f952e2608f9904a3e8235dc.jpg","fit":"cover","positionY":0,"positionX":0},"analyticsZone":4},{"id":1080316,"isPublished":true,"postType":"article","titleHtml":"Тест DeepSeek, Qwen, GLM и прочих LLM на продвинутом пользовательском железе","preview":"\u003Cp\u003EВ статье рассказываю о личном опыте запуска современных LLM на домашнем ПК, сравниваю модели, кванты и движки, показываю результаты практических тестов и делюсь выводами о том, что сегодня по моему мнению имеет смысл запускать локально.\u003C\u002Fp\u003E","author":{"alias":"ZloyFrag","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F977\u002F19f\u002F866\u002F97719f86694f910a27ba0a361d39ccaa.jpg","fit":"cover","positionY":0,"positionX":0},"analyticsZone":5},{"id":1077474,"isPublished":true,"postType":"article","titleHtml":"Что происходит в Wi-Fi: анализ трафика на канальном уровне","preview":"\u003Cp\u003EЗадача просмотра сетевого трафика интересна тем, что позволяет увидеть, что фактически передаётся между двумя хостами, и разобраться, что происходит внутри сетевого соединения, которое в обычной ситуации выглядит как «чёрный ящик».\u003C\u002Fp\u003E","author":{"alias":"artyomsoft","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002F0eb\u002Ff65\u002F678\u002F0ebf656780bb66b3c6428e085003718a.png","fit":"cover","positionY":0,"positionX":0},"analyticsZone":6}]},{"id":144,"position":2,"type":"leisure","items":[{"id":1080378,"isPublished":true,"postType":"news","titleHtml":"OpenPaper выпустила 13-дюймовую цветную E‑Ink‑рамку с поддержкой Home Assistant и автономностью до полугода","preview":"\u003Cp\u003EРазработчики проекта paperlesspaper \u003Ca href=\"https:\u002F\u002Fpaperlesspaper.de\u002Fen\u002Fopenpaper-l\" rel=\"noopener nofollow\"\u003Eвыпустили\u003C\u002Fa\u003E OpenPaper L — цифровую рамку с цветным 13,3-дюймовым экраном на электронных чернилах. Устройство работает локально и подключается к Home Assistant.\u003C\u002Fp\u003E","author":{"alias":"daniilshat","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fee8\u002F869\u002Fbce\u002Fee8869bceaf627648506279bd8981b18.jpg","fit":"cover","positionY":0,"positionX":0},"analyticsZone":7},{"id":1073670,"isPublished":true,"postType":"article","titleHtml":"Как Невил Маскелайн взломал беспроводной телеграф Маркони","preview":"\u003Cp\u003EНевила Маскелайна часто называют первым хакером в истории. Это спорный титул, ибо свой «взлом» Маскелайн совершил аж в 1903 году, задолго до появления первых ЭВМ. События, произошедшие 4 июня того года, сейчас любят пересказывать очень и очень упрощенно. И очень зря, ведь история реально интересная. Так как мы с вами на техническом портале, мы можем позволить себе углубиться в технику и узнать, что же именно сотворил хитрец Невил и как он смог «взломать» радиосвязь самого Гульельмо Маркони.\u003C\u002Fp\u003E","author":{"alias":"beget_com","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fd27\u002Fb05\u002F5f1\u002Fd27b055f18e52fa0f98df044d7bd0340.jpeg","fit":"cover","positionY":0,"positionX":10},"analyticsZone":8},{"id":1075056,"isPublished":true,"postType":"article","titleHtml":"PCIe и DDR3 на ПЛИС Gowin: как мы заставили работать то, что не работало у производителя","preview":"\u003Cp\u003EПривет, Хабр! Меня зовут Владимир Заикин. Под руководством Семёна Григорьева \u003Ca href=\"https:\u002F\u002Fhabr.com\u002Fru\u002Fusers\u002Frsdpisuy\u002F\"\u003E@rsdpisuy\u003C\u002Fa\u003E, я занимаюсь разработкой на ПЛИС в лаборатории YADRO в СПбГУ. Мой рассказ — о том, как мы реализовали общение по PCIe с ПЛИС на плате Sipeed Tang Mega 138K Pro с кристаллом GW5AST-138K от Gowin.\u003C\u002Fp\u003E\u003Cp\u003EУстройство, реализованное на ПЛИС, должно было принимать данные по PCIe, сохранять их в DDR3 и обрабатывать пользовательской логикой. Чтобы реализовать такую функциональность, мы обратились к примеру взаимодействия с PCIe-контроллером от производителя платы, но тот оказался нерабочим. Пришлось разбираться самим. В итоге мы сделали рабочий стенд: Linux-драйвер, хост-программа на C и аппаратное описание на Verilog. Все это выложили в открытый доступ, чтобы поделиться своим опытом и помочь другим безболезненно разобраться с этой проблемой.\u003C\u002Fp\u003E","author":{"alias":"Friend-zva","deleted":false},"image":{"url":"https:\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fupload_files\u002Fd81\u002F102\u002F9c5\u002Fd811029c5e54837fa8aa485b91f9a0d3.jpg","fit":"cover","positionY":69,"positionX":0},"analyticsZone":9}]}],"previousPackId":70,"previousPackDate":"2026-09-09","nextPackId":null,"nextPackDate":null,"bookmarksCount":7,"isBookmarked":false,"reach":83924,"readers":617}},"_currentPackId":72,"_bookmarkPacksIds":{},"_bookmarksPageCount":{},"isLoadingPack":false},"projectsBlocks":{"activeBlocks":{"Salary":"project-block-article"}},"karma":{"userReasonsList":null}};(function(){var s=document.currentScript||document.scripts[document.scripts.length-1];s.parentNode.removeChild(s);}());</script>
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/svg-img-r1n9vzM8.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/scroll-lock-transition-DptUpVQt.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/user-publication-8eVtRXjz.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/svg-icon-R4RmUFby.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/user-publication-presenter-opr9GWzo.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-presenter-qr679rag.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/new-sidebar-BPHLfQC9.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/current-viewers-BthVhL8c.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/use-uuid-3pUaeD3o.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-snippet-B1AKowH-.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/user-info-DEXNWEJ2.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-datetime-published-mCvYLFVV.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/title-Dx1eIaw5.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/icon-counter-BmPpUBhH.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/publication-label-D1N8NdVO.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/build-page-producer-dimension-DUUblpig.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/check-content-B5yILTmZ.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/tags-list-D8puhLyp.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/data-icons-UIQKMN1V.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/votes-lever-XAPVRp2k.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/bookmarks-button-CJSOFWtd.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-comments-counter-link-BbnQY3y7.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-extra-menu-vdAxZGFQ.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-blocks-Wdo7C-3d.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/digest-subscription-wrapper-CREbAoOg.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/article-redirects-2rVJQLrh.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/button-Ccclp6vQ.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/button-base-BdvEVC2k.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/daily-pack-B6LQ_-JL.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/tabs-NcEC6QlL.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/bordered-card-XBXkhzQo.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/promo-block-BjDUi6nP.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/random-project-block-C7jz2gmb.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/user-card-BdcjvV9G.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/reach-counter-DUy9zrm3.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/button-follow-Bar2fZBL.css">
<link rel="stylesheet" href="https://assets.habr.com/habr-web/release_2.347.0/client/css/reach-and-readers-popup-CqS5bpAT.css">
<script>window.yaContextCb = window.yaContextCb || [];</script>
<script>
window.bus = {
queue: {},
cbs: {},
on(event, cb) {
if (!this.cbs[event]) this.cbs[event] = [];
this.cbs[event].push(cb);
if (this.queue[event]) {
this.queue[event].forEach(e => cb(e));
}
},
off(event, cb) {
if (!this.cbs[event]) return;
this.cbs[event] = this.cbs[event].filter(savedCb => savedCb !== cb);
},
emit(event, payload) {
if (!this.queue[event]) this.queue[event] = [];
this.queue[event].push(payload);
if (this.cbs[event]) this.cbs[event].forEach(cb => cb(payload));
}
}
</script>
<script src="https://yandex.ru/ads/system/context.js" async onerror="window.bus.emit('yandex-error')" onload="window.bus.emit('yandex-load')"></script>
<script async src="https://cdn.skcrtxr.com/roxot-wrapper/js/roxot-manager.js?pid=c427193e-c45c-4b31-b9de-0d5bc41115fd" onerror="window.bus.emit('roxot-error')"></script>
<script src="https://rap.skcrtxr.com/pub/pg/e40a4600-273c-4ae8-a7fd-74ad2f469a15/run-rap.js"></script>
<script type="module" crossorigin src="https://assets.habr.com/habr-web/release_2.347.0/client/js/indexCBH9KBq6.js"></script>
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/rolldown-runtime5zDrZkfy.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/form-builderBf-5cQyb.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/tslib.es6DdE0m_Aj.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/preload-helperDKYn4wcI.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/i18nBC_usZhd.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/lodash.throttleBnMf-J3V.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/libD5UGurDK.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/side-channelC-bck2GT.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/errorCVLWPCaY.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/xhrB5rMP3G9.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/domlA3vWeWI.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/scrollNYk8uEoL.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/publication-routingCO4AVfaO.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/postBxgf9mVm.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/getEnvVariable8tnsj5oD.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/vue-routerB5lCuJRK.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/globalD54yVNTu.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/useRegionCookieDJBjKxae.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/_plugin-vue_export-helperC7MAXBGH.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/button-baseBMe73YB2.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/buttonCoHU6Vun.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/use-i18nDjpGdBfU.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/svg-imgBNy6H3kV.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/svg-iconBD1brows.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/urlCZ1R9nMr.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/lodash.merge5yY-iYN8.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/build-route-keyCBP7Cirw.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/userB75LfMl0.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/meCLQ8vGN7.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/objectBkWN9koo.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/pull-refreshBlwScxK8.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/flowB3Mk76Ru.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/hubDIF-0aD-.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/base-popoverBTGySt6O.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/sharedDoDYkdMM.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/coreMpPmEKRq.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/device-detectionCiE-b2Hp.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/scroll-lock-transition_JauzMLl.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/get-default-avatarDus2kVFJ.js">
<link rel="modulepreload" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/js/swiperBez1B41G.js">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/button-base-BdvEVC2k.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/button-Ccclp6vQ.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/svg-img-r1n9vzM8.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/svg-icon-R4RmUFby.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/base-popover-DIfjoqU2.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/scroll-lock-transition-DptUpVQt.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/swiper-DTw_kYGC.css">
<link rel="stylesheet" crossorigin href="https://assets.habr.com/habr-web/release_2.347.0/client/css/index-BhHKOogt.css">
</head>
<body>
<noscript><img src="https://rap.skcrtxr.com/pub/pix/ecc25904-3c35-4d25-a558-7b09044600eb" style="position:absolute;height:0;width:0;" alt=""></noscript>
<noscript><img src="https://rap.skcrtxr.com/pub/pix/ec5c8352-000b-44cf-a2b3-98b962ba86b6" style="position:absolute;height:0;width:0;" alt=""></noscript>
<noscript><img src="https://rap.skcrtxr.com/pub/pix/c656aa38-4739-45f0-9e75-36a681d2de0f" style="position:absolute;height:0;width:0;" alt=""></noscript>
<noscript><img src="https://rap.skcrtxr.com/pub/pix/5cd2665a-3293-483b-9fbd-5c5bddab7ddd" style="position:absolute;height:0;width:0;" alt=""></noscript>
<noscript><img src="https://rap.skcrtxr.com/pub/pix/e0388a69-7257-480a-8c16-6d5e40c76d87" style="position:absolute;height:0;width:0;" alt=""></noscript>
<div id="mount"><div id="app"><div class="tm-layout__wrapper"><!--[--><!----><div></div><div class="header-banner-wrapper"><!----></div><header class="tm-header tm-header"><!----><!----><div class="tm-page-width"><!--[--><div class="tm-header__container"><button aria-expanded="false" aria-label="Toggle menu" class="burger-button tm-header__button tm-header__burger" data-v-806e3326><span class="line top" data-v-806e3326></span><span class="line middle" data-v-806e3326></span><span class="line bottom" data-v-806e3326></span></button><span class="tm-header__logo-wrap"><a class="tm-header__logo tm-header__logo_hl-ru tm-header__logo" href="/ru/feed"><svg class="tm-svg-img tm-header__icon" height="16" width="16"><title>Хабр</title><use xlink:href="/img/habr-logo-ru.svg#logo"></use></svg></a></span><span class="tm-header__divider"></span><!--[--><a class="tm-header__all-flows" href="/ru/articles/">Все потоки</a><!--]--><!----><div class="tm-header-user-menu tm-base-layout__user-menu"><a href="/ru/search/" class="tm-header-user-menu__item tm-header-user-menu__search"><svg class="tm-svg-img tm-header-user-menu__icon tm-header-user-menu__icon_search" height="24" width="24"><title>Поиск</title><use xlink:href="/img/megazord-v28.svg?2.347.0#search"></use></svg></a><!----><!----><div class="tm-header-user-menu__item tm-header-user-menu__write"><a href="/ru/sandbox/start/" class=""><svg class="tm-svg-img tm-header-user-menu__icon tm-header-user-menu__icon_write" height="24" width="24"><title>Редактировать</title><use xlink:href="/img/megazord-v28.svg?2.347.0#pencil"></use></svg></a><!----></div><!--[--><div class="tm-header-user-menu__item"><button class="tm-header-user-menu__toggle"><svg class="tm-svg-img tm-header-user-menu__icon" height="24" width="24"><title>Настройки</title><use xlink:href="/img/megazord-v28.svg?2.347.0#page-settings"></use></svg></button></div><a href="https://habr.com/kek/v1/auth/habrahabr/?back=/ru/articles/1080784/&hl=ru" rel="nofollow" class="tm-header-user-menu__item tm-header-user-menu__login" role="button"><!--[-->Войти<!--]--></a><!--]--><template><!----></template><!--teleport start--><!--teleport end--></div></div><!--]--></div></header><div class="tm-layout"><div class="tm-page-progress-bar"></div><!----><div class="tm-page-width notifications-container"><!--[--><!----><!----><!----><!--]--></div><main class="tm-layout__container"><div class="tm-page" hl="ru" data-async-called="true" style="--v6b5459bf:300px;--v63b999fc:0;--v53792d5e:0;"><!----><div class="tm-page-width"><!--[--><!----><div class="tm-page__wrapper"><!----><div class="tm-page__main_has-sidebar tm-page__main"><div class="pull-down"><!----><div class="pull-down__header" style="height:0px;"><div class="pull-down__content" style="bottom:10px;"><svg class="tm-svg-img pull-down__icon pull-down__arrow" height="24" width="24"><title>Обновить</title><use xlink:href="/img/megazord-v28.svg?2.347.0#pull-arrow"></use></svg></div></div><!--[--><div><!--[--><div class="tm-article-presenter" data-async-called="true"><!--[--><!--]--><!--[--><!--[--><!--]--><!----><!--]--><div class="tm-article-presenter__body"><div class="tm-misprint-area"><div class="tm-misprint-area__wrapper"><!--[--><article class="tm-article-presenter__content tm-article-presenter__content_narrow"><!--[--><div class="tm-article-presenter__header"><!--[--><!--]--><div class="article-snippet full-article tm-article-presenter__snippet" data-v-a0c576a8><!--[--><!--]--><div class="meta-container" data-v-a0c576a8><div class="meta" data-v-a0c576a8><span class="tm-user-info author" data-v-a0c576a8><a href="/ru/users/Goker1235/" class="tm-user-info__userpic"><!--[--><div class="tm-entity-image"><!--[--><img alt="" class="tm-entity-image__pic" height="24" src="https://assets.habr.com/habr-web/release_2.347.0/client/img/avatars/108.png" width="24"><!--]--></div><!--]--></a><span class="tm-user-info__user tm-user-info__user_appearance-default"><a href="/ru/users/Goker1235/" class="tm-user-info__username"><!--[-->Goker1235<!--]--></a><!----><!--[--><span class="tm-article-datetime-published" data-v-a0c576a8><time data-allow-mismatch datetime="2026-09-10T10:21:24.000Z" title="2026-09-10, 10:21">10 часов назад</time></span><!--]--></span></span></div><!----></div><h1 class="tm-title tm-title_h1" lang="ru" data-v-a0c576a8><span>Мы сделали свой UI Kit: 6 компонентов превратились в 30+, а одна ошибка сломала разметку во всех проектах</span></h1><div class="stats" data-v-a0c576a8><div class="tm-article-complexity tm-article-complexity_complexity-low" data-v-a0c576a8><span class="tm-svg-icon__wrapper tm-article-complexity__icon"><svg class="tm-svg-img tm-svg-icon" height="24" width="24"><title>Уровень сложности</title><use xlink:href="/img/megazord-v28.svg?2.347.0#complexity-low"></use></svg></span><span class="tm-article-complexity__label">Простой</span></div><div class="tm-article-reading-time" data-v-a0c576a8><span class="tm-svg-icon__wrapper tm-article-reading-time__icon"><svg class="tm-svg-img tm-svg-icon" height="24" width="24"><title>Время на прочтение</title><use xlink:href="/img/megazord-v28.svg?2.347.0#clock"></use></svg></span><span class="tm-article-reading-time__label">7 мин</span></div><span class="tm-icon-counter tm-data-icons__item reach-counter" data-v-a0c576a8><svg class="tm-svg-img tm-icon-counter__icon" height="24" width="24"><title>Охват и читатели</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-views"></use></svg><span class="tm-icon-counter__value" title="3651">3.7K</span></span></div><div class="tm-publication-hubs__container" data-v-a0c576a8><div class="tm-publication-hubs"><!--[--><span class="tm-publication-hub__link-container"><a href="/ru/hubs/javascript/" class="tm-publication-hub__link"><!--[--><span>JavaScript</span><span class="tm-article-snippet__profiled-hub" title="Профильный хаб"> * </span><!--]--></a></span><span class="tm-publication-hub__link-container"><a href="/ru/hubs/reactjs/" class="tm-publication-hub__link"><!--[--><span>ReactJS</span><span class="tm-article-snippet__profiled-hub" title="Профильный хаб"> * </span><!--]--></a></span><span class="tm-publication-hub__link-container"><a href="/ru/hubs/typescript/" class="tm-publication-hub__link"><!--[--><span>TypeScript</span><span class="tm-article-snippet__profiled-hub" title="Профильный хаб"> * </span><!--]--></a></span><span class="tm-publication-hub__link-container"><a href="/ru/hubs/css/" class="tm-publication-hub__link"><!--[--><span>CSS</span><span class="tm-article-snippet__profiled-hub" title="Профильный хаб"> * </span><!--]--></a></span><!--]--></div></div><div class="tm-article-labels" data-v-a0c576a8 data-v-d420b184><div class="tm-article-labels__container" data-v-d420b184><!----><!--[--><div class="publication-label variant-case" data-v-d420b184 data-v-db35b1ec><span data-v-db35b1ec>Кейс</span></div><!--[--><div class="publication-label variant-sandbox" data-v-d420b184 data-v-db35b1ec><a href="/ru/sandbox/" class="" data-v-db35b1ec>Из песочницы</a></div><!--]--><!--]--></div></div><!----><!----><!--teleport start--><!--teleport end--></div></div><!--[--><!----><div class="article-body" data-gallery-root lang="ru" data-v-df744e53><div data-v-df744e53><!--[--><!--]--></div><div id="post-content-body" data-v-df744e53><div><div class="article-formatted-body article-formatted-body article-formatted-body_version-2"><div xmlns="http://www.w3.org/1999/xhtml"><p><em>Практический разбор создания и развития собственной UI-библиотеки</em></p><blockquote><p><strong>Коротко: </strong><em>Собственная UI-библиотека — это не только набор React-компонентов. После появления нескольких проектов-потребителей на первый план выходят API, состояние, совместимость, зависимости, тестирование, документация и версионирование.</em></p></blockquote><h3>С чего всё начиналось</h3><p>Когда в проекте начинает появляться всё больше одинаковых элементов интерфейса, рано или поздно возникает вопрос: зачем каждый раз создавать один и тот же Button, Input или Select?</p><p>Мы рассматривали готовые решения, в том числе Ant Design и Material UI, но они не закрывали наши требования. Нам требовался больший контроль над внешним видом и поведением компонентов, возможность детально адаптировать их под наши задачи и учитывать требования проекта к безопасности.</p><p>Поэтому мы решили сделать собственную UI-библиотеку. Первая версия заняла примерно месяц — вместе с макетами, разработкой, настройкой сборки и тестами.</p><h3>Стек и первая версия</h3><ul><li><p>React</p></li><li><p>TypeScript</p></li><li><p>CSS Modules</p></li><li><p>Webpack</p></li><li><p>Unit-тесты</p></li></ul><p>На старте было шесть компонентов:</p><ul><li><p>Input</p></li><li><p>Select</p></li><li><p>Drawer</p></li><li><p>Badge</p></li><li><p>Table</p></li><li><p>Layout — базовая обёртка для построения интерфейса</p></li></ul><p>Для каждого компонента были предусмотрены тесты. Сейчас библиотека выросла до 30+ компонентов. Среди них есть сложные составные элементы — например, фильтр и календарь с выбором дат и времени в разных форматах.</p><figure class="full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/830/98a/107/83098a10775cc2dc835a65d831efd1dc.png" width="1781" height="737" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/830/98a/107/83098a10775cc2dc835a65d831efd1dc.png 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/830/98a/107/83098a10775cc2dc835a65d831efd1dc.png 781w" loading="lazy" decode="async"/></figure><h3>От простых компонентов к сложным</h3><div class="floating-image"><figure class="float "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/433/1ca/c2c/4331cac2cfbec45de9ab8ab02aecdaa6.PNG" width="323" height="241" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/433/1ca/c2c/4331cac2cfbec45de9ab8ab02aecdaa6.PNG 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/433/1ca/c2c/4331cac2cfbec45de9ab8ab02aecdaa6.PNG 781w" loading="lazy" decode="async"/></figure><p>Первые компоненты решали достаточно конкретные задачи. Но по мере развития продукта стали появляться элементы, которые объединяют несколько контролов, popup-элементов и собственную логику. В этот момент становится понятно, что UI Kit — уже не просто коллекция компонентов. Это отдельный программный продукт со своим API, тестами, документацией, версиями и жизненным циклом.</p></div><h3>Ошибка №1. Слишком сложный компонент</h3><p>Одна из главных ошибок — попытка сделать компонент максимально универсальным. На бумаге кажется логичным: если компонент умеет больше, его можно использовать в большем количестве мест.</p><p>На практике часто происходит наоборот. Чем больше логики появляется внутри компонента, тем сложнее его изменить, протестировать и адаптировать под конкретный сценарий.</p><p>Особенно это заметно на составных компонентах. Фильтр может включать несколько контролов, собственное состояние, кнопки применения и сброса, popup-элементы и дополнительную логику взаимодействия.</p><blockquote><p><strong>Практический вывод: </strong><em>Не стоит пытаться сделать один компонент решением абсолютно всех задач. Иногда лучше дать разработчику набор небольших компонентов и возможность собрать нужный сценарий самостоятельно.</em></p></blockquote><h3>Ошибка №2. Состояния разных экземпляров должны быть независимыми</h3><p>Одна из проблем переиспользуемых компонентов — неправильная организация состояния. Представим, что на разных страницах приложения есть Select с фильтрами. Открытие Select на странице A не должно влиять на Select на странице B.</p><figure class="full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/78b/379/1bf/78b3791bf1a89fa0b2956267a51c8a21.png" width="1781" height="666" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/78b/379/1bf/78b3791bf1a89fa0b2956267a51c8a21.png 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/78b/379/1bf/78b3791bf1a89fa0b2956267a51c8a21.png 781w" loading="lazy" decode="async"/></figure><p>Если разные экземпляры получают общий источник состояния без явной необходимости, пользователь может увидеть ситуацию, когда открытие одного селектора влияет на другой. Выглядит это как случайный баг, но причина архитектурная.</p><ul><li><p>что хранится внутри конкретного экземпляра;</p></li><li><p>что может быть контролируемым извне;</p></li><li><p>что действительно должно быть общим;</p></li><li><p>какие состояния не должны пересекаться между экземплярами.</p></li></ul><h3>Ошибка №3. Несколько popup внутри одного компонента</h3><p>Ещё сложнее становится, когда внутри одного сложного компонента находится несколько popup-элементов.</p><figure class="full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/05a/23b/734/05a23b73461ca67f8f17e3d5d7a9fa56.png" width="1781" height="792" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/05a/23b/734/05a23b73461ca67f8f17e3d5d7a9fa56.png 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/05a/23b/734/05a23b73461ca67f8f17e3d5d7a9fa56.png 781w" loading="lazy" decode="async"/></figure><p>Каждому popup необходимо правильно определить положение относительно элемента, который его открыл. Если механизм позиционирования недостаточно изолирован, один popup может начать влиять на другой.</p><p>Причём отдельно Select, DatePicker и Dropdown могут работать нормально, а проблема появляется только при их совместном использовании внутри сложного компонента. Поэтому такие сценарии важно тестировать не только по отдельности, но и в комбинациях, близких к реальному использованию.</p><h3>Ошибка №4. «Компонент работает» — ещё не значит, что он готов</h3><div class="floating-image"><figure class="float "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/464/d1e/13b/464d1e13bc995cf1a3eadca2e9fd52c1.PNG" width="347" height="379" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/464/d1e/13b/464d1e13bc995cf1a3eadca2e9fd52c1.PNG 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/464/d1e/13b/464d1e13bc995cf1a3eadca2e9fd52c1.PNG 781w" loading="lazy" decode="async"/></figure><p>На каждый компонент нашего UI Kit есть тесты. Но наличие теста само по себе не гарантирует отсутствие проблем. Компонент может пройти unit-тесты и при этом визуально сломаться после изменения CSS или вести себя иначе в составе сложного компонента. Поэтому важно разделять две вещи: «код работает» и «компонент корректно работает во всех местах, где его используют».</p></div><h3>Самый показательный случай — Drawer</h3><p>Самый неприятный случай произошёл с Drawer. Мы внесли изменения в компонент, выпустили новую версию UI Kit, после чего обновили её в проектах. После обновления разметка Drawer уехала.</p><figure class="full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/1c2/82e/4e5/1c282e4e5b43b8e074bd78318c4e71a1.png" width="1781" height="809" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/1c2/82e/4e5/1c282e4e5b43b8e074bd78318c4e71a1.png 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/1c2/82e/4e5/1c282e4e5b43b8e074bd78318c4e71a1.png 781w" loading="lazy" decode="async"/></figure><blockquote><p><strong>Почему это больно: </strong><em>Ошибка в локальном компоненте затрагивает одну страницу.</em><br/><em>Ошибка в общей библиотеке может затронуть сразу несколько проектов после обновления версии.</em></p></blockquote><p>Получилась цепочка: изменение Drawer → новая версия UI Kit → обновление версии в проектах → изменение поведения Drawer → проблема во всех проектах.</p><p>Именно здесь особенно хорошо видно отличие UI Kit от обычного компонента внутри приложения. Внутри приложения ошибку можно быстро исправить и задеплоить. В библиотеке ошибка становится частью версии, которую могут использовать несколько независимых приложений.</p><p>Поэтому любое изменение общего компонента необходимо рассматривать не только с точки зрения самого компонента. Нужно задать вопрос: «Что произойдёт со всеми потребителями этой библиотеки?»</p><h3>Ошибка №5. Недооценили влияние зависимостей</h3><p>UI Kit не существует в вакууме. У него есть React, TypeScript и другие зависимости. Если несколько приложений используют одну библиотеку, обновление её зависимостей может стать отдельной задачей.</p><p>Особенно это заметно в микросервисной архитектуре, где разные приложения могут иметь собственные циклы релизов. Если общий пакет начинает жёстко диктовать версии зависимостей, обновление может превратиться в цепочку изменений сразу в нескольких проектах.</p><blockquote><p><strong>Практический вывод:</strong><em>Общие пакеты стоит проектировать с учётом границ ответственности за зависимости: какие версии поддерживаются, что является peer dependency, как проверяется совместимость и как выпускаются breaking changes.</em></p></blockquote><h3>Ошибка №6. Недооценили документацию</h3><p>Когда компонент пишет его автор, большая часть API кажется очевидной. Но через несколько месяцев библиотекой пользуется другой разработчик.</p><pre><code><Button
variant="primary"
size="large"
loading
>
Сохранить
</Button></code><div class="code-explainer"><a href="https://sourcecraft.dev/" class="tm-button code-explainer__link" style="visibility: hidden;"><img style="width:87px;height:14px;object-fit:cover;object-position:left;"/></a></div></pre><p>Без документации разработчику приходится открывать исходный код, чтобы понять варианты, обязательные props, состояния и ограничения.</p><ul><li><p>что делает компонент;</p></li><li><p>какие props он принимает;</p></li><li><p>какие значения поддерживаются;</p></li><li><p>какие значения используются по умолчанию;</p></li><li><p>какие есть ограничения;</p></li><li><p>какие есть реальные сценарии использования. </p></li></ul><blockquote><p><strong>Главный принцип: </strong>Документация — не приложение к UI Kit. Это часть самого продукта.</p></blockquote><h3>Ошибка №7. Не нужно изобретать велосипед</h3><p>Создавая собственную библиотеку, легко попасть в ловушку: «раз это наша библиотека, всё должно быть написано нами с нуля». Но даже если Ant Design или Material UI не подходят целиком, их решения стоит изучать.</p><ul><li><p>как похожая задача решена в других библиотеках;</p></li><li><p>какой API они используют;</p></li><li><p>как устроено управление состоянием;</p></li><li><p>как решено позиционирование popup;</p></li><li><p>какие edge cases они учитывают;</p></li><li><p>какие ограничения есть у их подхода.</p></li></ul><p>Не обязательно копировать реализацию. Гораздо разумнее взять проверенную концепцию и адаптировать её под свои требования.</p><h3>Что бы мы выбрали сегодня: Ant Design, MUI или собственный UI Kit?</h3><p>Однозначного ответа нет. Выбор зависит не от того, какая библиотека «лучше», а от требований конкретного продукта.</p><div><div class="table"><table><tbody><tr><td data-colwidth="185" width="185"><p align="center"><strong>Критерий</strong></p></td><td><p align="center"><strong>Готовая библиотека</strong></p></td><td><p align="center"><strong>Собственный UI Kit</strong></p></td><td data-colwidth="185" width="185"><p align="center"><strong>Что важно</strong></p></td></tr><tr><td data-colwidth="185" width="185"><p align="left">Скорость старта</p></td><td><p align="left">Высокая</p></td><td><p align="left">Ниже</p></td><td data-colwidth="185" width="185"><p align="left">Для MVP и внутренних систем готовое решение часто выгоднее</p></td></tr><tr><td data-colwidth="185" width="185"><p align="left">Контроль UI</p></td><td><p align="left">Зависит от API</p></td><td><p align="left">Максимальный</p></td><td data-colwidth="185" width="185"><p align="left">Свой дизайн и поведение легче контролировать</p></td></tr><tr><td data-colwidth="185" width="185"><p align="left">Поддержка</p></td><td><p align="left">Есть экосистема</p></td><td><p align="left">На команде</p></td><td data-colwidth="185" width="185"><p align="left">Свой UI Kit требует постоянного сопровождения</p></td></tr><tr><td data-colwidth="185" width="185"><p align="left">Несколько проектов</p></td><td><p align="left">Зависит от настройки</p></td><td><p align="left">Удобно</p></td><td data-colwidth="185" width="185"><p align="left">Особенно если есть общая дизайн-система</p></td></tr><tr><td data-colwidth="185" width="185"><p align="left">Цена ошибки</p></td><td><p align="left">Обычно ниже</p></td><td><p align="left">Может быть высокой</p></td><td data-colwidth="185" width="185"><p align="left">Общая библиотека влияет на всех потребителей</p></td></tr></tbody></table></div></div><p>Если задача — быстро собрать внутреннюю админку, CRM или MVP, я бы в первую очередь посмотрел на готовые решения. Они дают большой набор компонентов и экономят время на базовой инфраструктуре.</p><p>Собственная библиотека начинает иметь смысл, когда требования проекта заметно выходят за рамки готового решения: нужен полностью свой дизайн, специфическое поведение, единая система компонентов для нескольких проектов или полный контроль над API.</p><div><div class="table"><table><tbody><tr><td><p align="left"><strong>Наш выбор: </strong><em>Если требования проекта останутся такими же, мы, скорее всего, снова выберем собственный UI Kit. Но сегодня подошли бы к его созданию осторожнее: заранее продумали бы границы компонентов, состояние, API, зависимости, совместимость, документацию, тестирование и стратегию версионирования.</em></p></td></tr></tbody></table></div></div><h3>Как изменился UI Kit</h3><figure class="full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/b3a/3f2/6ca/b3a3f26ca1ef69b5876e5daaa046a185.png" width="1781" height="737" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/b3a/3f2/6ca/b3a3f26ca1ef69b5876e5daaa046a185.png 780w, https://habrastorage.org/r/w1560/getpro/habr/upload_files/b3a/3f2/6ca/b3a3f26ca1ef69b5876e5daaa046a185.png 781w" loading="lazy" decode="async"/></figure><p>Главное изменение произошло даже не в количестве компонентов. Изменилось наше отношение к библиотеке.</p><p>В начале мы воспринимали UI Kit как набор переиспользуемых компонентов. Теперь это отдельный продукт, у которого есть API, версии, зависимости, тесты, документация, проекты-потребители, обратная совместимость и собственный жизненный цикл.</p><h3>Что бы я сделал иначе сегодня</h3><ol><li><p>Сначала изучил бы существующие решения — не нужно изобретать всё самостоятельно.</p></li><li><p>Не делал бы компоненты слишком универсальными — компонент должен решать задачу, но не предусматривать все возможные сценарии.</p></li><li><p>С самого начала продумал бы состояние — особенно для интерактивных компонентов.</p></li><li><p>Считал бы документацию частью разработки — компонент без понятного API и примеров сложно считать законченным.</p></li><li><p>Тестировал бы не только компоненты, но и их комбинации — особенно сложные составные компоненты.</p></li><li><p>Осторожнее относился бы к breaking changes — если библиотеку используют несколько проектов, изменение одного компонента может затронуть всю систему.</p></li></ol><h3>Вместо заключения</h3><p>Создать первый UI Kit оказалось не так сложно. Первую версию мы сделали примерно за месяц.</p><p>Самое интересное началось после этого. Шесть компонентов превратились в тридцать с лишним. Появились сложные составные компоненты, собственные сценарии взаимодействия, зависимости, версии и несколько проектов, использующих одну библиотеку.</p><p>И вместе с ростом библиотеки изменились сами проблемы.</p><blockquote><p><strong>Главный урок: </strong>В начале главный вопрос был: «Как сделать этот компонент?»<br/>Позже вопрос стал другим: «Как изменить этот компонент так, чтобы не сломать всё, что уже использует нашу библиотеку?»</p></blockquote><p>UI-библиотека — это не набор красивых компонентов. Это отдельный продукт, который нужно проектировать с расчётом на развитие, совместимость и будущих пользователей.</p><p>И чем больше проектов её используют, тем дороже становится каждая архитектурная ошибка.</p></div></div></div><!----><!----></div><!----><!----></div><!--]--><!----><div class="tm-article-presenter__meta"><div class="tm-separated-list tag-list tm-article-presenter__meta-list"><span class="tm-separated-list__title">Теги:</span><ul class="tm-separated-list__list"><!--[--><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[javascript]" class="link"><span>javascript</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[typescript]" class="link"><span>typescript</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[uikit]" class="link"><span>uikit</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[ui]" class="link"><span>ui</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[ui+kit]" class="link"><span>ui kit</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[react]" class="link"><span>react</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[react.js]" class="link"><span>react.js</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5]" class="link"><span>программирование</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[%D1%84%D1%80%D0%BE%D0%BD%D1%82%D0%B5%D0%BD%D0%B4]" class="link"><span>фронтенд</span></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/search/?target_type=posts&order=relevance&q=[%D1%84%D1%80%D0%BE%D0%BD%D1%82%D0%B5%D0%BD%D0%B4-%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0]" class="link"><span>фронтенд-разработка</span></a><!--]--></li><!--]--><!----></ul></div><div class="tm-separated-list tm-article-presenter__meta-list"><span class="tm-separated-list__title">Хабы:</span><ul class="tm-separated-list__list"><!--[--><li class="tm-separated-list__item"><!--[--><a href="/ru/hubs/javascript/" class="link"><!--[--><span>JavaScript</span><!--]--></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/hubs/reactjs/" class="link"><!--[--><span>ReactJS</span><!--]--></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/hubs/typescript/" class="link"><!--[--><span>TypeScript</span><!--]--></a><!--]--></li><li class="tm-separated-list__item"><!--[--><a href="/ru/hubs/css/" class="link"><!--[--><span>CSS</span><!--]--></a><!--]--></li><!--]--><!----></ul></div></div><!----><!--]--></article><!--]--></div><!----></div><div class="article-sticky-panel" style="" data-v-c6d39422><div class="tm-data-icons tm-data-icons tm-data-icons_space-big icons" data-v-c6d39422><div class="article-rating tm-data-icons__item" data-v-208d50a6><div class="tm-votes-lever tm-votes-lever tm-votes-lever_appearance-article votes-switcher" data-v-208d50a6><button class="tm-votes-lever__button" title="Нравится" type="button"><svg class="tm-svg-img tm-votes-lever__icon" height="24" width="24"><title>Нравится</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-vote"></use></svg></button><div class="tm-votes-lever__score tm-votes-lever__score_appearance-article tm-votes-lever__score"><!--[--><span><span class="tm-votes-lever__score-counter tm-votes-lever__score-counter">0</span></span><!--]--></div><button class="tm-votes-lever__button" title="Не нравится" type="button"><svg class="tm-svg-img tm-votes-lever__icon tm-votes-lever__icon_arrow-down" height="24" width="24"><title>Не нравится</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-vote"></use></svg></button></div><!--teleport start--><!--teleport end--><!----></div><!--[--><!----><!----><button class="bookmarks-button tm-data-icons__item" title="Добавить в закладки" type="button" data-v-41bd4ade><span class="tm-svg-icon__wrapper icon" data-v-41bd4ade><svg class="tm-svg-img tm-svg-icon" height="24" width="24"><title>Добавить в закладки</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-favorite"></use></svg></span><span class="counter" title="Количество пользователей, добавивших публикацию в закладки" data-v-41bd4ade>0</span></button><!--]--><div class="sharing tm-data-icons__item" title="Поделиться" data-v-20bd092c><button class="sharing-button" type="button" data-v-20bd092c><svg class="tm-svg-img icon" height="16" width="16" data-v-20bd092c><title>Поделиться</title><use xlink:href="/img/megazord-v28.svg?2.347.0#share"></use></svg></button><!--teleport start--><!--teleport end--></div><div class="article-comments-counter-link-wrapper tm-data-icons__item" title="Читать комментарии" data-v-766640b7><a href="/ru/articles/1080784/comments/" class="article-comments-counter-link" data-v-766640b7><!--[--><svg class="tm-svg-img icon" height="24" width="24" data-v-766640b7><title>Комментарии</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-comments"></use></svg><span class="value" data-v-766640b7>0</span><!--]--></a><!----></div><!--[--><!--[--><!--[--><!----><!--]--><!--]--><!--]--><!--teleport start--><!--teleport end--><!----></div></div></div><!--[--><!--]--><div class="tm-article-presenter__footer"><!--[--><!--[--><div class="tm-article-blocks" data-v-c43131eb><!----><!--[--><section class="tm-block tm-block tm-block_spacing-bottom" data-v-c43131eb><!----><!--[--><div class="tm-block__body tm-block__body tm-block__body_variant-balanced"><!--[--><div class="article-author" data-async-called="true" data-v-c43131eb data-v-dad080d4><!--[--><!--]--><div class="user-card article user-card" data-async-called="true" data-v-dad080d4 data-v-38c4b225><div class="header" data-v-38c4b225><div class="header-data" data-v-38c4b225><a href="/ru/users/Goker1235/" class="avatar avatar-40" data-v-38c4b225><!--[--><div class="tm-entity-image" data-v-38c4b225><!--[--><img alt="" class="tm-entity-image__pic" src="https://assets.habr.com/habr-web/release_2.347.0/client/img/avatars/108.png"><!--]--></div><!--]--></a><div class="meta" data-v-38c4b225><div class="tm-counter-container" data-v-38c4b225 data-v-55802e98><div class="tm-counter-container__header"><!--[--><!--[--><!--]--><span class="counter" data-v-55802e98>2K+</span><!--]--></div><div class="tm-counter-container__footer"><!--[--><span class="label" data-v-55802e98>Охват за 30 дней</span><!--]--></div></div><div class="tm-counter-container karma" title=" 1 голос " data-v-38c4b225 data-v-00d8122e><div class="tm-counter-container__header"><!--[--><div class="tm-votes-lever tm-votes-lever tm-votes-lever_appearance-karma votes-switcher" is-natural data-v-00d8122e><!----><div class="tm-votes-lever__score tm-votes-lever__score_appearance-karma tm-votes-lever__score"><!--[--><span><span class="tm-votes-lever__score-counter tm-votes-lever__score-counter_positive tm-votes-lever__score-counter">1</span></span><!--]--></div><!----></div><!----><!--]--></div><div class="tm-counter-container__footer"><!--[--><div class="karma-text" data-v-00d8122e>Карма</div><!--teleport start--><!--teleport end--><!--]--></div></div></div></div></div><div class="info" data-v-38c4b225><div class="title" data-v-38c4b225><!----><a href="/ru/users/Goker1235/" class="nickname" data-v-38c4b225><!--[-->@Goker1235<!--]--></a><!----></div><p class="short-info" data-v-38c4b225>Пользователь</p></div><!----><div class="buttons" data-v-38c4b225><!----><div class="button" data-v-38c4b225><button class="btn btn_transparent btn_small tm-button_color-christi tm-button-follow" tabindex="0" type="button" data-v-38c4b225 data-v-72d4aa13><!--[--><!--[--><!----><span class="button-content" data-v-72d4aa13>Подписаться</span><!--]--><!--]--></button></div><!----><!--[--><div class="button write" data-v-38c4b225><svg class="tm-svg-img icon" height="16" width="16" data-v-38c4b225><title>Отправить сообщение</title><use xlink:href="/img/megazord-v28.svg?2.347.0#mail"></use></svg></div><!----><!----><!--]--><!----></div><!----></div><div class="author-contacts" data-v-dad080d4><!----><!----><!----></div></div><!--]--></div><!--]--><!----></section><!----><!--[--><div class="sponsor-block" style="--v1edb046e:0;--v225a0844:100%;" data-v-c371591c><div class="title" data-v-c371591c>Поток Фронтенд доступен 24/7 благодаря поддержке друзей Хабра</div><div class="content-container" data-v-c371591c><img alt="Хабр Карьера Курсы" class="content-image" loading="lazy" src="https://habrastorage.org/webt/97/0d/9d/970d9d74094c58a7ed44dda9c1d40875.png" data-v-c371591c><div class="content" data-v-c371591c><div class="content-title-container" data-v-c371591c><div class="content-title" data-v-c371591c>Хабр Курсы для фронтендеров</div><div class="sponsor-mark" data-v-c371591c>РЕКЛАМА</div></div><div class="content-text" data-v-c371591c> Большая витрина: от крупнейших школ до частных авторов. Сравнивайте по цене, длительности, формату и выбирайте самый подходящий курс! </div><a class="content-action" href="https://career.habr.com/courses/programmirovanie/frontend-razrabotka/?erid=2VSb5xvfNsT&utm_source=habr&utm_medium=sponsorship_hub" target="_blank" data-v-c371591c><button class="btn btn_solid btn_small tm-button_color-horizon" tabindex="0" type="button" data-v-c371591c><!--[--><!--[-->Перейти<!--]--><!--]--></button></a></div></div><div class="footer" data-v-c371591c><a href="/ru/flows/frontend/" class="link sponsor-link" data-v-c371591c> Перейти в поток Фронтенд</a></div><!----></div><!--]--><!--]--><div class="tm-article-blocks__comments"><div id="publication-comments" class="tm-article-page-comments"><div><!--[--><div class="article-comments-counter-link-wrapper tm-article-comments-counter-button" data-v-766640b7><a href="/ru/articles/1080784/comments/" class="article-comments-counter-link button-style" data-v-766640b7><!--[--><svg class="tm-svg-img icon icon--contrasted" height="24" width="24" data-v-766640b7><title>Комментарии</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-comments"></use></svg><span class="value value--contrasted" data-v-766640b7> Комментировать </span><!--]--></a><!----></div><!--]--></div></div></div><!--[--><div class="tm-pack-preview" data-async-called="true" data-v-c43131eb><div class="preview-card preview-card--compact compact-pack-block" data-v-82b31548 data-v-d969c622><!--[--><div class="pack-header" title="ЕЖЕДНЕВНЫЙ ХАБР | 10 СЕНТ 2026" closable="true" variant="feed" data-v-82b31548 data-v-57d466c5 data-v-4ae019f5><!--[--><a href="/ru/packs/daily/72/" class="title" data-v-57d466c5>ЕЖЕДНЕВНЫЙ ХАБР | 10 СЕНТ 2026</a><!--]--><div class="icons" data-v-4ae019f5><span class="tm-icon-counter reach-counter" data-v-4ae019f5><svg class="tm-svg-img tm-icon-counter__icon" height="24" width="24"><title>Охват</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-views"></use></svg><span class="tm-icon-counter__value" title="83924">84K</span></span><!--[--><!--]--></div><!--[--><button class="close" data-v-57d466c5 data-v-6fea0f18><svg class="tm-svg-img" height="24" width="24" data-v-6fea0f18><title>Закрыть</title><use xlink:href="/img/megazord-v28.svg?2.347.0#close"></use></svg></button><!--]--><!--teleport start--><!--teleport end--></div><!--]--><div class="layout" data-v-d969c622><!--[--><div class="article-content full lead" data-v-82b31548 style="--v697d5c97:cover;--v4ca22c63:0% 0%;" data-v-5b875e18><!--[--><div class="content" data-v-5b875e18><!----><h2 class="tm-title tm-title_h2 title" data-v-5b875e18><!--[--><a href="/ru/articles/1080212/" class="tm-title__link" data-article-link="true"><span>Мощный ИИ агент в 16гб VRAM</span></a><!--]--></h2><div class="description" data-v-5b875e18><p>Недавно я обновил свою видеокарту до RTX-5060-TI 16GB. Получив новую карту я решил исследовать, что можно на ней запустить из нейросетей. Традиционно пишут, что нормальные ИИ модели начинаются с rtx3090 и выше, что ниже 24GB VRAM жизни нет. В этой статье я постараюсь развеять этот миф.</p></div><!----></div><!--[--><img alt="Мощный ИИ агент в 16гб VRAM" class="image" loading="eager" src="https://habrastorage.org/getpro/habr/upload_files/c4b/afa/d5c/c4bafad5cfcfa50026978cb196d32dd3.JPG" data-v-5b875e18><!--]--><!--]--></div><!--]--><div class="divider" data-v-d969c622></div><!--[--><div class="sidebar" data-v-82b31548 data-v-bd359547><!--[--><div class="article-content compact" data-v-bd359547 style="--v697d5c97:cover;--v4ca22c63:0% 0%;" data-v-5b875e18><!--[--><div class="content" data-v-5b875e18><!----><h3 class="tm-title tm-title_h3 title" data-v-5b875e18><!--[--><a href="/ru/articles/1080196/" class="tm-title__link" data-article-link="true"><span>Видит ли MAX ваш VPN при Split Routing, если VPN не установлен на компьютере?</span></a><!--]--></h3><!----><!----></div><!----><!--]--></div><div class="article-content compact" data-v-bd359547 style="--v697d5c97:cover;--v4ca22c63:0% 0%;" data-v-5b875e18><!--[--><div class="content" data-v-5b875e18><!----><h3 class="tm-title tm-title_h3 title" data-v-5b875e18><!--[--><a href="/ru/articles/1080342/" class="tm-title__link" data-article-link="true"><span>AmneziaWG: от 2.0 к 3.1, и что творится у вас под капотом</span></a><!--]--></h3><!----><!----></div><!----><!--]--></div><div class="article-content compact" data-v-bd359547 style="--v697d5c97:cover;--v4ca22c63:0% 0%;" data-v-5b875e18><!--[--><div class="content" data-v-5b875e18><!----><h3 class="tm-title tm-title_h3 title" data-v-5b875e18><!--[--><a href="/ru/articles/1079984/" class="tm-title__link" data-article-link="true"><span>Как защитить веб‑приложения с помощью Coraza (ModSecurity)</span></a><!--]--></h3><!----><!----></div><!----><!--]--></div><!--]--><div class="buttons" data-v-bd359547><!--[--><!--[--><button class="bookmarks-button" title="Добавить в закладки" type="button" data-v-31ce2e7f data-v-41bd4ade><span class="tm-svg-icon__wrapper icon" data-v-41bd4ade><svg class="tm-svg-img tm-svg-icon" height="24" width="24"><title>Добавить в закладки</title><use xlink:href="/img/megazord-v28.svg?2.347.0#counter-favorite"></use></svg></span><span class="counter" title="Количество пользователей, добавивших публикацию в закладки" data-v-41bd4ade>7</span></button><!----><!--]--><!--]--><a href="/ru/packs/daily/72/" class="btn btn_transparent btn_small tm-button_color-horizon footer-button" tabindex="0" data-v-bd359547><!--[--><!--[-->Ещё<!--]--><!--]--></a></div></div><!--]--></div></div></div><section class="tm-block tm-block tm-block_spacing-bottom" data-v-c43131eb><header class="tm-block__header tm-block__header tm-block__header_variant-borderless"><div class="tm-block__header-container"><h2 class="tm-block__title tm-block__title tm-block__title_variant-large">Публикации</h2><!--[--><!--]--></div><!----></header><!--[--><div class="tm-block__body tm-block__body tm-block__body_variant-condensed-slim"><!--[--><!--[--><div class="tabs" data-v-357c3e77><!--[--><!--]--><div class="" data-v-357c3e77><!--[--><span class="tab-item" data-v-357c3e77><button class="active slim tab-link" data-v-357c3e77>Лучшие за сутки</button></span><span class="tab-item" data-v-357c3e77><button class="slim tab-link" data-v-357c3e77>Похожие</button></span><!--]--></div><!----></div><div class="similar-and-daily__tab-view"><div class="daily-articles-list"><ul class="article-card-list" data-v-5815b5ed><!--[--><!--]--><div class="tm-bordered-card" data-v-5815b5ed><!----><!--[--><!--]--></div></ul><div class="daily-articles-block__button-container"><button class="btn btn_transparent btn_small tm-button_color-horizon" tabindex="0" type="button"><!--[--><!--[-->Показать лучшие за всё время<!--]--><!--]--></button></div></div><!----></div><!--]--><!--]--></div><!--]--><!----></section><!--[--><div><div class="placeholder-wrapper"><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><div class="tm-placeholder-promo"><div class="tm-placeholder-promo__header"><div class="tm-placeholder__line tm-placeholder__line_promo-title"></div></div><div class="tm-placeholder-promo__body"><div class="tm-placeholder-promo__posts"><div class="tm-placeholder-promo__post"><div class="tm-placeholder-promo__image"></div><div class="tm-placeholder__line tm-placeholder__line_post-title"></div></div><div class="tm-placeholder-promo__post"><div class="tm-placeholder-promo__image"></div><div class="tm-placeholder__line tm-placeholder__line_post-title"></div></div><div class="tm-placeholder-promo__post"><div class="tm-placeholder-promo__image"></div><div class="tm-placeholder__line tm-placeholder__line_post-title"></div></div></div><div class="tm-placeholder-promo__dots"><div class="tm-placeholder-promo__dot"></div><div class="tm-placeholder-promo__dot"></div><div class="tm-placeholder-promo__dot"></div></div></div></div><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----></div></div><div><div class="placeholder-wrapper"><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><div class="tm-placeholder-inset tm-placeholder-salary"><div class="tm-placeholder-inset__header"><div class="tm-placeholder__line tm-placeholder__line_inset-header loads"></div></div><div class="tm-placeholder-salary__body"><div class="tm-placeholder-salary__text"><div class="tm-placeholder__line tm-placeholder__line_salary_average"></div><div class="tm-placeholder-salary__description"><div class="tm-placeholder__line loads"></div><div class="tm-placeholder__line loads"></div><div class="tm-placeholder__line loads"></div><div class="tm-placeholder__line loads"></div></div></div><div class="tm-placeholder-salary__image loads"></div></div><div class="tm-placeholder-inset__footer tm-placeholder-inset__footer_salary"><div class="tm-placeholder__line tm-placeholder__line_inset-footer loads"></div></div></div><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----></div></div><!--]--><!----><!--[--><!--]--><!--]--></div><!----><!--]--><!--]--></div></div><!--]--></div><!--]--></div></div><div class="tm-page__sidebar"><!--[--><!----><!--]--></div></div><!--]--></div><!----></div></main><!----></div><div class="tm-footer-menu"><div class="tm-page-width"><!--[--><div class="tm-footer-menu__container"><!--[--><div class="tm-footer-menu__block"><p class="tm-footer-menu__block-title">Ваш аккаунт</p><div class="tm-footer-menu__block-content"><ul class="tm-footer-menu__list"><!--[--><li class="tm-footer-menu__list-item"><a href="/kek/v1/auth/habrahabr/?back=/ru/articles/1080784/&hl=ru" rel="nofollow" target="_self">Войти</a></li><li class="tm-footer-menu__list-item"><a href="/kek/v1/auth/habrahabr-register/?back=/ru/articles/1080784/&hl=ru" rel="nofollow" target="_self">Регистрация</a></li><!--]--></ul></div></div><div class="tm-footer-menu__block"><p class="tm-footer-menu__block-title">Разделы</p><div class="tm-footer-menu__block-content"><ul class="tm-footer-menu__list"><!--[--><li class="tm-footer-menu__list-item"><a href="/ru/articles/" class="footer-menu__item-link">Статьи</a></li><li class="tm-footer-menu__list-item"><a href="/ru/news/" class="footer-menu__item-link">Новости</a></li><li class="tm-footer-menu__list-item"><a href="/ru/hubs/" class="footer-menu__item-link">Хабы</a></li><li class="tm-footer-menu__list-item"><a href="/ru/companies/" class="footer-menu__item-link">Компании</a></li><li class="tm-footer-menu__list-item"><a href="/ru/users/" class="footer-menu__item-link">Авторы</a></li><li class="tm-footer-menu__list-item"><a href="/ru/sandbox/" class="footer-menu__item-link">Песочница</a></li><!--]--></ul></div></div><div class="tm-footer-menu__block"><p class="tm-footer-menu__block-title">Информация</p><div class="tm-footer-menu__block-content"><ul class="tm-footer-menu__list"><!--[--><li class="tm-footer-menu__list-item"><a href="/ru/docs/help/" class="footer-menu__item-link">Устройство сайта</a></li><li class="tm-footer-menu__list-item"><a href="/ru/docs/authors/codex/" class="footer-menu__item-link">Для авторов</a></li><li class="tm-footer-menu__list-item"><a href="/ru/docs/companies/corpblogs/" class="footer-menu__item-link">Для компаний</a></li><li class="tm-footer-menu__list-item"><a href="/ru/docs/docs/transparency/" class="footer-menu__item-link">Документы</a></li><li class="tm-footer-menu__list-item"><a href="https://account.habr.com/info/agreement/?hl=ru_RU" target="_blank">Соглашение</a></li><li class="tm-footer-menu__list-item"><a href="https://account.habr.com/info/confidential/?hl=ru_RU" target="_blank">Конфиденциальность</a></li><!--]--></ul></div></div><div class="tm-footer-menu__block"><p class="tm-footer-menu__block-title">Услуги</p><div class="tm-footer-menu__block-content"><ul class="tm-footer-menu__list"><!--[--><li class="tm-footer-menu__list-item"><a href="https://company.habr.com/ru/corporate-blogs/" target="_blank">Корпоративный блог</a></li><li class="tm-footer-menu__list-item"><a href="https://company.habr.com/ru/advertising/" target="_blank">Медийная реклама</a></li><li class="tm-footer-menu__list-item"><a href="https://company.habr.com/ru/native-special/" target="_blank">Нативные проекты</a></li><li class="tm-footer-menu__list-item"><a href="https://company.habr.com/ru/education-programs/" target="_blank">Образовательные программы</a></li><li class="tm-footer-menu__list-item"><a href="https://company.habr.com/ru/hello-startup/" target="_blank">Стартапам</a></li><!--]--></ul></div></div><!--]--></div><!--]--></div></div><div class="tm-footer"><div class="tm-page-width"><!--[--><div class="tm-footer__container"><!----><div class="social-icons tm-footer__social" data-v-99bbd584><!--[--><a class="tm-svg-icon__wrapper social-icon" href="https://vk.com/habr" rel="nofollow noopener noreferrer" target="_blank" data-v-99bbd584><svg class="tm-svg-img tm-svg-icon" height="36" width="36"><title>VK</title><use xlink:href="/img/new-social-icons-sprite.svg#social-logo-vk"></use></svg></a><a class="tm-svg-icon__wrapper social-icon" href="https://telegram.me/habr_com" rel="nofollow noopener noreferrer" target="_blank" data-v-99bbd584><svg class="tm-svg-img tm-svg-icon" height="36" width="36"><title>Telegram</title><use xlink:href="/img/new-social-icons-sprite.svg#social-logo-telegram"></use></svg></a><a class="tm-svg-icon__wrapper social-icon" href="http://www.youtube.com/@Habr_com" rel="nofollow noopener noreferrer" target="_blank" data-v-99bbd584><svg class="tm-svg-img tm-svg-icon" height="36" width="36"><title>Youtube</title><use xlink:href="/img/new-social-icons-sprite.svg#social-logo-youtube"></use></svg></a><a class="tm-svg-icon__wrapper social-icon" href="https://dzen.ru/habr" rel="nofollow noopener noreferrer" target="_blank" data-v-99bbd584><svg class="tm-svg-img tm-svg-icon" height="36" width="36"><title>Яндекс Дзен</title><use xlink:href="/img/new-social-icons-sprite.svg#social-logo-dzen"></use></svg></a><!--]--></div><!--teleport start--><!--teleport end--><button class="tm-footer__link"><!----> Настройка языка</button><a href="/ru/feedback/" class="tm-footer__link">Техническая поддержка</a><span class="tm-footer__notice">На информационном ресурсе применяются <a href="/docs/docs/recommendation-rules/">рекомендательные технологии</a></span><div class="tm-footer-copyright"><span class="tm-copyright"><span class="tm-copyright__years">© 2006–2026,</span><span class="tm-copyright__name"><a class="tm-copyright__link" href="https://company.habr.com/" rel="noopener" target="_blank"> Habr </a></span></span></div></div><!--]--></div></div><!----><!----><!----><!--]--></div><!----></div></div>
<div id="overlays"><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--><!--teleport start anchor--><!----><!--teleport anchor--></div>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-S28W1WC23F"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
</script>
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
</script>
<script>
var _paq = window._paq = window._paq || [];
(function() {
var u="https://habr.com/mt/";
var tu = u + 'e';
var ts = u + 't.js';
_paq.push(['setTrackerUrl', tu]);
_paq.push(['setSiteId', '7']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=ts; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img referrerpolicy="no-referrer-when-downgrade" src="https://habr.com/mt/e?idsite=7&rec=1" style="border:0;" alt="" /></p></noscript>
</body>
</html>