UI componentsdocs/ui/components/media.md

Media

Images, video, galleries and device frames.

← Component reference

Image #

Images, figures and background images.

Image #

Responsive image: lazy-loaded by default, with fit, radius, srcset and sizes support.

Placeholder photo
<Image
    src="/images/photo.jpg"
    alt="Placeholder photo"
    width={320}
    radius={8}
/>
PropTypeRequiredDefaultDescription
srcstringyesImage URL.
altstring""Alternative text; leave empty only for decorative images.
decoding"auto" | "async" | "sync""async"Native decoding hint.
fit"cover" | "contain"How the image fills its box when both width and height are set.
fullwidthbooleanFull width of the container (default true).
heightstring | numberHeight (pixels or any CSS length); also reserves space before loading.
loading"lazy" | "eager""lazy"Native lazy loading (default lazy); use eager for above-the-fold media.
radiusstring | numberCorner radius (pixels or any CSS length).
roundedbooleanFully round (for avatars and logos).
sizesstringDisplay sizes for srcset (native sizes).
srcsetstringResponsive image candidates (native srcset).
widthstring | numberWidth (pixels or any CSS length); also reserves space before loading.

Figure #

Figure with an optional caption.

Build time chart
Build times dropped 40% after caching dependencies.
<Figure caption="Build times dropped 40% after caching dependencies.">
    <img src="/images/chart.jpg" alt="Build time chart" width="480" height="240" />
</Figure>

Slots: root caption

PropTypeRequiredDefaultDescription
captioncontentCaption under the content (<figcaption>).
childrencontentThe image or other figure content.

BackgroundImage #

No description.

Content over a background image.
<BackgroundImage src="/images/background.jpg" radius>
    <Box style={{ margin: "2rem", "max-width": "18rem" }}>Content over a background image.</Box>
</BackgroundImage>
PropTypeRequiredDefaultDescription
srcstringyesImage URL (data URIs work too).
childrencontentContent drawn over the image.
radiusbooleanRound the corners (theme radius).

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

VideoFrame #

Responsive iframe embed.

<VideoFrame title="Product tour" src="about:blank" ratio={16 / 9} />

Slots: root frame

PropTypeRequiredDefaultDescription
srcstringyesEmbed URL (YouTube, Vimeo, or any page that can be framed).
rationumber16Width divided by height, e.g. 16 / 9.
titlestring"Video"Accessible title of the embedded frame.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Galleries #

Carousels, thumbnails and image grids.

Previous/next carousel; controlled when value is set. ← → switch slides.

<Carousel
    label="Highlights"
    slides={[
        {
            id: "s1",
            content: (
                <img src="/images/signals.jpg" alt="Signals" width="640" height="280" />
            ),
        },
        {
            id: "s2",
            content: <img src="/images/ssr.jpg" alt="SSR" width="640" height="280" />,
        },
        {
            id: "s3",
            content: (
                <img src="/images/theming.jpg" alt="Theming" width="640" height="280" />
            ),
        },
    ]}
/>

Slots: root control controls slide status viewport

PropTypeRequiredDefaultDescription
slidesCarouselSlide[]yesThe slides, in order.
labelstring"Carousel"Accessible name (default "Carousel").
onChange(id: string) => voidCalled with the id of the slide being shown.
valuestringId of the shown slide (controlled); uncontrolled when unset.

CarouselSlide

FieldTypeRequiredDescription
idstringyesSlide id, passed to onChange and matched against value.
contentcontentyesSlide content.

Thumbnav #

Thumbnail picker.

Forest trail
function Example() {
    const photos = [
        { id: "lake", src: "/images/lake.jpg", alt: "Lake at dawn" },
        { id: "forest", src: "/images/forest.jpg", alt: "Forest trail" },
        { id: "desert", src: "/images/desert.jpg", alt: "Desert dunes" },
        { id: "city", src: "/images/city.jpg", alt: "City at night" },
    ];
    const photo = signal("forest");
    const current = () => photos.find((ph) => ph.id === photo()) ?? photos[0];
    return (
        <Stack gap="0.75rem" style={{ "max-width": "24rem" }}>
            <img
                src={current()?.src}
                alt={current()?.alt}
                width="640"
                height="400"
                style={{ width: "100%", height: "auto", "border-radius": "var(--a-radius)" }}
            />
            <Thumbnav label="Photos" items={photos} value={photo()} onChange={photo.set} />
        </Stack>
    );
}

Slots: root image item

PropTypeRequiredDefaultDescription
itemsThumbnavItem[]yesThe thumbnails, in order.
onChange(id: string) => voidyesCalled with the id the user picks.
valuestringyesId of the selected thumbnail.
labelstring"Thumbnails"Accessible name (default "Thumbnails").

ThumbnavItem

FieldTypeRequiredDescription
idstringyesThumbnail id, passed to onChange and matched against value.
srcstringyesThumbnail image URL.
altstringAlternative text (the button's accessible name).

GalleryGrid #

Image grid.

<GalleryGrid columns={3}>
    <img src="/images/a.jpg" alt="Gallery A" width="320" height="240" />
    <img src="/images/b.jpg" alt="Gallery B" width="320" height="240" />
    <img src="/images/c.jpg" alt="Gallery C" width="320" height="240" />
</GalleryGrid>
PropTypeRequiredDefaultDescription
childrencontentImages or tiles.
columns2 | 3 | 43Number of columns.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

BeforeAfter #

Image compare slider.

After redesign
Before redesign
<BeforeAfter
    label="Compare designs"
    before="/images/before.jpg"
    beforeAlt="Before redesign"
    after="/images/after.jpg"
    afterAlt="After redesign"
    initial={50}
/>

Slots: root handle image overlay range

PropTypeRequiredDefaultDescription
afterstringyesImage URL for the "after" state.
beforestringyesImage URL for the "before" state.
afterAltstring"After"Alternative text for the "after" image.
beforeAltstring"Before"Alternative text for the "before" image.
initialnumber50Starting divider position in percent.
labelstring"Compare"Accessible name for the slider (default "Compare").

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Logo cloud & marquee #

Rows of logos and scrolling tickers.

LogoCloud #

Row of partner logos / names.

AcmeGlobexInitechUmbrella
<LogoCloud items={["Acme", "Globex", "Initech", "Umbrella"]} />

Slots: root item

PropTypeRequiredDefaultDescription
itemsunknown[]yesLogos: names or content (images).

Marquee #

Seamless ticker: content is rendered twice so the −50% keyframe loops without a gap; the copy is hidden from assistive tech.

Arachne 2.4 · dark theme · 330+ components · SSR & hydration · full customization
Acme · Globex · Initech · Umbrella · Hooli · Stark Industries
<Stack gap="0.75rem">
    <Marquee pauseOnHover>
        Arachne 2.4 · dark theme · 330+ components · SSR & hydration · full customization
    </Marquee>
    <Marquee variant="plain" speed="slow">
        Acme · Globex · Initech · Umbrella · Hooli · Stark Industries
    </Marquee>
</Stack>

Slots: root group track

PropTypeRequiredDefaultDescription
childrencontentContent to scroll (rendered twice for a seamless loop).
pauseOnHoverbooleanPause scrolling while hovered or focused (default true).
speed"normal" | "slow" | "fast""normal"Scroll speed.
variant"soft" | "plain""soft""soft" (default) sits on a tinted, bordered strip; "plain" has no background or border.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Device frames #

Browser and phone mock-ups.

BrowserFrame #

Browser chrome mock-up.

https://acme.arachne.app
Site preview
<BrowserFrame url="https://acme.arachne.app">
    <img src="/images/preview.jpg" alt="Site preview" width="640" height="300" />
</BrowserFrame>

Slots: root bar body url

PropTypeRequiredDefaultDescription
childrencontentPage content (a screenshot fills the frame).
urlstring"https://example.com"Address shown in the bar.

PhoneFrame #

Phone mock-up.

App screen
<PhoneFrame>
    <img src="/images/app.jpg" alt="App screen" width="300" height="600" />
</PhoneFrame>

Slots: root body notch

PropTypeRequiredDefaultDescription
childrencontentScreen content (a screenshot fills the screen).

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.