# React — slots

> Name a position in your UI, let an admin rebind it to a different asset without a deploy.

import { Code } from '@astrojs/starlight/components';
import reactSlots from '../../../../examples/05-react-slots.tsx?raw';

A **slot** is a named position — `"storefront.cr.hero"` — that resolves to
whatever asset is currently bound to it. The component asks for the *name*;
what it points at is data, so an admin can change the hero image without a
deploy, a rebuild, or you.

<Code code={reactSlots} lang="tsx" title="react-slots.tsx" />

## `useSlots`, not `useSlot` in a loop

`useSlots` resolves the whole set in one request. Calling `useSlot` inside a
`map` turns a nine-image gallery into nine round-trips, each with its own
loading state, and they land out of order.

## An unbound slot is a normal state

A slot nobody has bound yet resolves with no URL. That is not an error — it is
what every slot looks like on the day it is added. Render the layout without
the image:

```tsx
if (error || !url) return <div className="hero hero--empty" />;
```

Treating it as a failure means the first person to add a slot to the codebase
breaks the page for everyone until someone remembers to bind it.

## The browser client cannot hold a key

`@nitida/sdk/web` exports an `NitidaClient` whose **type rejects** `apiKey`
and `signingKey`. That is deliberate: the browser talks to *your* route, and
your route holds the key. If you find yourself wanting to pass a key here, the
architecture went wrong one step earlier.