Item
Composable list row. Media on the left, stacked Title/Description in the center, Actions on the right. Hover colors the row when rendered as a link; otherwise it stays quiet.
Basic
Last modified 2 days ago
<script lang="ts">
import * as Item from '$lib/components/ui/item';
</script>
<Item.Root>
<Item.Media variant="icon">
<Icon name="folder-line" />
</Item.Media>
<Item.Content>
<Item.Title>Project files</Item.Title>
<Item.Description>Last modified 2 days ago</Item.Description>
</Item.Content>
</Item.Root>With actions

ada@bloom.design
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
export const itemVariants = tv({
base: "[a]:hover:bg-substrate-hover rounded-2xl border text-sm group/item focus-visible:border-ring focus-visible:ring-ring/50 relative flex w-full flex-wrap items-center transition-colors duration-(--motion-duration-micro) outline-none focus-visible:ring-(length:--ring-width) [a]:transition-colors before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:[background:color-mix(in_oklch,var(--accent)_calc(var(--proximity,0)*15%),transparent)] before:transition-[background-color] before:duration-(--motion-duration-fast) before:content-['']",
variants: {
variant: {
default: 'border-transparent',
outline: 'border-border',
muted: 'bg-input/30 border-transparent'
},
size: {
default: 'gap-3.5 px-4 py-3.5',
sm: 'gap-3.5 px-3.5 py-3',
xs: 'gap-2.5 px-3 py-2.5 in-data-[slot=dropdown-menu-content]:p-0'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ItemSize = VariantProps<typeof itemVariants>['size'];
export type ItemVariant = VariantProps<typeof itemVariants>['variant'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
class: className,
child,
variant,
size,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
child?: Snippet<[{ props: Record<string, unknown> }]>;
variant?: ItemVariant;
size?: ItemSize;
} = $props();
const mergedProps = $derived({
class: cn(itemVariants({ variant, size }), className),
'data-slot': 'item',
'data-proximity-target': '',
'data-variant': variant,
'data-size': size,
...restProps
});
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div bind:this={ref} {...mergedProps}>
{@render mergedProps.children?.()}
</div>
{/if}
Group
42 KB — markdown
1.2 MB — image
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
export const itemVariants = tv({
base: "[a]:hover:bg-substrate-hover rounded-2xl border text-sm group/item focus-visible:border-ring focus-visible:ring-ring/50 relative flex w-full flex-wrap items-center transition-colors duration-(--motion-duration-micro) outline-none focus-visible:ring-(length:--ring-width) [a]:transition-colors before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:[background:color-mix(in_oklch,var(--accent)_calc(var(--proximity,0)*15%),transparent)] before:transition-[background-color] before:duration-(--motion-duration-fast) before:content-['']",
variants: {
variant: {
default: 'border-transparent',
outline: 'border-border',
muted: 'bg-input/30 border-transparent'
},
size: {
default: 'gap-3.5 px-4 py-3.5',
sm: 'gap-3.5 px-3.5 py-3',
xs: 'gap-2.5 px-3 py-2.5 in-data-[slot=dropdown-menu-content]:p-0'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ItemSize = VariantProps<typeof itemVariants>['size'];
export type ItemVariant = VariantProps<typeof itemVariants>['variant'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
class: className,
child,
variant,
size,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
child?: Snippet<[{ props: Record<string, unknown> }]>;
variant?: ItemVariant;
size?: ItemSize;
} = $props();
const mergedProps = $derived({
class: cn(itemVariants({ variant, size }), className),
'data-slot': 'item',
'data-proximity-target': '',
'data-variant': variant,
'data-size': size,
...restProps
});
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div bind:this={ref} {...mergedProps}>
{@render mergedProps.children?.()}
</div>
{/if}
Sizes
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
export const itemVariants = tv({
base: "[a]:hover:bg-substrate-hover rounded-2xl border text-sm group/item focus-visible:border-ring focus-visible:ring-ring/50 relative flex w-full flex-wrap items-center transition-colors duration-(--motion-duration-micro) outline-none focus-visible:ring-(length:--ring-width) [a]:transition-colors before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:[background:color-mix(in_oklch,var(--accent)_calc(var(--proximity,0)*15%),transparent)] before:transition-[background-color] before:duration-(--motion-duration-fast) before:content-['']",
variants: {
variant: {
default: 'border-transparent',
outline: 'border-border',
muted: 'bg-input/30 border-transparent'
},
size: {
default: 'gap-3.5 px-4 py-3.5',
sm: 'gap-3.5 px-3.5 py-3',
xs: 'gap-2.5 px-3 py-2.5 in-data-[slot=dropdown-menu-content]:p-0'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ItemSize = VariantProps<typeof itemVariants>['size'];
export type ItemVariant = VariantProps<typeof itemVariants>['variant'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
class: className,
child,
variant,
size,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
child?: Snippet<[{ props: Record<string, unknown> }]>;
variant?: ItemVariant;
size?: ItemSize;
} = $props();
const mergedProps = $derived({
class: cn(itemVariants({ variant, size }), className),
'data-slot': 'item',
'data-proximity-target': '',
'data-variant': variant,
'data-size': size,
...restProps
});
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div bind:this={ref} {...mergedProps}>
{@render mergedProps.children?.()}
</div>
{/if}
Avatar row
Deploying preview builds
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
export const itemVariants = tv({
base: "[a]:hover:bg-substrate-hover rounded-2xl border text-sm group/item focus-visible:border-ring focus-visible:ring-ring/50 relative flex w-full flex-wrap items-center transition-colors duration-(--motion-duration-micro) outline-none focus-visible:ring-(length:--ring-width) [a]:transition-colors before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:[background:color-mix(in_oklch,var(--accent)_calc(var(--proximity,0)*15%),transparent)] before:transition-[background-color] before:duration-(--motion-duration-fast) before:content-['']",
variants: {
variant: {
default: 'border-transparent',
outline: 'border-border',
muted: 'bg-input/30 border-transparent'
},
size: {
default: 'gap-3.5 px-4 py-3.5',
sm: 'gap-3.5 px-3.5 py-3',
xs: 'gap-2.5 px-3 py-2.5 in-data-[slot=dropdown-menu-content]:p-0'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ItemSize = VariantProps<typeof itemVariants>['size'];
export type ItemVariant = VariantProps<typeof itemVariants>['variant'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
class: className,
child,
variant,
size,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
child?: Snippet<[{ props: Record<string, unknown> }]>;
variant?: ItemVariant;
size?: ItemSize;
} = $props();
const mergedProps = $derived({
class: cn(itemVariants({ variant, size }), className),
'data-slot': 'item',
'data-proximity-target': '',
'data-variant': variant,
'data-size': size,
...restProps
});
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div bind:this={ref} {...mergedProps}>
{@render mergedProps.children?.()}
</div>
{/if}
Icon only
Leading icon with no avatar or image — use variant="icon" on Item.Media.
Pinned for quick access
Opened in the last 7 days
<script lang="ts">
import * as Item from '$lib/components/ui/item';
import { Icon } from '$lib/components/ui/icon';
</script>
<Item.Root variant="outline">
<Item.Media variant="icon">
<Icon name="star-line" />
</Item.Media>
<Item.Content>
<Item.Title>Starred items</Item.Title>
</Item.Content>
</Item.Root>Image
Leading image via variant="image" on Item.Media — renders a clipped thumbnail.

Full-stack framework for Svelte
<script lang="ts">
import * as Item from '$lib/components/ui/item';
</script>
<Item.Root variant="outline">
<Item.Media variant="image">
<img src="https://github.com/sveltejs.png" alt="SvelteKit" />
</Item.Media>
<Item.Content>
<Item.Title>SvelteKit</Item.Title>
<Item.Description>Full-stack framework for Svelte</Item.Description>
</Item.Content>
</Item.Root>Header
Item.Header spans full width and sits above the media/content row — useful for section labels or metadata.
Your last session was 2 hours ago
<script lang="ts">
import * as Item from '$lib/components/ui/item';
import { Badge } from '$lib/components/ui/badge';
import { Icon } from '$lib/components/ui/icon';
</script>
<Item.Root variant="outline" class="flex-col items-start">
<Item.Header>
<span class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Recent</span>
<Badge variant="secondary">3 new</Badge>
</Item.Header>
<Item.Media variant="icon">
<Icon name="time-line" />
</Item.Media>
<Item.Content>
<Item.Title>Activity log</Item.Title>
<Item.Description>Your last session was 2 hours ago</Item.Description>
</Item.Content>
</Item.Root>Link
Render Item as an <a> element using the child snippet — the row
gains hover styling automatically.
<script lang="ts">
import * as Item from '$lib/components/ui/item';
import { Icon } from '$lib/components/ui/icon';
</script>
<Item.Root variant="outline">
{#snippet child({ props })}
<a href="/docs" {...props}>
<Item.Media variant="icon">
<Icon name="folder-line" />
</Item.Media>
<Item.Content>
<Item.Title>Documentation</Item.Title>
<Item.Description>Browse the component library</Item.Description>
</Item.Content>
<Item.Actions>
<Icon name="arrow-right-s-line" class="size-4 text-muted-foreground" size="1rem" />
</Item.Actions>
</a>
{/snippet}
</Item.Root>Item.Root props
Sub-components (Group, Header, Footer, Content, Title, Description, Actions, Media, Separator) each accept native div attributes.
| Prop | Type | Default | Description |
|---|---|---|---|
'default' | 'outline' | 'muted' | 'default' | Surface. Outline draws a border; muted tints the background; default stays flush. | |
'default' | 'sm' | 'xs' | 'default' | Adjusts padding and internal gap. xs collapses to zero padding inside menus. | |
Snippet<[{ props }]> | — | Render-prop snippet. Use it to render the Item as a different element (e.g. an anchor or Svelte component) while inheriting the merged classes and data attributes. | |
string | — | Merged onto the rendered element via tailwind-merge. |