import { Gallery } from "@elabs-ai/components-ai";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-aiPaste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui Gallery component (from @elabs-ai/components-ai) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look Gallery up before writing code: `npx -y @elabs-ai/components-cli docs Gallery` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-ai is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-ai` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-ai`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Avoid
- Letting images pop in with no per-image skeleton — reserve each box (AspectRatio + Skeleton) until onLoad or the grid shifts.
- Rendering a bare grid for an empty list — every list needs a real empty state.
- <img> without explicit width/height — it is the grid's largest CLS source.
Works with
Used inside
Pairs with
Examples
Small grid (3)
A handful of results render as a thumbnail grid (no overflow yet).
Full grid (exactly 5)
Exactly `maxVisible` images fill the grid with no "+N more" tile.
Overflow (+N more)
Beyond the cap, the last cell collapses into "+N more" and opens the carousel.
No metadata
Images with only `alt` — the detail panel shows a graceful "No details".
Long description + many fields
Long copy + many fields scroll inside the panel; the image stays put.
Custom metadata (renderMeta)
`renderMeta` replaces the default panel body with custom content.
Download hidden
`hideDownload` removes every download affordance (rights-restricted assets).
Loading (from empty)
LOADING — the set is still being generated. `loading` renders skeleton tiles in the grid footprint (a status live region for AT) instead of the empty state, so the panel reserves its space and reads as "working" rather than "nothing here" (loading-states.md).
Partial set (arriving)
PARTIAL SET — images stream in one at a time. With `expectedCount`, the arrived tiles render alongside skeleton tiles for the ones still coming, so the grid fills in place with no reflow and no premature "+N more" collapse.
Keyboard focus
Focus indicator on image thumbnails (#313): Tab to an image button and assert the compound indicator's OUTLINE layer — `outlineStyle: "solid"` sourced from `--ring-contour`. The pre-#67 `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring` pattern also paints a non-"none" `boxShadow` (Tailwind's `ring-2` box-shadow trick), so asserting boxShadow alone passes on the legacy class string too; the outline layer is the one thing only `focus-ring`/`focus-ring-inset` add.
Overflow tile keyboard focus
Focus indicator on the overflow ("+N more") tile (#313): a separate DOM node from the plain thumbnails, styled the same way — never covered by any prior test (`gallery.tsx:409`, `GalleryOverflowTile`).
API
Gallery
| Prop | Type | Description |
|---|---|---|
imagesrequired | GalleryImage[] | Images to display. `0` → empty state, `1` → standalone, `≥2` → grid. |
maxVisible | number | Thumbnails to show before collapsing the rest into a "+N more" tile. @default 5 |
aspectRatio | number | Aspect ratio for grid tiles and the standalone image. @default 1 (square) |
hideDownload | boolean | Hide every download affordance (e.g. rights-restricted assets). @default false |
loading | boolean | The set is still arriving (loading-states.md `loading` signal). Renders skeleton tiles in the grid footprint instead of the empty state, and fills the gap up to `expectedCount` (or `maxVisible`) while images stream in. @default false |
expectedCount | number | Total images expected once the set finishes arriving. When `images.length < expectedCount`, the arrived tiles render alongside skeleton tiles for the missing ones, so the grid fills in place (no reflow). |
renderMeta | (image: GalleryImage, index: number) => ReactNode | Replace the default metadata body in the detail panel. Receives the currently-active image, so it stays in sync with the carousel. The panel chrome + title heading remain. |
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| loading | per-image Skeleton until onLoad; expectedCount reserves the tiles |