Разработка библиотеки Web Components (Shoelace)
Shoelace (теперь Web Awesome) — готовая библиотека UI-компонентов на Web Components. Около 50 компонентов: кнопки, инпуты, селекты, диалоги, тултипы, деревья, date picker. Работает без фреймворка и с любым: React, Vue, Angular, Svelte.
Задача при работе со Shoelace — не написать библиотеку с нуля, а правильно настроить, кастомизировать под дизайн-систему и, при необходимости, расширить кастомными компонентами.
Установка
npm install @shoelace-style/shoelace
Или CDN (без сборщика):
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/themes/light.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/shoelace-autoloader.js"></script>
Cherry-pick импорт (tree-shaking)
Не импортировать всю библиотеку — только нужные компоненты:
// Регистрация конкретных компонентов
import '@shoelace-style/shoelace/dist/components/button/button.js'
import '@shoelace-style/shoelace/dist/components/input/input.js'
import '@shoelace-style/shoelace/dist/components/dialog/dialog.js'
import '@shoelace-style/shoelace/dist/components/select/select.js'
// Настройка пути к иконкам и ассетам
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js'
setBasePath('/shoelace') // копируем ассеты в public/shoelace
// vite.config.ts — копирование ассетов при сборке
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [{
src: 'node_modules/@shoelace-style/shoelace/dist/assets',
dest: 'shoelace/assets',
}],
}),
],
})
Кастомизация через CSS Custom Properties
Shoelace строится на дизайн-токенах. Переопределение через CSS:
/* Глобальный переопределение токенов */
:root {
/* Основной цвет */
--sl-color-primary-50: hsl(262 100% 97%);
--sl-color-primary-100: hsl(262 95% 92%);
--sl-color-primary-200: hsl(262 90% 85%);
--sl-color-primary-300: hsl(262 85% 75%);
--sl-color-primary-400: hsl(262 80% 65%);
--sl-color-primary-500: hsl(262 75% 55%);
--sl-color-primary-600: hsl(262 75% 45%); /* ← основной */
--sl-color-primary-700: hsl(262 75% 38%);
--sl-color-primary-800: hsl(262 75% 30%);
--sl-color-primary-900: hsl(262 75% 20%);
--sl-color-primary-950: hsl(262 75% 12%);
/* Типографика */
--sl-font-sans: 'Inter', system-ui, sans-serif;
--sl-font-mono: 'JetBrains Mono', monospace;
--sl-font-size-medium: 0.9375rem; /* 15px */
/* Радиусы */
--sl-border-radius-small: 4px;
--sl-border-radius-medium: 8px;
--sl-border-radius-large: 12px;
/* Переходы */
--sl-transition-medium: 200ms ease;
}
CSS Parts: точечная кастомизация
Каждый Shoelace-компонент экспортирует ::part() для внешней стилизации:
/* Кастомизация кнопки */
sl-button::part(base) {
font-weight: 700;
letter-spacing: 0.02em;
text-transform: uppercase;
font-size: 13px;
}
sl-button[variant="primary"]::part(base) {
background: linear-gradient(135deg, #7000ff, #b600ff);
border: none;
}
sl-button[variant="primary"]::part(base):hover {
background: linear-gradient(135deg, #5500cc, #9400cc);
}
/* Инпут */
sl-input::part(base) {
border-radius: 10px;
border-width: 1.5px;
}
sl-input::part(input) {
font-size: 15px;
}
/* Диалог */
sl-dialog::part(panel) {
border-radius: 20px;
box-shadow: 0 25px 60px rgba(0,0,0,0.3);
}
sl-dialog::part(overlay) {
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
}
Интеграция с React
npm install @shoelace-style/shoelace
// src/shoelace-setup.ts — запустить один раз
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js'
import '@shoelace-style/shoelace/dist/components/button/button.js'
import '@shoelace-style/shoelace/dist/components/input/input.js'
import '@shoelace-style/shoelace/dist/components/dialog/dialog.js'
import '@shoelace-style/shoelace/dist/themes/light.css'
setBasePath('/shoelace')
// Типы для TypeScript + JSX
// src/types/shoelace.d.ts
import type { SlButton, SlInput, SlDialog } from '@shoelace-style/shoelace'
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'sl-button': React.DetailedHTMLProps<
React.HTMLAttributes<SlButton> & {
variant?: 'default' | 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text'
size?: 'small' | 'medium' | 'large'
disabled?: boolean
loading?: boolean
outline?: boolean
pill?: boolean
circle?: boolean
type?: 'button' | 'submit' | 'reset'
href?: string
target?: string
},
SlButton
>
'sl-input': React.DetailedHTMLProps<
React.HTMLAttributes<SlInput> & {
type?: string
label?: string
placeholder?: string
value?: string
required?: boolean
disabled?: boolean
readonly?: boolean
'help-text'?: string
'error-message'?: string
clearable?: boolean
'password-toggle'?: boolean
},
SlInput
>
'sl-dialog': React.DetailedHTMLProps<
React.HTMLAttributes<SlDialog> & {
label?: string
open?: boolean
'no-header'?: boolean
},
SlDialog
>
}
}
}
import { useRef } from 'react'
import type SlDialog from '@shoelace-style/shoelace/dist/components/dialog/dialog.js'
import '../shoelace-setup'
export function ContactModal() {
const dialogRef = useRef<SlDialog>(null)
const openDialog = () => dialogRef.current?.show()
const closeDialog = () => dialogRef.current?.hide()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
// ...
closeDialog()
}
return (
<>
<sl-button variant="primary" onClick={openDialog}>
Написать нам
</sl-button>
<sl-dialog ref={dialogRef} label="Контактная форма">
<form onSubmit={handleSubmit}>
<sl-input
label="Имя"
required
placeholder="Иван Иванов"
style={{ marginBottom: '16px' }}
/>
<sl-input
label="Email"
type="email"
required
placeholder="[email protected]"
style={{ marginBottom: '16px' }}
/>
<sl-textarea
label="Сообщение"
rows={4}
required
/>
</form>
<sl-button slot="footer" variant="default" onClick={closeDialog}>
Отмена
</sl-button>
<sl-button slot="footer" variant="primary" type="submit">
Отправить
</sl-button>
</sl-dialog>
</>
)
}
Тёмная тема
Shoelace поддерживает dark/light через CSS-класс на <html>:
<!-- Light -->
<html class="sl-theme-light">
<!-- Dark -->
<html class="sl-theme-dark">
// Переключение темы
function toggleTheme() {
const html = document.documentElement
const isDark = html.classList.contains('sl-theme-dark')
html.classList.toggle('sl-theme-dark', !isDark)
html.classList.toggle('sl-theme-light', isDark)
localStorage.setItem('theme', isDark ? 'light' : 'dark')
}
// Инициализация по prefers-color-scheme
const savedTheme = localStorage.getItem('theme')
|| (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.classList.add(`sl-theme-${savedTheme}`)
Создание кастомных компонентов в стиле Shoelace
Если библиотека расширяется собственными компонентами, наследуем от ShoelaceElement:
import ShoelaceElement from '@shoelace-style/shoelace/dist/internal/shoelace-element.js'
import { customElement, property } from 'lit/decorators.js'
import { html, css } from 'lit'
@customElement('app-stat-card')
export class AppStatCard extends ShoelaceElement {
static styles = css`
:host { display: block; }
/* ... */
`
@property() label = ''
@property({ type: Number }) value = 0
@property() unit = ''
render() {
return html`
<div class="stat-card">
<div class="stat-value">${this.value}<span>${this.unit}</span></div>
<div class="stat-label">${this.label}</div>
</div>
`
}
}
Сроки
Настройка Shoelace + кастомизация под дизайн-систему + React-интеграция — 2–4 дня. Дополнительно кастомные компоненты на базе Shoelace/Lit + dark theme + документация — ещё 1–2 недели.







