React Integration
Supports: React 18 and 19 • Stencil v5
@stencil/react-output-target generates a React component for each of your Stencil components. Consumers of your library get real React components: typed props, onMyEvent handlers for your custom events, refs, and optional server-side rendering in Next.js. They never touch the custom elements directly.
React 19 can also render custom elements without wrappers. If typed props are all your consumers need, Framework Types is a lighter option.
Setup
The generated React components live in their own package, published alongside your Stencil library. A monorepo with one package for each is the recommended structure:
my-design-system/
├── package.json workspace root
└── packages/
├── core/ your Stencil library
└── react/ the generated React library
New Project
Run the stencil init wizard, pick React under framework integrations, and accept the monorepo workspace prompt. It creates the structure above with both packages configured.
Existing Project
Run the wizard from your Stencil project:
stencil add @stencil/react-output-target
It asks where the React package should go and whether to enable server-side rendering, then:
- creates the React package, with its
package.json,tsconfig.json, and entry file - adds
reactOutputTarget()to yourstencil.config.ts, along with thestandaloneoutput target it builds on - installs the dependencies for both packages
Manual Setup
To do the same by hand, install the output target in your Stencil project:
- npm
- Yarn
- pnpm
npm install @stencil/react-output-target --save-dev
yarn add @stencil/react-output-target --dev
pnpm add @stencil/react-output-target --save-dev
Add it to your config, pointing outDir at the React package's source directory:
import { Config } from '@stencil/core';
import { reactOutputTarget } from '@stencil/react-output-target';
export const config: Config = {
namespace: 'MyLibrary',
outputTargets: [
{ type: 'standalone' },
reactOutputTarget({
outDir: '../react/src',
}),
],
};
The React package depends on your Stencil library and on @stencil/react-output-target, which provides the runtime the generated components use:
{
"name": "my-library-react",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
},
"scripts": {
"build": "tsc"
},
"dependencies": {
"my-library": "^1.0.0",
"@stencil/react-output-target": "^1.6.0"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
}
Its entry file re-exports the generated components:
export * from './components.js';
Building
Build the Stencil library first. That writes the React components to packages/react/src/components.ts. Then build the React package:
- npm
- Yarn
- pnpm
npm run build
yarn build
pnpm run build
In a workspace created by the wizard, running this from the root builds both in order.
Publish the two packages together, keeping their versions in step. See Publishing.
Consumer Usage
Consumers install your React package and import components from it:
import { MyButton } from 'my-library-react';
export function SaveBar() {
return (
<MyButton variant="primary" onMyPress={(event) => console.log(event.detail)}>
Save
</MyButton>
);
}
Each component registers its own custom element when it's first imported, so there's no loader to call, and a bundler includes only the components an app uses.
A custom event named myPress becomes an onMyPress prop. Write event names in camelCase in your Stencil components so they read naturally in every framework.
Server-Side Rendering
For Next.js, the output target can generate server versions of your React wrappers, which render their Stencil component during Next.js's own server rendering. Next.js renders React Server Components one at a time, so there's no whole page of HTML to pass through renderToString(), the approach other server frameworks use.
Add the ssr output target and set hydrateModule and clientModule on the React output target:
import { Config } from '@stencil/core';
import { reactOutputTarget } from '@stencil/react-output-target';
export const config: Config = {
outputTargets: [
{ type: 'ssr' },
reactOutputTarget({
outDir: '../component-library-react/src',
// your library's ssr module
hydrateModule: 'component-library/ssr',
// the package the client-side wrappers are published as
clientModule: 'component-library-react',
}),
],
};
This generates components.ts for the client and components.server.ts for the server. Expose the server entry point from your React package:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./next": {
"types": "./dist/components.server.d.ts",
"import": "./dist/components.server.js"
}
}
}
Then import components from that entry point in your Next.js app:
import { MyComponent } from 'component-library-react/next';
export default function Home() {
return <MyComponent first="Max" last="Mustermann" />;
}
See the React output target's hydrateModule and excludeServerSideRenderingFor options for more control.
API
outDir
Required
Type: string
The directory the React components are written to, relative to your Stencil project.
esModules
Type: boolean
Default: false
Write each component to its own file, with components.ts re-exporting them all, instead of one file containing every component.
excludeComponents
Type: string[]
Tag names to leave out of the React library.
stencilPackageName
Type: string
The package the generated components import your Stencil library from. Defaults to the name in your Stencil project's package.json.
customElementsDir
Type: string
The directory of the standalone output, relative to your Stencil project. Defaults to that output target's own dir.
hydrateModule
Type: string
The import path of your library's ssr module: my-library/dist/ssr, or my-library/ssr if your library sets generateExportMaps. Setting it enables server-side rendering, and requires clientModule and the ssr output target.
clientModule
Type: string
The name of your React package, for example my-library-react. The server components import the client components from it. Required when hydrateModule is set.
excludeServerSideRenderingFor
Type: string[]
Tag names to render on the client only, when server-side rendering is enabled.
serializeShadowRoot
Type: 'declarative-shadow-dom' | 'scoped' | { 'declarative-shadow-dom'?: string[]; scoped?: string[]; default: 'declarative-shadow-dom' | 'scoped' } | false
Default: 'declarative-shadow-dom'
How shadow DOM components are rendered on the server. See serializeShadowRoot.
transformTag
Type: boolean
Default: false
Generates a tag-transformer.ts file alongside the components, so consumers can register them under different tag names. See Tag Transformation.