What Is a Web Component: Your Guide to Reusable UI
What is a web component - Discover what a web component is. Learn how these reusable, encapsulated UI elements work and how to apply them effectively in your

If you build front-end systems long enough, you eventually hit the same wall. Your team has a solid component library in React or Vue, a second app appears with a different stack, and suddenly your “reusable” components aren’t reusable at all. They’re tied to one renderer, one state model, and one framework’s lifecycle rules.
That’s where the question what is a web component stops being academic. It becomes practical. A web component gives you a browser-native way to define custom UI elements that work like real HTML tags. Instead of relying on React or Vue to decide when something mounts or unmounts, the browser itself handles the component lifecycle. You register an element, drop it into markup, and the platform knows what to do with it.
Web components are far from a niche experiment. Google’s 2019 web.dev article estimated that between 5% and 8% of all page loads already used one or more web components, indicating their mainstream production use as a cross-framework interoperability layer (web.dev on web components).

Table of Contents
- Introduction Beyond Framework Lock-In
- The Three Pillars of Web Components
- Web Components vs Framework Components
- Real World Benefits and Usage Patterns
- Headless Components and Modern Integration
- Common Pitfalls and Getting Started
Introduction Beyond Framework Lock-In
The best way to think about a web component is simple. It’s a custom HTML element powered by browser standards, not by one framework runtime. MDN describes web components as a suite of JavaScript APIs built from Custom Elements, Shadow DOM, HTML templates, and slots, which together let you create reusable elements with encapsulated behavior and styling (MDN web components overview).
That distinction changes how you build UI. A React component only exists inside React’s world. A Vue component expects Vue. A web component exists at the HTML layer, so you can place it anywhere the browser accepts an element tag. That makes it useful for design systems, shared enterprise UI, embedded widgets, and mixed-stack environments where not every team uses the same tools.
Why lifecycle feels different
With framework components, mounting and unmounting usually flow through a virtual DOM or a framework renderer. With web components, the browser runs the lifecycle. When your element enters the document, browser lifecycle hooks fire. When it leaves, cleanup hooks fire. You still write the behavior, but you don’t need a framework to mediate the element’s existence.
Practical rule: If the UI should behave like a platform primitive and survive framework churn, a web component is often a strong fit.
That doesn’t mean frameworks become irrelevant. It means the responsibility line moves. The browser owns the element contract. Frameworks can still own application state, routing, and composition around it.
Why developers keep coming back to them
Web components solve the challenge of building UI once and consuming it in more than one environment. If your organization has a React marketing app, a Vue dashboard, and a plain HTML admin surface, the browser-native element model gives you a shared delivery format that doesn’t force a rewrite for every stack.
The Three Pillars of Web Components
A web component isn’t one API. It’s a set of platform features working together. If you strip away the hype, there are three parts most developers need to understand to be productive: Custom Elements, Shadow DOM, and templates with slots.

Custom Elements
Custom Elements let you define your own HTML tags. Instead of <div> or <button>, you can create <simple-card> or <user-menu> and attach real behavior to those elements.
At the bare minimum, it looks like this:
<script>
class SimpleCard extends HTMLElement {
connectedCallback() {
this.innerHTML = `<p>Hello from a custom element.</p>`;
}
}
customElements.define('simple-card', SimpleCard);
</script>
<simple-card></simple-card>
The important part isn’t the code length. It’s the contract. After registration, the browser understands <simple-card> as a valid element and manages its lifecycle.
A few practical rules matter here:
- Use a hyphen in the name: Custom element names must include one, such as
user-card. - Keep the public API boring: Prefer attributes, properties, methods, and events that feel like normal DOM usage.
- Think in HTML first: If consuming the component requires framework-specific knowledge, portability drops fast.
Shadow DOM
Shadow DOM is the encapsulation layer. It gives your component its own internal DOM tree, separate from the main document tree. That isolation is the reason web components remain maintainable at scale.
The key technical benefit is encapsulation. Code inside a shadow tree is isolated from outside CSS and DOM queries, so styles defined inside do not leak out and external styles do not reach in, which reduces unintended side effects in larger apps and design systems (Open WC basics on shadow DOM encapsulation).
That sounds abstract until you’ve dealt with CSS collisions in a large app. A global .button selector in one feature can accidentally restyle another team’s button. Shadow DOM sharply reduces that class of problem.
Encapsulation is one of the biggest reasons web components work well in design systems. It limits the blast radius of styling and DOM changes.
Here’s the basic shape:
class SimpleCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 0.5rem;
background: white;
}
</style>
<div class="card">
<slot></slot>
</div>
`;
}
}
customElements.define('simple-card', SimpleCard);
Now the .card styles live inside the component’s shadow root instead of polluting the page.
A good reference for how a production-oriented component contract is usually documented is the DOM Studio component spec.
A quick visual walkthrough helps if you’re new to the model:
Templates and Slots
Templates and slots handle structure and composition. A <template> stores markup that won’t render until you clone it. A <slot> creates a placeholder where consumer content can be inserted.
Slots matter because they keep your component flexible. You don’t want every card, modal, or dropdown to hardcode all its content internally.
<simple-card>
<h2 slot="title">Account</h2>
<p>Your plan renews next month.</p>
</simple-card>
Inside the component, you might write:
<div class="card">
<header><slot name="title"></slot></header>
<section><slot></slot></section>
</div>
That gives the component structure without taking away consumer control.
A Small Example
Put together, the three pillars look like this:
class SimpleCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const template = document.createElement('template');
template.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 12px;
padding: 16px;
background: white;
}
h3 {
margin: 0 0 8px;
font-size: 1rem;
}
</style>
<article class="card">
<h3><slot name="title">Untitled</slot></h3>
<div><slot></slot></div>
</article>
`;
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('simple-card', SimpleCard);
Use it like this:
<simple-card>
<span slot="title">Profile</span>
<p>Edit your settings and account details.</p>
</simple-card>
That’s the core of what a web component is. A custom element gives it identity. Shadow DOM gives it boundaries. Templates and slots give it reusable structure.
Web Components vs Framework Components
The confusion usually comes from framing this as a fight. It isn’t. Web components and framework components solve different layers of the problem. One standardizes the element at the browser level. The other gives you an opinionated developer experience inside a framework.
Where the Browser Takes Over
A useful mental model is this: framework components are authoring models, while web components are delivery primitives.
| Attribute | Web Components | Framework Components (e.g., Vue/React) |
|---|---|---|
| Public interface | HTML element, attributes, properties, events | Framework component API |
| Lifecycle owner | Browser | Framework runtime |
| Styling boundary | Shadow DOM and browser APIs | Scoped CSS, CSS-in-JS, conventions |
| Portability | High across environments | Usually tied to one framework |
| Rendering internals | Flexible | Usually coupled to framework renderer |
| Best fit | Shared UI primitives, design systems, embeddable widgets | App-specific views, state-heavy screens |
The major architectural win is that the browser standardizes the public interface while developers can choose any internal rendering approach. That’s why components built with different libraries can live on the same page and still expose a common HTML-level contract. In practice, that’s what makes them portable across React, Vue, Angular, and plain HTML environments, as noted earlier.
The Trade-Offs Are Real
Web components aren’t a free lunch. They give you interoperability, but sometimes with more responsibility.
- You own DOM-level API design: Events, attributes, and properties need to be clear because consumers may not share your framework assumptions.
- Styling requires intention: Shadow DOM prevents leaks, but it also means theming needs APIs such as CSS custom properties or exposed parts.
- Framework ergonomics can be smoother: React and Vue still offer a more polished local experience for app development, especially around state, composition, and devtools.
A good rule of thumb is to use framework components for application composition and use web components for portable primitives that need to survive across apps.
Where teams go wrong is trying to make web components replace everything. They don’t need to. A common pattern is to build the low-level primitive as a web component, then wrap it in Vue or React so product teams get familiar props, events, and state bindings.
That approach works because the portability lives in the element, while the framework wrapper improves day-to-day ergonomics.
Real World Benefits and Usage Patterns
Teams usually adopt web components for one of three reasons. They need one design system across multiple stacks. They want stronger isolation in a large front-end surface. Or they’re tired of rewriting foundational UI every time the framework strategy changes.
One Component Contract Across Apps
The most practical benefit is interoperability. A single component can serve multiple applications because the contract lives at the HTML layer. That’s valuable in enterprise environments where one team ships React, another uses Vue, and a legacy surface still renders server-generated HTML.
This pattern also works well in micro-frontends. If each slice of the system brings its own framework, shared browser-native elements reduce the coordination burden. You don’t have to standardize every app on one renderer just to share a dialog, menu, tab set, or combobox.
If you want a hands-on feel for this kind of portability, a live component playground for browser-native primitives is a useful reference point.
Encapsulation Helps Large Teams
Encapsulation pays off more as a codebase grows. In a small app, global CSS leaks are annoying. In a large product with multiple contributors, they become expensive. Shadow DOM changes the default from “everything can affect everything” to “changes stay local unless you intentionally expose them.”
That changes maintenance behavior in a good way:
- Safer refactors: Internal markup can evolve without breaking external selectors.
- Cleaner ownership: Teams can ship components without negotiating every class name globally.
- Better accessibility packaging: Complex keyboard handling and ARIA behavior can live inside the component instead of being reimplemented in every app.
Why Teams See Them As Future-Proof
Frameworks change faster than browser standards. That doesn’t make frameworks bad. It just means a design system built entirely around one renderer inherits that renderer’s churn.
Web components reduce that risk because the delivery format is native to the platform. Even if a team migrates from one framework to another, the element contract can stay stable. In practice, that’s one reason browser-native components keep showing up in long-lived design systems.
When a component library is based on web standards, migration work often shifts from “rewrite the component” to “adapt the integration layer.”
That’s a much better problem to have.
Headless Components and Modern Integration
One of the most useful patterns in modern UI is the headless component. Instead of shipping fixed visuals, a headless component ships behavior, semantics, and interaction logic. You get keyboard navigation, focus handling, state transitions, and accessibility wiring, but you stay in control of the presentation.
Why headless fits web components well
Web components are a strong fit for headless primitives because they already model behavior as browser-native elements. You can encapsulate the hard parts, such as popover behavior, dismissal logic, roving focus, or menu state, while leaving teams free to apply their own design tokens and layout rules.
That separation is healthy. Product teams often want custom visuals. They usually don’t want to rebuild dialog trapping, listbox navigation, or screen reader semantics from scratch.

A solid example of the pattern is a headless component catalog where browser-native primitives are documented independently from framework sugar. A reference like the DOM Studio headless docs shows what that looks like in practice: behavior-first elements that can be consumed directly or paired with a framework layer.
Where this works best
This architecture shines when teams want both portability and a polished developer experience.
A practical setup often looks like this:
- Headless custom elements underneath: The primitive owns semantics, DOM behavior, and accessibility logic.
- Thin framework wrappers on top: Vue or React wrappers map those primitives into idiomatic props, slots, and binding patterns.
- Styling at the app or design-system layer: Teams keep brand control without forking the interaction model.
That combination solves a common problem. Raw browser-native elements are portable, but some teams don’t want to work directly with low-level DOM APIs in every feature. A thin integration layer gives them framework comfort without throwing away the cross-framework foundation.
This is also where web components stop feeling like a competing ideology and start feeling like infrastructure. The browser handles the custom element lifecycle. The primitive handles interaction. The framework wrapper handles local ergonomics. Each layer has a clear job.
The strongest use of web components today isn’t “replace your framework.” It’s “build stable primitives once, then integrate them well wherever your apps live.”
Common Pitfalls and Getting Started
Most web component frustration comes from a handful of predictable issues, not from the core idea itself.
The friction points to expect
The first is styling across the shadow boundary. Encapsulation is great until a design team wants a custom border radius or spacing hook. Plan for that early with CSS custom properties and ::part where appropriate.
The second is SSR and hydration expectations. Browser-native elements work in server-rendered pages, but you still need to think about when the component definition loads and how the upgrade from plain HTML to active element behavior happens.
The third is form behavior. If you build custom inputs, selects, or complex fields, don’t assume they’ll behave exactly like native form controls without extra work. Form participation, validation, and accessibility details deserve design attention from the start.
A good way to start
Don’t begin with your most complex component. Start with something like a card, button-like primitive, disclosure, or dialog shell. Keep the public API small. Test it in plain HTML first. Then mount it inside Vue or React and check what feels awkward.
A good starting checklist looks like this:
- Define one clear element contract: Choose attributes, properties, events, and slots deliberately.
- Decide your styling surface early: Know what stays private and what consumers can theme.
- Wrap only when it improves DX: If a Vue or React wrapper makes usage cleaner, add it as a thin layer instead of moving the logic back into the framework.
Web components aren’t a replacement for frameworks. They’re a foundational browser skill that helps you build UI with a longer shelf life.
If you’re exploring headless, portable UI primitives in a real product context, DOM Studio is worth a look. It pairs browser-native web component foundations with a polished Vue integration layer, which is a practical way to get portability without giving up framework ergonomics.