Drop In Web Components

How to drop existing web components into any project with five lines of HTML and no build step -- plus where to find them and what to reach for first.

June 12, 20267 min read3 / 3

The first web component I actually shipped was one I didn't write.

I needed a before/after image slider. I knew what that implementation looked like: pointer events, touch events, absolute-positioned images of unknown dimensions, responsive containment, keyboard accessibility. I also knew what week that PR would turn into. Then I found a component from Google Chrome Labs called <two-up>, and I was done in five lines of HTML.

HTML
<two-up> <div><img src="before.jpg" alt="Before the flood"></div> <div><img src="after.jpg" alt="After the flood"></div> </two-up> <script type="module" src="https://unpkg.com/two-up-element@1"></script>

No CLI. No node_modules. No build step. The custom element tag is just HTML, and the component is an ES module you import from a CDN.

The drop-in pattern: a custom tag, a module script, and a rendered component ExpandThe drop-in pattern: a custom tag, a module script, and a rendered component

The Drop-In Pattern

Every standalone web component follows the same shape:

  1. Add the custom element tag where you want the UI
  2. Load the component definition as type="module" from a CDN
HTML
<my-component></my-component> <script type="module" src="https://unpkg.com/package-name"></script>

That type="module" attribute is not optional.

Without it, the browser parses the file as a classic script. Classic scripts do not support ES module export syntax. The component never registers. Nothing renders, and nothing in the console explains why. This is the most common reason a web component appears to "not work" on first use.

Progressive Enhancement Comes Free

Notice what happens to the two-up slider if the script never loads. You get one image on top of another. Not a broken layout. Not a blank space. Just the image.

This isn't special error handling -- it's how custom elements work by default. The browser sees an unknown tag, treats it as an anonymous container, and renders its children normally. If you structure the content inside with that in mind, the fallback requires zero extra work.

A <details> element inside <details-utils> still discloses its content if the component never loads. A <audio controls> inside a podcast player component still plays audio.

The custom element layer is enhancement. The native element underneath keeps working regardless.

Wrapping What Already Exists

The most underrated use case for web components isn't building new UI -- it's adding behavior to elements that already exist.

<details-utils> is a good example. The native <details> element is genuinely useful: free disclosure, keyboard accessible, works everywhere. But it has gaps:

  • It does not close when you press Escape
  • It does not close when you click outside
  • Animating the reveal requires measuring its height at runtime and transitioning between a known and unknown value

Wrap it, and all three problems go away:

HTML
<details-utils close-esc close-when-outside-click animate> <details> <summary>Filter results</summary> <!-- form content --> </details> </details-utils>

Nothing inside the <details> changed. The wrapper adds behavior through attributes. The native element handles everything else.

The same pattern applies broadly: a table that exports to CSV, an audio element that becomes a full podcast player with speed control and 30-second rewind, any native element where you want superpowers without replacing the original.

Styling From the Outside

One thing that trips people up: you cannot target shadow DOM internals with an external stylesheet. Your CSS has no path into the component.

Component authors anticipate this. The standard pattern is CSS custom properties, because variables cross the shadow boundary:

CSS
scroll-shadow { --scroll-shadow-size: 40px; --scroll-shadow-color: rgba(130, 80, 255, 0.35); }

Before committing to a component, check what custom properties it exposes. That is the intentional styling surface. You also control any content you pass in as light DOM -- whatever sits between the component's opening and closing tags is yours to style freely.

Composing Components

Web components are HTML tags. You can nest them.

A browser-based image compression tool -- Squoosh -- is built from a stack of web components: a <pinch-zoom> component wraps a <two-up> component, each handling a different part of the comparison experience. Different components, composed in HTML, producing a third behavior that neither component has on its own.

Nesting is how individual drop-in components become a UI. You start with one for a single feature, then realize you can stack two and get a third for free.

Where to Find Them

The ecosystem doesn't have a single discovery point. A few worth bookmarking:

Awesome Standalones is a curated GitHub list of components that work with no build step -- CDN URL, custom tag, done. A good first stop for any "I don't want to code this" problem.

A few from that list worth knowing specifically:

lite-youtube -- a drop-in replacement for <iframe> YouTube embeds. The standard YouTube embed fires a large block of JavaScript on page load. lite-youtube defers all of it until the user clicks play. The swap is one attribute change. The page speed impact can be extraordinary -- a content-heavy page that embeds many YouTube videos can move from a score in the teens to the 90s from this one change alone.

model-viewer -- renders 3D models with optional AR support. The syntax mirrors an image element: src points to a .glb file, alt is for screen readers, ar enables the "place this in your room" button. If you have ever searched for an animal on mobile Google and gotten a 3D model you could drop into your living room, that is model-viewer.

scroll-shadow -- adds a directional gradient shadow to a scrollable container, indicating that content continues beyond the visible edge. The right effect for overflow navigation or long dropdown menus. Without a component, you are writing ResizeObserver and scroll event listeners by hand.

generic-components (Pascal Schilp) -- a small library of accessible, unstyled components: tabs, accordion, disclosure, tooltip. Each is built against the WAI-ARIA Authoring Practices spec. No styles imposed -- you bring your own CSS. A good middle ground between one-off standalone components and a full design system.

Design Systems

Single components solve one problem. Design systems solve a product's worth.

The main split: styled (opinions on look included) vs headless (accessible structure, no visual opinions).

Headless systems worth knowing:

  • Lion (ING Bank) -- banking-grade accessibility requirements baked in, designed to be extended before use rather than used directly
  • PatternFly Elements (Red Hat) -- thorough WAI-ARIA coverage, enterprise-grade without lock-in
  • Fast (Microsoft) -- some innovative ideas on adaptive color and design tokens; powers parts of the Fluent UI

Styled but customizable:

  • Shoelace -- the most approachable, well-documented web component design system available; started as vanilla JS, migrated to Lit when the author found it genuinely improved the codebase
  • Spectrum Web Components (Adobe) -- Adobe's own design system as web components; Photoshop on the web uses them in production
  • Ionic -- originally for hybrid mobile apps, works in any web context; strong for tab bars and navigation patterns that match iOS/Android conventions

Build Tooling: Bring Your Own

The CDN import pattern is ideal for prototyping. Production is different -- you will want components locally installed, bundled, and served from your own domain.

The good news is that web components don't mandate a build tool. There is no required bundler, no webpack version you are locked into. If you use Vite, esbuild, or Parcel, web components slot in. You own the tooling decision.

The tradeoff: unlike frameworks that come with prescribed pipelines, the web component ecosystem hands you primitives. More control, more choices to make.

The next topic -- styling web components intentionally, working with the shadow boundary rather than against it, and understanding what CSS can and cannot reach -- is where the surface area gets real.

The Essentials

  1. You do not need to write web components to use them. An existing component is a custom tag plus <script type="module" src="...">.
  2. Always add type="module" to the script tag. Without it, the browser uses the classic script parser, ES module exports fail silently, and the component never registers.
  3. When JavaScript fails to load, the content inside an unregistered custom element renders as normal HTML. Native elements keep working -- they are the fallback.
  4. CSS custom properties cross the shadow boundary. They are the primary styling API for most third-party components.
  5. Awesome Standalones is the first place to check for a drop-in component that solves a specific problem without a build step.
  6. lite-youtube is the highest-ROI swap for most sites: replace standard YouTube iframes, change zero JavaScript, get dramatically better page speed scores.
  7. Web components work with any bundler at the production level. The CDN import pattern is for development and prototyping.

Further Reading and Watching