Carousel
A horizontal collection with native scrolling and named controls.
More examples
One item
Source
<script lang="ts">
import 'portal-bits/styles.css';
import 'portal-bits/tokens.css';
import type { Snippet } from 'svelte';
let { children }: { children: Snippet } = $props();
</script>
{@render children()}
<script lang="ts">
import CarouselExample from './CarouselExample.svelte';
</script>
<CarouselExample layout={"responsive"} />
<script lang="ts">
import { ArticleCard, Carousel } from 'portal-bits';
let { layout = 'responsive' }: { layout?: 'responsive' | 'carousel' } = $props();
// Synthetic posters as inline SVG data. Pass your own image URLs as `image` instead.
function poster(word: string, background: string, ink: string) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="400" viewBox="0 0 640 400"><rect width="640" height="400" fill="${background}"/><circle cx="440" cy="170" r="120" fill="none" stroke="${ink}" stroke-width="2" opacity=".45"/><text x="32" y="178" fill="${ink}" font-family="Arial,sans-serif" font-size="66" font-weight="500">${word}</text></svg>`;
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}
const stories = [
{
id: 'field',
label: 'A field guide to small ideas',
image: poster('field', '#e6ddd0', '#776749'),
href: '#field-guide',
},
{
id: 'orbit',
label: 'Make space for your next project',
image: poster('ORBIT', '#c5d4f6', '#263864'),
href: '#next-project',
},
{
id: 'tempo',
label: 'Find a rhythm that works',
image: poster('tempo', '#ded5f0', '#614984'),
href: '#rhythm',
},
];
</script>
<Carousel label="Studio journal" items={stories} {layout}>
{#snippet item(story)}
<ArticleCard
title={story.label}
href={story.href}
image={story.image}
imageAlt="Geometric illustration"
/>
{/snippet}
</Carousel>
API reference
These tables list the props and snippets that Carousel declares. Attributes passed through to an element or primitive are listed under Forwarded attributes, not here.
Props
| Name | Type | Default | Required | Binding | Description |
|---|---|---|---|---|---|
label | string | None | Yes | One-way | Names the carousel region. The viewport and pagination groups add a suffix to it. |
items | T[] where T extends { id: string; label: string } | None | Yes | One-way | The slides. Each `id` must be unique and stable. Each `label` describes the slide for assistive technology. |
layout | 'responsive' | 'carousel' | responsive | No | One-way | Responsive becomes a three-column grid at 48rem of component width. Carousel always scrolls. |
Snippets
| Name | Type | Default | Required | Binding | Description |
|---|---|---|---|---|---|
item | Snippet<[T]> | None | Yes | One-way | Renders one slide. It receives the item. |
Forwarded attributes
None. Carousel forwards no attributes.
No data attributes are documented as stable.
Limits
- Arrow keys, Home, and End work only while the viewport itself has focus. Nested controls keep their own keys.
- No autoplay, looping, bound index, or custom drag. Navigation is immediate, including with reduced motion.
- An empty `items` renders no viewport. One item renders no pagination.
- Changing item ids or their order resets to the first slide. The 48rem threshold is an adaptation.
- Keyboard navigation is left to right.