A simple utility for adding named slots to React components, inspired by the slot pattern in web components and Vue.
- Named slots: Expand on React's children by defining specific child component options.
- Structured layout: Assign your slot components to static locations in the JSX.
- Multiple/Repeatable slots: Allow multiple children for a slot by wrapping the slot component in an array.
- Composed components: Add components to the namespace without rendering them as slots.
- TypeScript support: Strongly typed slot schemas and props.
- Tiny bundle size: A whopping 1.1kb file.
Add the package to your React application using your preferred package manager.
npm i aiera-inc/react-slots
Alternatively copy the react-slots.tsx
file directly into your project. It's that small!
Slotted components are created using the SlottedComponent
utility method exported by the library and consumed just like any other React component.
import { SlottedComponent } from 'react-slots';
// Your parent components props
type MyComponentProps = {
author: string;
};
// The components you are assigning as slots
const SLOT_SCHEMA = {
Title: (props: { title: string }) => <header>{props.title}</header>,
Footer: () => <footer>Footer</footer>,
} as const;
// Your slotted component
export const MyComponent = SlottedComponent(SLOT_SCHEMA)<MyComponentProps>(({
author,
children,
slots: { Title, Footer },
}) => {
return (
<div>
{Title}
<h3>By: {author}</h3>
<main>{children}</main>
{Footer}
</div>
);
});
Your slotted component works like any other React component, only it now has several slots added as properties to it.
import { MyComponent } from './my-component';
export function App() {
return (
<div>
<MyComponent author="John Doe">
<MyComponent.Title title="Hello World" />
<article>This is the main content.</article>
<article>A second article.</article>
<MyComponent.Footer />
</MyComponent>
</div>
);
}
Note, the order of the slotted child components doesn't actually matter because the MyComponent
code has placed it in a concrete location within it's layout.
function App() {
return (
<>
// This is equivalent...
<MyComponent author="John Doe">
<MyComponent.Title title="Hello World" />
<article>This is the main content.</article>
<article>A second article.</article>
<MyComponent.Footer />
</MyComponent>
// ...to this
<MyComponent author="John Doe">
<MyComponent.Title title="Hello World" />
<MyComponent.Footer />
<article>This is the main content.</article>
<article>A second article.</article>
</MyComponent>
</>
);
}
The slot schema has two unique syntax structures that can be used to define repeated slots and namespace slots.
By default, only one instance of a component will be passed to a slot and the others will be tossed.
const ArticleExample = (props) => <article>{props.children}</article>;
const SLOT_SCHEMA = {
Article: ArticleExample,
} as const;
const Blog = SlottedComponent(SLOT_SCHEMA)<{ author: string }>(({
author,
children,
slots: { Article },
}) => {
return <div>{Article}</div>;
});
function App() {
return (
<Blog author="John Doe">
<Blog.Article>Article 1</Blog.Article>
<Blog.Article>Article 2</Blog.Article> // This will be ignored
</Blog>
);
}
To allow for repeated uses of a slot, you simply need to wrap it in square brackets in the slot schema. This tells the utility that multiple instances of the <Article />
slot may be passed and should be rendered.
const ArticleExample = (props) => <article>{props.children}</article>;
const SLOT_SCHEMA = {
Article: [ArticleExample],
} as const;
Sometimes you may want to link components to indicate a relationship between parent and child, but you don't have a concrete location that you want to place the children in your layout. You are effectively namespacing the child component to the parent. This is often also described as composed components.
const ArticleExample = (props) => <article>{props.children}</article>;
const SLOT_SCHEMA = { // Wrapping the component in curly braces
Article: { ArticleExample }, // tells the utility this is a namespaced
} as const; // component.
const Blog = SlottedComponent(SLOT_SCHEMA)<{ author: string }>(({
author,
children, // children contains your Article instances
slots, // Slots will be an empty object
}) => {
return <div>{children}</div>;
});
function App() {
return (
<Blog author="John Doe">
<Blog.Article>Article 1</Blog.Article>
<Blog.Article>Article 2</Blog.Article>
</Blog>
);
}
Wraps your component and attaches slot components as static properties. The slots
prop will be injected into your component, containing the slotted children.
Utility to extract slots and non-slot children from a component's children.
Higher-order component for applying a slot schema in a more declarative way.
SlotDictionary
: Defines the slot schema.WithSlotProps
: Utility type for merging slot props into your component props.SlotProviderInterface
: Interface alternative for slot props.
- Nested child fragments of composed components will be automatically flattened.
- Nesting fragments by 2 or more levels is not supported.
- Namespaced slots are not included in the
slots
prop.
MIT