In the ever-evolving landscape of web development, the quest for reusable, performant, and framework-agnostic components is a constant priority. While frameworks like React, Vue, and Angular offer robust component models, they often lock developers into a specific ecosystem. This is where Web Components, a suite of web platform APIs, come into play, allowing for the creation of encapsulated, reusable custom elements. However, writing vanilla Web Components can be verbose and complex. Enter Stencil.js, a powerful compiler that generates standard-compliant Web Components with an exceptional developer experience.
Stencil combines the best features from popular frameworks—such as JSX, TypeScript, and a decorator-based API—to simplify the creation of highly optimized components. Unlike a traditional framework, Stencil is a compiler that does its work at build time, shipping lean, dependency-free components to the browser. This approach ensures minimal runtime overhead and maximum performance. As developers increasingly look to build design systems and component libraries that can be used anywhere, staying updated with Stencil News and its capabilities is more critical than ever. This article dives deep into Stencil.js, exploring its core concepts, advanced techniques, and best practices for building the next generation of web components.
Understanding the Core Concepts of Stencil.js
At its heart, Stencil.js is a toolchain for building reusable, scalable component libraries. It’s not a framework that gets included in your application’s runtime bundle; instead, it compiles your code into standard-compliant Web Components that can be used in any project, with or without a framework. This distinction is fundamental to understanding its performance benefits. Let’s break down the core building blocks.
Key Features and Decorators
Stencil uses TypeScript and JSX, providing a development experience that will feel familiar to anyone who has worked with modern JavaScript frameworks. It relies heavily on decorators to define component metadata and behavior:
- @Component: This is the primary decorator that registers a class as a Stencil component. It takes a configuration object to define the component’s tag name, stylesheet URL, and Shadow DOM strategy.
- @Prop: Defines a public property on the component. Props are the primary way to pass data from a parent element down to a child component. They can be configured to be mutable, reflect to attributes, and more.
- @State: Declares an internal state property. When a
@State
property’s value changes, the component’srender()
function is automatically called to re-render the UI. This is for data that is managed internally by the component. - @Method: Exposes a public method on the component’s element, allowing it to be called from outside (e.g.,
document.querySelector('my-component').someMethod()
). - @Event: Creates a custom DOM event emitter. This is the standard way for a component to communicate data or actions back up to its parent.
These decorators provide a declarative and organized way to build components. Here is a simple counter component that illustrates these concepts in action.
import { Component, h, Prop, State } from '@stencil/core';
@Component({
tag: 'my-counter',
styleUrl: 'my-counter.css',
shadow: true,
})
export class MyCounter {
/**
* The starting value for the counter.
*/
@Prop() startValue: number = 0;
@State() currentValue: number;
componentWillLoad() {
// Initialize state when the component is about to load
this.currentValue = this.startValue;
}
private increment = () => {
this.currentValue++;
};
private decrement = () => {
this.currentValue--;
};
render() {
return (
<div>
<p>Current Count: {this.currentValue}</p>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
);
}
}
Implementation and Framework Integration
Getting started with Stencil is straightforward. The Stencil CLI helps you scaffold a new project, which comes with a well-defined structure. The most important file for configuration is stencil.config.ts
, where you define how your components are built and what assets are generated.
The Build Process and Output Targets
When you run the build command (npm run build
), the Stencil compiler analyzes your components and generates multiple output targets. This is one of Stencil’s most powerful features, as it allows you to tailor the build for different use cases. Common output targets include:

- dist: The primary distribution output, containing the lazy-loaded components. This is ideal for use via a
<script>
tag. - dist-custom-elements: Generates each component as a standalone, self-defining custom element class. This is perfect for bundlers like Vite or Webpack where you want to import and define components manually.
- docs-readme: Automatically generates markdown documentation for your components based on JSDoc comments.
- www: A simple output for creating a static demo site.
Here’s an example of a stencil.config.ts
file configured for a standard web distribution and custom elements bundle.
import { Config } from '@stencil/core';
export const config: Config = {
namespace: 'my-component-library',
taskQueue: 'async',
outputTargets: [
{
type: 'dist',
esmLoaderPath: '../loader',
},
{
type: 'dist-custom-elements',
// This allows you to use the components without the loader script
// perfect for integration with frameworks like React, Vue, or Angular.
},
{
type: 'docs-readme',
},
{
type: 'www',
serviceWorker: null, // disable service workers
},
],
// Configure testing with Jest and Puppeteer
testing: {
browserHeadless: 'new',
},
};
Using Stencil Components Anywhere
The beauty of Stencil is that the output is just standard HTML and JavaScript. To use your compiled component library in a plain HTML file, you simply include the script tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stencil Component Demo</title>
<!-- Load the component library -->
<script type="module" src="/build/my-component-library.esm.js"></script>
<script nomodule src="/build/my-component-library.js"></script>
</head>
<body>
<h1>Using a Stencil Web Component</h1>
<!-- Use the custom element just like any other HTML tag -->
<my-counter start-value="10"></my-counter>
</body>
</html>
This seamless integration is a game-changer. You can build a single component library and use it across projects built with React, Vue.js, Angular, or even legacy systems that use jQuery. This drastically reduces code duplication and streamlines maintenance, a frequent topic in React News and Vue.js News discussions about micro-frontends.
Advanced Techniques and Patterns
Once you’ve mastered the basics, Stencil offers advanced features for handling complex scenarios like cross-component communication, styling encapsulation, and state management.
Component Communication with Events
While props are great for passing data down, components often need to send information back up to their parents. The standard way to do this in Web Components is with custom DOM events. Stencil’s @Event
decorator makes this incredibly easy.
In the following example, a custom button component emits a buttonClicked
event with a payload whenever it’s clicked. A parent component can then listen for this event and react accordingly.
import { Component, h, Event, EventEmitter } from '@stencil/core';
@Component({
tag: 'custom-button',
styleUrl: 'custom-button.css',
shadow: true,
})
export class CustomButton {
/**
* Emits data when the button is clicked.
* The event detail will be an object with a timestamp.
*/
@Event() buttonClicked: EventEmitter<{ time: number }>;
private handleClick = () => {
// Emit the custom event with a payload
this.buttonClicked.emit({ time: Date.now() });
};
render() {
return (
<button onClick={this.handleClick}>
<slot /> <!-- Use slot for button text -->
</button>
);
}
}
// In a parent component or vanilla JS:
// const myButton = document.querySelector('custom-button');
// myButton.addEventListener('buttonClicked', (event) => {
// console.log('Button was clicked at:', event.detail.time);
// });
Styling Encapsulation: Shadow DOM vs. Scoped CSS
Stencil gives you fine-grained control over CSS encapsulation. In the @Component
decorator, you can set `shadow: true` to use the native Shadow DOM. This provides the strongest encapsulation, preventing styles from leaking in or out of your component. This is the default and recommended approach. However, in some cases, you might need global styles to affect your component. For this, you can use `scoped: true`, which polyfills scoping by adding data attributes to your CSS selectors at build time, a technique also seen in frameworks discussed in Svelte News.
State Management

For simple component state, @State
is sufficient. For more complex applications, you might need a more robust state management solution. The Stencil team maintains @stencil/store
, a lightweight, proxy-based state management library inspired by MobX. It’s an excellent choice for managing shared state between components without pulling in a heavy dependency. This aligns with trends in the broader ecosystem, including updates from Node.js News and TypeScript News which emphasize leaner, more efficient tooling.
Best Practices and Performance Optimization
Building with Stencil is already a step towards better performance, but following best practices can elevate your components even further.
Keep Components Small and Focused
Adhere to the single-responsibility principle. Each component should do one thing well. This makes them easier to maintain, test, and reuse. Instead of building a monolithic “user-profile” component, break it down into smaller pieces like `avatar-image`, `user-name-display`, and `contact-button`.
Leverage Functional Components
For components that are purely presentational and don’t need internal state or lifecycle methods, Stencil supports functional components. These are simple functions that receive props and return JSX, resulting in even smaller and faster components.

Effective Testing Strategies
Stencil’s CLI comes with a pre-configured testing setup using Jest for unit tests and Puppeteer for end-to-end (E2E) tests. Write unit tests to verify a component’s logic (e.g., methods, state changes) and E2E tests to ensure it renders and behaves correctly in a real browser environment. Keeping up with Jest News and Playwright News can provide insights into modern testing patterns that can be applied to your Stencil projects.
Stay Informed on the Ecosystem
The world of web development moves fast. News from build tools like Vite News and Turbopack News can influence how you bundle and serve your components. Similarly, updates from linters and formatters covered in ESLint News and Prettier News help maintain code quality across your team.
Conclusion
Stencil.js offers a compelling solution to a long-standing challenge in web development: creating a single set of components that can work everywhere. By leveraging web standards and a powerful compiler, it delivers on the promise of truly portable, high-performance UI elements. Its familiar development experience, powered by TypeScript and JSX, lowers the barrier to entry, while its advanced features provide the depth needed for complex applications and large-scale design systems.
As organizations continue to break down monolithic frontends and adopt more flexible, micro-frontend architectures, tools like Stencil will become increasingly indispensable. By investing in a Stencil-based component library, you are not just building for your current tech stack, but for any stack you might use in the future. The next step is to dive into the official documentation, experiment with the CLI, and start building your own library of framework-agnostic web components.