XDSThumbnail@xds/core · Thumbnail

Usage

Thumbnail displays a compact, square preview of an image attachment. It shows a shimmer effect while uploading, the image on success, and a placeholder icon when no source is set. Use it in chat composers, file upload lists, or anywhere you need a small image preview with optional remove and click actions.

Best practices

GuidancePractices
DoAlways provide a label prop with the file name so the thumbnail and its remove button are accessible to screen readers and show a tooltip on hover.
DoUse isLoading without a src to show a skeleton during initial upload, and isLoading with a src to show a spinner overlay once a preview URL is available.
DoPair onClick with a lightbox or detail view so users can inspect the full image — the thumbnail adds button semantics and a hover shadow automatically.
Don'tDon't use Thumbnail for non-image file types like PDFs or spreadsheets — use a file attachment component with an appropriate icon instead.
Don'tDon't omit alt text when a src is provided — screen readers need a description of the image content, not just the file name from label.

Anatomy

ElementDescription
ImageThe preview image, displayed as a square with cover fit.
PlaceholderAn image silhouette icon shown when no src is provided.
Remove buttonAn overlaid close button in the top-right corner. Appears when onRemove is set. Uses APCA luminance detection to stay visible on any image.
Upload overlayA semi-transparent overlay with a spinner, shown when isLoading is true and a src preview is available.
SkeletonA shimmer animation shown when isLoading is true and no src is set.

Import

ts
import {XDSThumbnail} from '@xds/core/Thumbnail'

Props

PropTypeDescription
src
stringImage source URL.
alt
stringAlt text for the image.
label
stringAccessible label (e.g. file name). Shown as tooltip on hover.
onRemove
(e: React.MouseEvent) => voidCallback for the overlaid remove button.
onClick
(e: React.MouseEvent) => voidClick handler. Adds button semantics and hover shadow.
isLoading
boolean (default: false)Shows skeleton (no src) or upload overlay (with src).
isDisabled
boolean (default: false)Whether the thumbnail is disabled.
xstyle
StyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}.
className
stringCSS class name for the root element. Prefer xstyle for styling — className is provided for integration with non-StyleX systems.
style
CSSPropertiesInline styles for the root element. Prefer xstyle for styling — inline styles bypass StyleX optimization.
data-testid
stringTest selector for automated testing frameworks.

Examples

Common configurations, variations, and states.
Thumbnail — DisabledThumbnails in the disabled state with reduced opacity. The remove button and click handler are suppressed when disabled.
tsx
'use client';
import {XDSThumbnail} from '@xds/core/Thumbnail';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
export default function ThumbnailDisabled() {
return (
<XDSStack direction="vertical" gap={4}>
<XDSStack direction="vertical" gap={1}>
<XDSText type="supporting" color="secondary">
Enabled
</XDSText>
<XDSStack direction="horizontal" gap={3} vAlign="center">
<XDSThumbnail
src="https://lookaside.facebook.com/assets/xds_oss/moody-scene-vertical-2.png"
alt="Bright landscape"
label="landscape.jpg"
onRemove={() => {}}
/>
<XDSThumbnail label="document.pdf" onRemove={() => {}} />
</XDSStack>
</XDSStack>
<XDSStack direction="vertical" gap={1}>
<XDSText type="supporting" color="secondary">
Disabled
</XDSText>
<XDSStack direction="horizontal" gap={3} vAlign="center">
<XDSThumbnail
src="https://lookaside.facebook.com/assets/xds_oss/moody-scene-vertical-2.png"
alt="Bright landscape"
label="landscape.jpg"
onRemove={() => {}}
isDisabled
/>
<XDSThumbnail label="document.pdf" onRemove={() => {}} isDisabled />
</XDSStack>
</XDSStack>
</XDSStack>
);
}
Thumbnail — GalleryA row of clickable thumbnails with labels that open a detail view. Use for image attachment lists where users need to preview and manage uploads.
tsx
'use client';
import {useState} from 'react';
import {XDSThumbnail} from '@xds/core/Thumbnail';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
const ATTACHMENTS = [
{id: 1, src: 'https://lookaside.facebook.com/assets/xds_oss/illustrative-vertical-1.jpg', alt: 'River through a valley', label: 'valley.jpg'},
{id: 2, src: 'https://lookaside.facebook.com/assets/xds_oss/illustrative-vertical-2.jpg', alt: 'Foggy mountain peak', label: 'mountain.jpg'},
{id: 3, src: 'https://lookaside.facebook.com/assets/xds_oss/illustrative-vertical-3.jpg', alt: 'Golden retriever puppy', label: 'puppy.jpg'},
{id: 4, src: 'https://lookaside.facebook.com/assets/xds_oss/illustrative-vertical-4.jpg', alt: 'Bridge at sunset', label: 'bridge.jpg'},
];
export default function ThumbnailGallery() {
const [selected, setSelected] = useState<string | null>(null);
const [items, setItems] = useState(ATTACHMENTS);
return (
<XDSStack direction="vertical" gap={3}>
<XDSText type="supporting" color="secondary">
Click to preview, dismiss to remove
</XDSText>
<XDSStack direction="horizontal" gap={2} vAlign="center">
{items.map(item => (
<XDSThumbnail
key={item.id}
src={item.src}
alt={item.alt}
label={item.label}
onClick={() => setSelected(item.label)}
onRemove={() =>
setItems(prev => prev.filter(i => i.id !== item.id))
}
/>
))}
</XDSStack>
{selected != null && (
<XDSText type="supporting" color="active">
Previewing: {selected}
</XDSText>
)}
</XDSStack>
);
}
Thumbnail — RemovableThumbnails with a remove button overlay. The close button uses APCA luminance detection to stay visible on both dark and light images.
tsx
'use client';
import {useState} from 'react';
import {XDSThumbnail} from '@xds/core/Thumbnail';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
const IMAGES = [
{id: 1, src: 'https://lookaside.facebook.com/assets/xds_oss/moody-scene-vertical-1.png', alt: 'Dark cityscape at night', label: 'dark-city.jpg'},
{id: 2, src: 'https://lookaside.facebook.com/assets/xds_oss/moody-scene-vertical-2.png', alt: 'Bright snowy landscape', label: 'snow.jpg'},
{id: 3, src: 'https://lookaside.facebook.com/assets/xds_oss/moody-home-vertical-1.png', alt: 'Warm sunset over mountains', label: 'sunset.jpg'},
];
export default function ThumbnailRemovable() {
const [items, setItems] = useState(IMAGES);
return (
<XDSStack direction="vertical" gap={4}>
<XDSText type="supporting" color="secondary">
Remove button adapts contrast to image luminance
</XDSText>
<XDSStack direction="horizontal" gap={3} vAlign="center">
{items.map(item => (
<XDSThumbnail
key={item.id}
src={item.src}
alt={item.alt}
label={item.label}
onRemove={() =>
setItems(prev => prev.filter(i => i.id !== item.id))
}
/>
))}
{items.length === 0 && (
<XDSText type="supporting" color="secondary">
All removed.
</XDSText>
)}
</XDSStack>
</XDSStack>
);
}
Thumbnail — StatesAll visual states side by side: image loaded, placeholder, skeleton loading, and upload overlay. Demonstrates the full lifecycle of a thumbnail from empty to loaded.
tsx
'use client';
import {XDSThumbnail} from '@xds/core/Thumbnail';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
export default function ThumbnailStates() {
return (
<XDSStack direction="vertical" gap={4}>
<XDSStack direction="vertical" gap={1}>
<XDSText type="supporting" color="secondary">
Lifecycle: empty → uploading → processing → loaded
</XDSText>
<XDSStack direction="horizontal" gap={3} vAlign="end">
<XDSStack direction="vertical" gap={1} hAlign="center">
<XDSThumbnail label="report.pdf" />
<XDSText type="supporting" color="secondary">
Placeholder
</XDSText>
</XDSStack>
<XDSStack direction="vertical" gap={1} hAlign="center">
<XDSThumbnail isLoading label="uploading.jpg" />
<XDSText type="supporting" color="secondary">
Skeleton
</XDSText>
</XDSStack>
<XDSStack direction="vertical" gap={1} hAlign="center">
<XDSThumbnail
src="https://lookaside.facebook.com/assets/xds_oss/moody-home-vertical-1.png"
alt="Mountain landscape"
isLoading
label="landscape.jpg"
/>
<XDSText type="supporting" color="secondary">
Uploading
</XDSText>
</XDSStack>
<XDSStack direction="vertical" gap={1} hAlign="center">
<XDSThumbnail
src="https://lookaside.facebook.com/assets/xds_oss/moody-home-vertical-1.png"
alt="Mountain landscape"
label="landscape.jpg"
/>
<XDSText type="supporting" color="secondary">
Loaded
</XDSText>
</XDSStack>
</XDSStack>
</XDSStack>
</XDSStack>
);
}

Showcase source

tsx
'use client';
import {XDSThumbnail} from '@xds/core/Thumbnail';
export default function ThumbnailShowcase() {
return (
<XDSThumbnail
src="https://lookaside.facebook.com/assets/xds_oss/moody-scene-vertical-2.png"
alt="Sample image"
label="photo.jpg"
/>
);
}