Stencil is the Framework Nobody Uses (But Maybe Should)

March 6, 2026

I’ve been building web components with Stencil for years now. Every time I mention it, I get the same response: “Oh yeah, I’ve heard of that. Ionic’s thing, right?”

Yes. And also no.

Stencil is what happens when you ask “what if we compiled JSX to web standards instead of shipping a runtime?” It’s brilliant, underappreciated, and solves problems most developers don’t realize they have.

Let me explain why you should care.

The Problem with Every Framework

Here’s the dirty secret about React, Vue, Angular, and Svelte: they all ship JavaScript to do what the browser already knows how to do.

// React component\nfunction Button({ variant, onClick }) {\n  return <button className={`btn-${variant}`} onClick={onClick}>Click me</button>\n}\n\n// Ships to browser:\n// - React runtime (40kb+)\n// - Virtual DOM reconciliation\n// - Event system wrapper\n// - Hydration logic

Every user downloads React. Every time. Even if you code-split and tree-shake and do all the performance theater, you’re still shipping a framework runtime.

Stencil asks a different question: what if we compiled that component to web standards and shipped nothing?

How Stencil Actually Works

Stencil components look like React:

import { Component, Prop, h } from '@stencil/core';\n\n@Component({\n  tag: 'my-button',\n  styleUrl: 'my-button.css',\n  shadow: true\n})\nexport class MyButton {\n  @Prop() variant: string;\n  \n  render() {\n    return <button class={`btn-${this.variant}`}>\n      <slot />\n    </button>;\n  }\n}\n

At build time, this compiles to:

// Actual CustomElement that registers with the browser\nclass MyButton extends HTMLElement {\n  // Efficient property/attribute handling\n  // Optimized rendering\n  // No framework runtime needed\n}\ncustomElements.define('my-button', MyButton);\n

The result? A tiny JavaScript bundle (2-3kb per component) that uses native browser APIs. No React. No Vue. No runtime at all.

For my design system, 30 components compile to ~40kb total. The same thing in React? Easily 100kb+ before you even write component logic.

The DX is Better Than You Think

“But web components have terrible DX!” Yeah, I thought that too. Then I actually used Stencil.

JSX that works: Not some weird template DSL. Actual JSX with TypeScript support, autocomplete, and all the ergonomics React developers expect.

Props that make sense:

@Prop() userName: string;  // Not attributes-as-strings nonsense\n@Prop() isActive: boolean;  // Actual types\n@Prop() config: ComplexObject;  // Objects work fine

State management that’s simple:

@State() count: number = 0;\n\nincrement() {\n  this.count++; // Re-renders automatically\n}

Lifecycle hooks that are clear:

componentWillLoad() { }  // Like componentDidMount\ncomponentDidLoad() { }\ncomponentWillUpdate() { }\n

It’s React’s API, compiled to standards. The learning curve is about 30 minutes if you know React.

When Web Components Actually Make Sense

I’ve shipped Stencil components in:

  • React apps

  • Angular apps

  • Vue apps

  • Plain HTML with no framework

Same components. No rewrites. No framework adapters.

Design systems are the killer use case. You want your button component to work everywhere? Stencil gives you that for free.

<!-- Works in any framework -->\n<design-button variant="primary">Click me</design-button>\n\n<!-- React -->\n<DesignButton variant="primary">Click me</DesignButton>\n\n<!-- Angular -->\n<design-button [variant]="'primary'">Click me</design-button>

No wrappers. No compatibility layers. Just web standards.

The Parts Nobody Talks About

Shadow DOM is optional. Everyone assumes web components = Shadow DOM. Nope. Stencil lets you choose:

@Component({\n  tag: 'my-component',\n  shadow: true  // Can be false for light DOM\n})

Need global styles? Use light DOM. Need true encapsulation? Use shadow DOM. You decide per component.

TypeScript is first-class. Not bolted on. Built in. Your components get full type safety, and consumers get proper types too.

Testing is actually good. Stencil comes with a Jest-based testing setup that handles web components properly:

it('renders', async () => {\n  const page = await newSpecPage({\n    components: [MyButton],\n    html: `<my-button variant="primary">Test</my-button>`\n  });\n  expect(page.root).toEqualHtml(`...`);\n});\n

SSR works. Stencil components can render on the server. No hydration mismatch hell.

The React Connection

Here’s what React developers don’t want to admit: Stencil’s API is better than React’s for web components.

React’s web component story is terrible:

// React can't pass objects to web components\n<my-component data={complexObject} />  // Doesn't work\n<my-component data={JSON.stringify(complexObject)} />  // Ugly workaround\n

Stencil handles this elegantly because it’s built for web components from day one.

And the JSX? Stencil had JSX for web components before React even acknowledged web components existed. React inspired the syntax, but Stencil proved it could compile to standards.

The Honest Downsides

  • Ecosystem is small. No massive component library like Material UI. You’re building most things yourself.

  • Documentation gaps. The official docs are good for basics, bad for advanced patterns. You’ll be reading source code.

  • Tooling is Ionic-focused. Feels like a second-class citizen if you’re not building an Ionic app.

  • Debugging compiled output. When something goes wrong in production, you’re debugging the compiled web component, not your nice TypeScript source.

  • Community is tiny. Stack Overflow has 100x more React questions. You’re often figuring things out alone.

So Should You Use It?

Yes, if:

  • You’re building a design system that needs to work across frameworks

  • You want framework-agnostic components that aren’t tied to React’s lifecycle

  • Bundle size actually matters for your use case

  • You’re okay with a smaller ecosystem

No, if:

  • You need a rich ecosystem of pre-built components

  • Your team is all-in on React/Vue/Angular and has no cross-framework needs

  • You’re building a typical SPA where framework choice is already made

  • You need hand-holding documentation for every edge case

My Take

I love Stencil for what it is: a compiler that takes great DX and produces tiny, standards-based output.

For my design system, it’s perfect. I write once, it works everywhere, and users don’t download a framework runtime. The components I built three years ago still work unchanged in new projects.

But I’m not going to pretend it’s ready to replace React for everything. The ecosystem isn’t there. The community is small. You’ll hit weird edge cases that nobody else has documented.

Stencil occupies this weird space where it’s technically superior for its use case (cross-framework components) but practically limited by its niche adoption.

Maybe that’s okay. Not everything needs to be React. Sometimes the “framework nobody uses” is exactly the right tool for the job.


Try it yourself:

npm init stencil component my-component

Good for:

  • Design systems

  • Web component libraries

  • Framework-agnostic UI components

Not good for:

  • Full SPAs (use React/Vue/Svelte)

  • Projects needing rich ecosystems

  • Teams unfamiliar with web components


Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.

Get more from MSPK in the Substack app

Available for iOS and Android

Get the app