Gallery

Responsive grid for displaying images and media.

import { Gallery } from 'ui-lab-components/gallery';
Columns3
Gap TokenMedium
Items4
import { Gallery } from 'ui-lab-components/gallery';
 
const items = [
  { id: 'g1', title: 'Brand Kit', description: 'Identity · Updated 2d ago' },
  { id: 'g2', title: 'Icon Set', description: 'UI Assets · 142 icons' },
  { id: 'g3', title: 'Type Scale', description: 'Typography · 8 weights' },
  { id: 'g4', title: 'Color Tokens', description: 'Design System · v3.2' },
];
 
export default function Example() {
  return (
    <Gallery columns={3}>
      {items.map((item) => (
        <Gallery.Item key={item.id}>
          <Gallery.View aspectRatio="4/3" className="overflow-hidden rounded-sm bg-background-900" />
          <Gallery.Body>
            <strong>{item.title}</strong>
            <p>{item.description}</p>
          </Gallery.Body>
        </Gallery.Item>
      ))}
    </Gallery>
  );
}
 

gallery/02-item-orientation

Gallery items can be oriented vertically (stacked view + body) or horizontally (side-by-side view + body).

Loading interactive preview
"use client";
 
import type { CSSProperties } from 'react';
import { Gallery } from 'ui-lab-components/gallery';
import { Frame } from 'ui-lab-components/frame';
 
const BASE_CELL_STYLE = {
  '--fill': 'var(--background-900)',
  '--stroke-color': 'var(--background-600)',
} as CSSProperties;
 
function GalleryViewFrame({ orientation }: { orientation: 'vertical' | 'horizontal' }) {
  return (
    <Frame
      className={orientation === 'horizontal' ? 'shrink-0 p-px' : 'p-px'}
      pathStroke="dashed"
      strokeAlign="inside"
      style={{
        ...BASE_CELL_STYLE,
        ...(orientation === 'horizontal' ? { width: 'var(--view-width)', flexShrink: 0 } : {}),
      }}
    >
      <Gallery.View
        aspectRatio={orientation === 'horizontal' ? '1/1' : '4/3'}
        className="overflow-hidden rounded-sm bg-background-900"
        style={orientation === 'horizontal' ? { width: '100%' } : undefined}
      />
    </Frame>
  );
}
 
function GalleryBodyFrame({ orientation }: { orientation: 'vertical' | 'horizontal' }) {
  return (
    <Frame
      className={orientation === 'horizontal' ? 'min-w-0 flex-1 w-auto' : undefined}
      pathStroke="dashed"
      strokeAlign="inside"
      style={BASE_CELL_STYLE}
    >
      <Gallery.Body>
      </Gallery.Body>
    </Frame>
  );
}
 
export default function Example() {
  return (
    <div className="flex h-full w-[min(100%,640px)] min-h-0 min-w-0 flex-col gap-4">
      <Gallery columns={3} gap="sm" className="w-full">
        {Array.from({ length: 3 }, (_, index) => (
          <Gallery.Item key={index} orientation="vertical" className="bg-background-950 border-background-700 p-2" styles={{ root: 'gap-2' }}>
            <GalleryViewFrame orientation="vertical" />
            <GalleryBodyFrame orientation="vertical" />
          </Gallery.Item>
        ))}
      </Gallery>
 
      <Gallery columns={1} gap="sm" className="w-full">
        {Array.from({ length: 3 }, (_, index) => (
          <Gallery.Item
            key={index}
            orientation="horizontal"
            className="bg-background-950 border-background-700 p-2"
            styles={{ root: 'gap-2' }}
            style={{ '--view-width': '160px' } as CSSProperties}
          >
            <GalleryViewFrame orientation="horizontal" />
            <GalleryBodyFrame orientation="horizontal" />
          </Gallery.Item>
        ))}
      </Gallery>
    </div>
  );
}
 

gallery/03-span-layout

A featured item spans multiple columns and rows to create an editorial grid layout.

Loading interactive preview
"use client";
 
import type { CSSProperties } from 'react';
import { Gallery } from 'ui-lab-components/gallery';
import { Frame } from 'ui-lab-components/frame';
 
const BASE_CELL_STYLE = {
  '--fill': 'var(--background-900)',
  '--stroke-color': 'var(--background-600)',
} as CSSProperties;
 
const VIEW_FRAME_STYLE = {
  ...BASE_CELL_STYLE,
  '--fill': 'var(--background-900)',
} as CSSProperties;
 
function GalleryViewFrame({ featured = false }: { featured?: boolean }) {
  return (
    <Frame className={`${featured ? 'order-1 ' : ''} min-h-0 flex-1 p-px`} pathStroke="dashed" strokeAlign="inside" style={VIEW_FRAME_STYLE}>
      <div className="h-full">
        <Gallery.View
          aspectRatio={featured ? '16/9' : '4/3'}
          className="h-full w-full overflow-hidden rounded-sm bg-background-900"
          style={featured ? { aspectRatio: 'auto' } : undefined}
        />
      </div>
    </Frame>
  );
}
 
function GalleryBodyFrame({ featured = false }: { featured?: boolean }) {
  return (
    <Frame className={`${featured ? 'order-2 ' : ''}shrink-0`} pathStroke="dashed" strokeAlign="inside" style={BASE_CELL_STYLE}>
      <Gallery.Body>
      </Gallery.Body>
    </Frame>
  );
}
 
export default function Example() {
  return (
    <div className="flex w-[min(100%,640px)] min-w-0 flex-col">
      <Gallery columns={3} gap="md" className="w-full">
        <Gallery.Item columnSpan={2} rowSpan={2} className="bg-background-950 border-background-700 p-2" style={{ gap: 8 }} styles={{ root: 'gap-2' }}>
          <GalleryViewFrame featured />
          <GalleryBodyFrame featured />
        </Gallery.Item>
 
        {Array.from({ length: 4 }, (_, i) => (
          <Gallery.Item key={i} className="bg-background-950 border-background-700 p-2" style={{ gap: 8 }} styles={{ root: 'gap-2' }}>
            <GalleryViewFrame />
            <GalleryBodyFrame />
          </Gallery.Item>
        ))}
      </Gallery>
    </div>
  );
}