Why Web Components break when embedded into someone else's interface
A component that looks flawless in an isolated showcase lands in an existing interface and falls apart: headings overlap, the button loses its padding, the modal goes under someone else's layer.
Shadow DOM isolates rules but does not cancel inheritance: global resets and the host's typography reach slotted content, and the application's theme leaks inside through CSS variables.
A component's style isolation turns out to be an agreement, not a guarantee. Rename a token in the host — the component silently changes color; change how the font is loaded — the grid breaks.
Such style conflicts are almost invisible on a demo page: they surface on real data, in dark theme, and on long strings.
Then state and build come into play. Frameworks pass data in different ways — as a string attribute, a property, or a reactive update — and some changes inside the component remain invisible to the host.
An event from a nested node arrives as the component's own event, and handlers fire where they are not expected. Two versions of the same set in the build add extra weight and a dispute over whose tag registered first.
A separate topic is logic duplication: form validation, localization, date formatting, and theming spread between the application and components because they have no shared contract.
Below we break down how this is closed at the level of tokens, boundaries, and checks during embedding.
What a single component set across different stacks provides
A component is described once according to the Custom Elements web standard and is connected as a regular tag in React, Vue, and Angular — without wrappers for each framework.
Inside works a shadow DOM: styles and markup are isolated, so a rule from the design system is not overridden by the application's cascade.
On top of the standard, it is convenient to take Lit: declarative templates and reactive properties eliminate manual work with the element's lifecycle.
A single source of truth changes the economics of edits. Previously, a button, field, or table lived in three repositories and diverged at the first refinement; now a change is made once and arrives in all interfaces in a single release.
Duplicate code is reduced by a third or more, and the edit itself ships the same day, not as three parallel tasks.
The interaction contract also becomes shared: properties and events are described once, rather than rewritten for each framework's props. Behavior checks are written against the standard and are not duplicated across three platforms — one test suite covers all applications.
Hence predictability: the same component state looks and behaves the same in the admin panel, in the customer account, and on the landing page.
The cost of maintaining interfaces falls not through cuts, but through eliminating discrepancies: fewer code branches, fewer "quirks" of a specific framework, one procedure for updating dependencies. We discuss how such a layer is embedded into existing applications without rewriting.
Packaging and delivery methods for components: comparison
The delivery method determines how much work falls on teams with each update of the component set.
The same code can be delivered as a ready-made package, as a generated wrapper for a specific stack, or as source code in a shared repository — and the maintenance cost differs in these cases.
Below are the options that are actually chosen between, with limitations that show up not in a demo, but by the third or fourth release.
| Option | When it fits | Limitations |
|---|---|---|
| Ready-made component package | A single set across multiple applications, centralized updates | Themes are defined via custom properties; server-side rendering requires separate support |
| Wrapper for a stack | Teams want familiar syntax, typing, and autocomplete | The wrapper is rebuilt for every major version; some events and slots are passed through manually |
| Direct bundle connection to the page | Embedding into pages on an old stack, content systems, landing pages | Versions across pages diverge, there is no dependency control — compatibility is maintained manually |
| Source code in a shared repository | Forks and custom edits are needed | Updates do not arrive on their own; versioning and regression are on the team's side |
How we maintain backward compatibility
We tie component versioning to the contract, not to internals: properties, events, slots. The major changes only when an attribute is removed or its meaning changes; everything else goes out as a minor.
Old names continue to work for one major cycle, so updating the library in one service does not require a synchronized release of neighboring ones.
How we implement a component library: stages of work
Implementing a web components library starts not with code, but with an inventory of production interfaces. Until there is a map of existing elements and discrepancies between them, a timeline estimate is worthless. We follow a fixed sequence — from audit to handover to the team.
- Interface audit. We compile a list of screens and break down existing elements by frequency of use, discrepancies in markup, states, and behavior.
- Component scheme. We fix the boundaries of each element: what goes into the shared set, what remains local, which properties and events become the public interface.
- Prototype at the intersection of frameworks. We assemble one or two components and connect them in React, Vue, and Angular — we check that events, slots, and styles behave predictably in each.
- Library implementation. We write the agreed-upon set, cover states, accessibility, and theming, and set up versioning and publication of the internal package.
- Screen migration. We replace old implementations in stages, one route per release, with the ability to roll back to the previous version of the interface.
- Acceptance of the result. We run behavioral and visual checks, compare against mockups, and record the share of covered scenarios and the list of deliberate exceptions.
- Handover to the team. We hand over the repository, documentation, release and dependency update rules, and walk through the design on live code.
Each stage has a tangible output: after the audit — a map of elements; after the prototype — a component that works the same in all frameworks; after handover — a team that develops the library on its own.
What remains in the project: repository, schemas, regulations
We hand over the project so that the client's team can develop it without our involvement. Handover is not an archive with a build, but a set of artifacts, each answering its own question: what to change, where the boundaries are, and how to verify the result. Below is the composition of the package that goes to the client's side.
- Component source code. A repository with packages: the web components themselves, shared styles and theme tokens, thin wrappers for consuming frameworks. The build is reproduced with a single command on a clean machine.
- Library documentation. A public description of each component's API: properties, events, slots, loading and error states. Plus usage examples and a showcase with all design variants.
- Dependency diagram. A graph of "what depends on what": where external packages are, which versions are compatible with each other, and what will happen when they are updated upward.
- Update regulations. Rules for releasing versions: what counts as a breaking change, how a minor is raised, how consumers learn about a new version and how soon they migrate.
- Change contribution rules. Agreement on branches and task naming, mandatory checks before merge, requirements for code review and tests for new logic.
- Set of checks. Component regression, visual rendering comparisons, verification of contract compliance between a component and its wrappers, and running all of this on every change.
- Launch and release instructions. A step-by-step document: bring up the environment, build, debug, ship a release. Written for a new engineer, not for the author of the code.
Such a package closes the main risk of handover: the client sees where our work ends and their area of responsibility begins. The team makes its first independent edit with us present; by the second, we are no longer needed.
Case study: how interface duplication was removed across four products
Four products of one company — customer account, admin panel, public portal, and mobile web version — were developed by different teams and over time diverged in details.
Key interface blocks — tables with sorting and pagination, filters, forms, date picker, file upload — existed in 9–14 copies each.
Starting point
The copies lived their own lives: in one table keyboard navigation worked, in another it did not; a filter in the operator panel reset differently than in the customer account; and field validation differed in its rules.
Fixing one behavior required synchronized work in four repositories, and the discrepancy surfaced already at acceptance — the share of interface defects found after release held at around one third.
What we did
The shared blocks were consolidated into a single layer on Web Components: a component is assembled and tested once, and in React, Vue, and Angular it is connected as a regular element — without copies of markup, styles, or validation logic.
Styles, behavior, and test suites live in one repository with one version and one changelog, so one product can no longer lag behind the others in the behavior of a block.
| Metric | Before | After |
|---|---|---|
| Copies of one block | 9–14 | 1 |
| Repositories per edit | 4 | 1 |
| Test suite per block | its own in each product | shared |
| Interface defects after release | around one third | isolated ones |
The transition was gradual: new screens were immediately assembled from the shared layer, old copies were replaced as planned improvements were made, so there was no need to rewrite the products entirely.
The time to refine a typical block decreased severalfold, and rolling back to the previous version of a component is now done with one operation for all products at once.
How Web Components differ from a regular interface library
An interface library is an agreement within a single stack: components live in the rendering model of a specific framework and are assembled with its tools. While there is one product and one stack, the question of portability does not arise.
The boundary becomes visible when the same component is needed in another application or in someone else's framework.
Web Components move the agreements to the web platform level. A custom element is a regular tag in markup; its internal structure is closed by a shadow tree: external styles do not leak inside, and internal ones do not affect the page. Markup isolation is provided by the browser, not by conventions and linters.
Hence the difference in stack dependency. A library pulls the framework's runtime with it; its component cannot be inserted into someone else's application without rebuilding. A custom element is connected by import and works in any environment where there is a browser DOM.
The choice of approach is not about "modernity," but about where the boundary between products lies. If it is within one team and one stack, a library is enough. If components cross application boundaries, it is wiser to implement isolation at the platform level.
Common technical concerns before starting a project
Concerns before starting are not about the standard itself — it is stable and documented — but about the interfaces: how to embed elements into a live project, who will maintain them, what to do with old browsers, and how to ship updates without breaking neighboring modules. Below are engineering answers to what is most often asked in technical sessions.
Can it be implemented in a working project without rewriting?
Yes. Components are connected as regular tags and do not require changing the build: React, Vue, and Angular continue to work as before, while shared elements live in one place. We start with one block — a header or a payment widget — and move the rest as needed. There is no need to rewrite the entire interface at once.
Will we be able to maintain this on our own?
Yes, if we hand over the code, checks, and documentation on element contracts: which attributes a component accepts, which events it emits, and what is considered a breaking change. No knowledge beyond the browser platform is required.
We conduct the handover on live code so that your developer can start making edits independently right away.
What about old browsers and embedded views?
The standard's key capabilities are supported in all current browsers, including mobile ones. For outdated versions, we add a compatibility layer and design graceful degradation: if an element did not load, the user sees meaningful markup rather than an empty space. We test on real devices, not only in an emulator.
How to update components without breaking other modules?
Each element is versioned separately and published as an independent package, so updating one block does not drag the others along. We fix the contract — attributes, events, slots — and change it only in a major version, and before release we run compatibility checks. Neighboring teams move to the new version when they are ready.
Let's discuss the task and come back with an estimate and work plan
To come back with an estimate of the scope of work and an implementation plan, we do not need a full technical breakdown — a few inputs are enough; engineers will assemble the rest themselves on a call with your team.
The more precise the inputs, the fewer assumptions in the timelines of the stages and team composition.
What is useful to attach to the task description:
- A list of frameworks and their current branches in your applications — this determines how many adapters and wrappers will be included in the implementation plan.
- A list of interface blocks that should become shared: forms, tables, filters, notifications, widgets.
- A link to the repository or an export of the project structure — this way we can assess the build contour and integration points without guesswork.
- Theming requirements: light and dark themes, branding, accessibility, and keyboard navigation.
- Browser and environment constraints, including outdated versions that are still in production.
- Your team composition and areas of responsibility: who approves the architecture, who reviews changes, who is responsible for the release.
- Expected stage timelines and hard dates — tying to a release or migration changes the order of work.
After a short call, we fix the estimate of the scope of work, team composition, and stage timelines. We break the implementation plan into independent steps so that the first shared web components library can be connected without waiting for all applications to be migrated.
Describe the task — we will come back with an estimate and a plan.







