Реализация Shadow DOM для инкапсуляции стилей компонентов

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Shadow DOM для инкапсуляции стилей компонентов
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Shadow DOM для инкапсуляции стилей компонентов

Shadow DOM — механизм браузера для создания изолированного поддерева DOM. Стили снаружи не проникают внутрь, стили изнутри не вытекают наружу. Это позволяет создавать компоненты, которые работают одинаково в любом CSS-окружении.

Как работает Shadow DOM

Document (Light DOM)
├── <header>
├── <main>
│   └── <my-widget>      ← Custom Element
│       └── #shadow-root  ← Shadow Root (граница инкапсуляции)
│           ├── <style>   ← стили видны только здесь
│           └── <div class="widget">
│               └── <slot> ← проекция Light DOM контента
└── <footer>

Глобальный CSS div { color: red } не влияет на div внутри shadow-root. CSS из shadow-root не влияет на div снаружи.

Открытый и закрытый режимы

// mode: 'open' — доступ к shadowRoot извне (el.shadowRoot !== null)
const openShadow = element.attachShadow({ mode: 'open' })

// mode: 'closed' — shadowRoot === null снаружи
// Используется для максимальной инкапсуляции (нативные браузерные элементы)
const closedShadow = element.attachShadow({ mode: 'closed' })

В подавляющем большинстве кейсов используется mode: 'open' — инструменты разработчика, тестирующие утилиты и библиотеки управления формами (react-hook-form, формы браузера) требуют доступ к shadowRoot.

Стилизация изнутри

class StyledCard extends HTMLElement {
  constructor() {
    super()
    const shadow = this.attachShadow({ mode: 'open' })

    shadow.innerHTML = `
      <style>
        /* :host — сам элемент <styled-card> */
        :host {
          display: block;
          border-radius: 12px;
          overflow: hidden;
        }

        /* :host() с условием */
        :host([variant="dark"]) {
          background: #1a1a1a;
          color: #fff;
        }

        :host([variant="light"]) {
          background: #fff;
          color: #111;
          box-shadow: 0 2px 20px rgba(0,0,0,0.08);
        }

        /* :host-context() — реакция на окружение */
        :host-context(.dark-theme) {
          background: #222;
        }

        .card-body {
          padding: 24px;
        }

        .card-title {
          font-size: 20px;
          font-weight: 700;
          margin: 0 0 12px;
        }

        /* ::slotted — стилизация спроецированного контента */
        ::slotted(p) {
          margin: 0;
          line-height: 1.6;
          opacity: 0.7;
        }

        ::slotted([slot="footer"]) {
          margin-top: 20px;
          padding-top: 16px;
          border-top: 1px solid currentColor;
          opacity: 0.2;
        }
      </style>

      <div class="card-body">
        <h3 class="card-title">
          <slot name="title">Заголовок</slot>
        </h3>
        <slot></slot>
        <slot name="footer"></slot>
      </div>
    `
  }
}

CSS Custom Properties: мост через Shadow DOM

Единственный способ передать стили снаружи внутрь — через CSS-переменные. Они проникают сквозь Shadow DOM boundary.

/* Снаружи: задаём переменные */
styled-card {
  --card-bg: #f8f9fa;
  --card-radius: 16px;
  --card-padding: 32px;
  --card-title-color: #1a1a1a;
}
/* Внутри Shadow DOM: используем переменные с fallback */
:host {
  background: var(--card-bg, #fff);
  border-radius: var(--card-radius, 8px);
}

.card-body {
  padding: var(--card-padding, 24px);
}

.card-title {
  color: var(--card-title-color, inherit);
}
<!-- Разные экземпляры с разными стилями -->
<styled-card style="--card-bg: #1a0050; --card-title-color: #fff">
  <span slot="title">Тёмная карточка</span>
  <p>Контент</p>
</styled-card>

<styled-card style="--card-bg: #e8f5e9; --card-padding: 40px">
  <span slot="title">Зелёная карточка</span>
  <p>Контент</p>
</styled-card>

CSS Parts: точечная стилизация снаружи

part атрибут открывает конкретные элементы для внешних стилей через ::part():

shadow.innerHTML = `
  <div class="wrapper" part="wrapper">
    <button class="btn" part="button trigger">
      <slot></slot>
    </button>
    <div class="dropdown" part="dropdown">
      <slot name="items"></slot>
    </div>
  </div>
`
/* Снаружи — стилизуем только открытые части */
my-dropdown::part(button) {
  background: #7000ff;
  color: #fff;
  border-radius: 8px;
}

my-dropdown::part(dropdown) {
  background: #1a1a1a;
  border: 1px solid #333;
}

/* hover на часть */
my-dropdown::part(button):hover {
  background: #5500cc;
}

Adoptable Stylesheets (CSSStyleSheet API)

Для переиспользования стилей между несколькими Shadow DOM без дублирования строк:

// Создаём разделяемый stylesheet один раз
const sharedStyles = new CSSStyleSheet()
sharedStyles.replaceSync(`
  :host { box-sizing: border-box; }
  *, *::before, *::after { box-sizing: inherit; }

  button {
    cursor: pointer;
    border: none;
    font: inherit;
  }
`)

const typographyStyles = new CSSStyleSheet()
typographyStyles.replaceSync(`
  h1, h2, h3 { font-weight: 700; line-height: 1.2; margin: 0; }
  p { line-height: 1.6; margin: 0 0 1em; }
`)

// Применяем в компонентах
class ComponentA extends HTMLElement {
  constructor() {
    super()
    const shadow = this.attachShadow({ mode: 'open' })
    // Adoptable stylesheets — без дублирования CSS
    shadow.adoptedStyleSheets = [sharedStyles, typographyStyles]

    // Только компонент-специфичные стили
    const localStyle = new CSSStyleSheet()
    localStyle.replaceSync(`.wrapper { background: blue; }`)
    shadow.adoptedStyleSheets.push(localStyle)
  }
}

class ComponentB extends HTMLElement {
  constructor() {
    super()
    const shadow = this.attachShadow({ mode: 'open' })
    shadow.adoptedStyleSheets = [sharedStyles]  // только базовые стили
  }
}

Shadow DOM и формы

Нативные элементы форм внутри Shadow DOM не участвуют в form.elements и не попадают в FormData. Решение через ElementInternals:

class CustomInput extends HTMLElement {
  static get formAssociated() { return true }

  private internals: ElementInternals
  private shadow: ShadowRoot

  constructor() {
    super()
    // formAssociated + ElementInternals позволяют участвовать в форме
    this.internals = this.attachInternals()
    this.shadow = this.attachShadow({ mode: 'open' })
  }

  connectedCallback() {
    this.shadow.innerHTML = `
      <style>
        input {
          width: 100%;
          padding: 10px 14px;
          border: 1px solid var(--border-color, #d0d5dd);
          border-radius: 8px;
          font: inherit;
          outline: none;
        }
        input:focus {
          border-color: var(--focus-color, #7000ff);
          box-shadow: 0 0 0 3px var(--focus-ring, rgba(112, 0, 255, 0.15));
        }
      </style>
      <input type="text" />
    `

    const input = this.shadow.querySelector('input')!
    input.addEventListener('input', () => {
      // Синхронизируем значение с формой
      this.internals.setFormValue(input.value)
      this.internals.setValidity(
        input.validity,
        input.validationMessage,
        input
      )
    })
  }

  // Браузер вызывает при reset формы
  formResetCallback() {
    const input = this.shadow.querySelector('input')
    if (input) input.value = ''
    this.internals.setFormValue('')
  }
}

customElements.define('custom-input', CustomInput)

Сроки

Один компонент с Shadow DOM, Custom Properties и Parts — 1 день. Система из 5–8 компонентов с Adoptable Stylesheets, ElementInternals для форм и документацией — 1–2 недели.