Skip to content

Slots

Astro components use slots for content projection. In Storybook, you pass slot content through a special slots property inside args.

The default slot (<slot />) receives content via args.slots.default:

Alert.astro
---
const { variant = 'info' } = Astro.props;
---
<div class:list={['alert', variant]}>
<slot />
</div>
Alert.stories.jsx
import Alert from './Alert.astro';
export default {
title: 'Components/Alert',
component: Alert,
};
export const Info = {
args: {
variant: 'info',
slots: {
default: '<p>This is an informational message.</p>',
},
},
};
export const Warning = {
args: {
variant: 'warning',
slots: {
default: '<p><strong>Warning:</strong> Something needs attention.</p>',
},
},
};

Slot content can be an HTML string (any valid HTML — elements, nested markup, inline styles), an Astro component (see Passing a component as slot content), a configured component with its own props and slots, or an array mixing any of these.

Named slots (<slot name="..." />) are passed using matching keys in args.slots:

Modal.astro
---
const { open = false } = Astro.props;
---
<div class:list={['modal', { open }]}>
<div class="modal-header">
<slot name="header" />
</div>
<div class="modal-body">
<slot />
</div>
<div class="modal-footer">
<slot name="footer" />
</div>
</div>
Modal.stories.jsx
import Modal from './Modal.astro';
export default {
title: 'Components/Modal',
component: Modal,
};
export const Default = {
args: {
open: true,
slots: {
header: '<h2>Confirm Action</h2>',
default: '<p>Are you sure you want to proceed?</p>',
footer: '<button>Cancel</button> <button>Confirm</button>',
},
},
};

Each key in the slots object corresponds to a slot name in the component. The default key maps to the unnamed <slot />.

A slots entry can be another Astro component instead of an HTML string — the equivalent of React’s children:

Panel.astro
---
const { title } = Astro.props;
---
<section class="panel">
<h2>{title}</h2>
<div class="panel-body"><slot /></div>
</section>
Panel.stories.jsx
import Panel from './Panel.astro';
import Badge from './Badge.astro';
export default {
title: 'Components/Panel',
component: Panel,
};
export const WithComponent = {
args: {
title: 'Status',
slots: {
default: Badge,
},
},
};

The component is server-rendered and its markup is placed into the slot, with its scoped styles intact. This also works for named slots.

A bare component reference renders with its default props and no slot content of its own. To give the child its own props and slot content, use a configured component (next section).

Passing a configured component (with its own props and slots)

Section titled “Passing a configured component (with its own props and slots)”

To place a child component that has its own props and slot content, pass a configured-component object — { component, props, slots } — instead of the bare reference:

Panel.stories.jsx
import Panel from './Panel.astro';
import Badge from './Badge.astro';
export const WithConfiguredComponent = {
args: {
title: 'Status',
slots: {
default: {
component: Badge,
props: { variant: 'success' },
slots: { default: 'Shipped' },
},
},
},
};

Only component is required; props and slots are optional. A configured component’s slots are themselves slot values, so they can contain plain strings, more components, and further configured components — nesting to any depth.

A slot value can be an array, letting you interleave plain HTML strings with (configured) components in a single slot. Each entry is rendered and concatenated in order:

export const MixedContent = {
args: {
slots: {
default: [
'<p>Before the child</p>',
{ component: Badge, slots: { default: 'In the middle' } },
'<p>After the child</p>',
],
},
},
};

A configured component’s props are passed to the child untouched — they are not HTML-sanitized (so values like "A & B" survive verbatim). Its slots are sanitized like any other string slot content.

Props and slots are passed together in the same args object. Regular properties become Astro.props, and the slots property is handled separately by the renderer:

export const Default = {
args: {
imageSrc: '/hero.png',
imageAlt: 'Hero image',
slots: {
default: `
<h2>Welcome</h2>
<p>This is the text content beside the image.</p>
`,
},
},
};
  • Slot content is an HTML string, a component, or a list — write raw HTML strings (not JSX or Astro template syntax), pass an imported Astro component (bare or configured with { component, props, slots }), or an array mixing them. Component tags inside a string are not compiled — always pass the imported reference.
  • Multi-line content — Use template literals (backtick strings) for readable multi-line slot content.
  • No slot fallback in stories — If you don’t provide a slots entry, the component’s <slot> fallback content (if any) will render.
  • Static in builds — Like other Astro component args, slot content is pre-rendered at build time. It’s fully interactive in dev mode.