AppbrewDevelopers
Concepts

Screens and blocks

The screen model and the block contract every component follows.

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

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

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

{
  "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

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).

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.

The BaseBlockProps contract

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

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.

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.

On this page