# Screens and blocks (/docs/concepts/screens-and-blocks)



Screens and blocks are the two structural units of an Appbrew app. A screen is a named container; blocks are the components it renders, in order, from config.

## Screens [#screens]

Every screen has a `screenId`: `home`, `cart`, `payment`, or an id generated for a custom screen. The config decides which screens exist, what each contains, and how it is reached (tab, route, or modal). Screens can also carry a header configuration, authentication requirements (signed-in only), modal presentation, and style overrides such as background and insets.

## Block anatomy [#block-anatomy]

Each block in a screen's list is a config object:

```json
{
  "componentId": "image-banner",
  "instanceId": "hero-banner-1",
  "source": {
    "item": {
      "src": "https://cdn.shopify.com/...",
      "link": { "kind": "screen", "value": "collection-123" }
    }
  },
  "style": {
    "image": { "aspectRatio": 1.5, "resizeMode": "cover" }
  },
  "options": {
    "roundness": 8
  }
}
```

* **`componentId`** — the block *type*. It names which React component to render, looked up in the block registry.
* **`instanceId`** — unique per placement. The same block type can appear many times on a screen; each placement gets its own `instanceId` and its own config.
* **`source`** — the block's data and content: images, text, product handles, links.
* **`style`** — visual styling: colors, spacing, typography, aspect ratios.
* **`options`** — behavior flags: feature toggles, offsets, selectors.

## How blocks render [#how-blocks-render]

When a screen loads, the app walks its block list in order. For each block it:

1. Evaluates visibility rules — a block can declare a `visibility` condition (for example, based on product tags or product type) and is skipped when it doesn't match.
2. Looks up `componentId` in the block registry and renders the matching component.
3. The component reads its own `source`, `style`, and `options` through hooks (see [State and data](/docs/concepts/state-and-data)).

<Callout type="warn" title="Unregistered blocks render nothing">
  If a `componentId` has no entry in the block registry, that block renders nothing and a console error is logged. If a block you placed in config isn't showing up, check that its type is registered in the app before debugging anything else.
</Callout>

## The BaseBlockProps contract [#the-baseblockprops-contract]

Every block component receives the same props from the renderer. This is the real interface, from `@gauntlet/types`:

```ts
import { ReactElement } from 'react'

export interface BaseBlockProps {
  componentId: string
  instanceId: string
  screenId: string
  setDynamicFooter?: (element: ReactElement) => void
  index: number
  length: number
}
```

* **`componentId` / `instanceId` / `screenId`** — together identify exactly which config entry this render belongs to. You pass them to hooks like `useBlock` to fetch this instance's `source`, `style`, and `options`.
* **`index` / `length`** — the block's position in the screen's list and how many blocks the screen has. Useful for first/last styling such as trimming a top margin.
* **`setDynamicFooter`** — optional callback a block can use to pin an element to the screen's footer.

The props carry identity only: no `source`, no `style`, no `options`. Blocks fetch their own config through hooks. This keeps every block self-contained and lets the renderer stay generic.

```tsx
import { BaseBlockProps } from '@gauntlet/types'
import { useBlock, useBlockSettings } from '@gauntlet/state'

export function MyBlock(props: BaseBlockProps) {
  const block = useBlock(props.screenId, props.componentId, props.instanceId)
  const { source, options } = useBlockSettings(block)

  if (!source?.title) return null
  // render from source, options
}
```

To build one, see [Custom blocks](/docs/build/custom-blocks).
