Platform Blocks Layout
Layout primitives from @platform-blocks/ui for React Native (iOS/Android/Web).
Everything imports from the package root:
import { Flex, Row, Column, Grid, GridItem, Card, Surface, Block, AppShell } from '@platform-blocks/ui';
Four reference files sit alongside this one:
references/api.md — the curated API: how the pieces compose, the union
types, the defaults that bite. Read this first.
references/props.md — generated, exhaustive prop tables for every component
in this skill. Look here for the complete surface of a single prop.
references/icons.md — generated, every name the built-in Icon registry
accepts. Check it before writing <Icon name="…">; unlisted names render
nothing.
references/patterns.md — complete copy-paste screens.
Core primitives
| Component |
What it is |
Key defaults |
Flex |
Flexbox View |
direction="row", align="flex-start", justify="flex-start", wrap="nowrap", gap="sm" |
Row |
Flex with direction="row" |
gap="sm" |
Column |
Flex with direction="column" |
gap="sm", fullWidth defaults to true |
Grid / GridItem |
12-column responsive grid |
columns={12}, gap={0} (no gap by default) |
Block |
Polymorphic styled box (bg, radius, shadow, positioning) |
gap="sm" when flex |
Card |
Padded surface with variants + Card.Section |
variant="filled", padding="md" (12px), radius="md" |
Surface |
Elevation-ladder container (level 0–3) |
no padding, withBorder="auto" |
AppShell |
App chrome: header/navbar/aside/footer/bottomNav/main |
responsive, safe-area aware |
Typical screen skeleton:
<ScrollView contentContainerStyle={{ padding: 20 }}>
<Column gap="xl">
<Column gap="sm">
<Title order={1}>Screen title</Title>
<Text colorVariant="secondary">Subtitle</Text>
</Column>
<Card variant="elevated" p="lg">
<Column gap="md">{/* card content */}</Column>
</Card>
</Column>
</ScrollView>
Spacing system props
All layout components (Flex/Row/Column/Grid/GridItem/Card/Surface/Block/Masonry) accept
SpacingProps: m mx my mt mr mb ml and p px py pt pr pb pl.
Values (SpacingValue): a token 'xs'|'sm'|'md'|'lg'|'xl'|'2xl'|'3xl', a number
(raw px), 'auto', or 0. Token scale: xs=4, sm=8, md=12, lg=16, xl=20, 2xl=24, 3xl=32.
gap / rowGap / columnGap take a SizeValue — the same tokens or a number
(gap="md" or gap={16}). Percentages/strings like '10%' are NOT valid gap/spacing values.
Sizing props (LayoutProps, on Flex/Row/Column/Card/Surface): fullWidth, w, h,
maxW, minW, maxH, minH (React Native DimensionValue — numbers or '50%').
w wins over fullWidth.
RTL is handled for you: ml/mr/pl/pr map to logical start/end, and Flex row
directions mirror automatically (opt out with disableRTLMirroring).
Responsive layouts
Grid span/columns accept a plain number or a mobile-first breakpoint object with keys
{ base, sm, md, lg, xl } (no xs key here). Thresholds for Grid resolution
(DEFAULT_BREAKPOINTS): base 0, sm 480, md 640, lg 960, xl 1200.
<Grid columns={12} gap="md">
<GridItem span={{ base: 12, md: 6, lg: 4 }}>
<Card variant="elevated" p="md">…</Card>
</GridItem>
</Grid>
Breakpoint hook for conditional rendering — useBreakpoint() returns
'xs' | 'sm' | 'md' | 'lg' | 'xl' (thresholds: xs 0, sm 576, md 768, lg 992, xl 1200):
const breakpoint = useBreakpoint();
const isMobile = breakpoint === 'xs' || breakpoint === 'sm';
return isMobile ? <Column gap="md">{panes}</Column> : <Row gap="lg">{panes}</Row>;
BreakpointProvider is mounted automatically by PlatformBlocksProvider — no setup needed.
Helpers resolveResponsiveProp(value, width) and resolveResponsiveValue(value, breakpoint)
are exported for resolving responsive objects manually.
AppShell responsive sizes (headerHeight, navbarWidth, …) use ResponsiveSize
objects that additionally allow an xs key: { base?, xs?, sm?, md?, lg?, xl? }.
Cards and surfaces
Card variants: 'filled' (default) 'outline' 'elevated' 'subtle' 'ghost' 'gradient'.
Inner padding via padding (token/number, default 'md') — the spacing prop p also
works and overrides it. onPress makes the whole card pressable. withBorder adds a
1px border on any variant. Card.Section (direct child only) escapes the card padding
for full-bleed images/banded rows; set clip on the Card to round its corners.
Surface drives background + border + shadow together from an elevation level (0–3);
raised puts a nested surface one level above its parent automatically.
Block is the low-level styled box: bg, radius (incl. 'full'), shadow,
opacity, absolute position/top/left/zIndex, fluid (flex:1), polymorphic
component prop.
App shell / navigation chrome
Two ways to build app chrome:
Component composition — <AppShell header={{height:60}} navbar={{width:240, breakpoint:'md'}}>
with children AppShell.Header, AppShell.Navbar, AppShell.Aside, AppShell.Footer,
AppShell.BottomNav, AppShell.Main, AppShell.Section — or autoLayout with
headerContent/navbarContent/bottomNavItems props. Handles safe areas, mobile
drawer vs desktop rail, collapse/expand-on-hover. Hooks: useAppShell() (layout
context: isMobile, breakpoint, toggleNavbar…), useNavbarHover().
Layout blueprints — the declarative engine. defineAppLayout({...}) describes the
whole shell (sections as { component | render, props, show(ctx) } entries, responsive
breakpoints, main config, overlays); mount it once with
<AppLayoutProvider blueprint={...} value={{ query, pathname, navigation }}> wrapping
<AppLayoutRenderer>{routes}</AppLayoutRenderer>. Every entry receives an
AppLayoutRuntimeContext (breakpoint, isMobile, orientation, platform,
pathname, query, theme, colorScheme) so visibility and props can react to
route and viewport. See patterns.md for a full blueprint.
Pitfalls (verified against source)
No flex prop on Flex/Row/Column — use grow={1} or style={{ flex: 1 }}.
(Block does have fluid for flex:1.)
Column is fullWidth by default; plain Flex/Row are not — children of a
Row shrink-wrap unless you add fullWidth / grow.
Flex defaults align="flex-start", not stretch like raw RN Views — set
align="stretch" explicitly when children should fill the cross axis.
Grid defaults gap={0} (Flex defaults gap="sm") — pass gap="md" explicitly.
GridItem without span renders at span 1 (1/12 width) — always set span.
Grid responsive keys are {base, sm, md, lg, xl} — xs is silently ignored in
Grid span/columns objects (only AppShell ResponsiveSize accepts xs).
Two breakpoint scales exist: Grid resolves at 480/640/960/1200; useBreakpoint()
reports at 576/768/992/1200. Don't expect them to flip at exactly the same width.
hiddenFrom/visibleFrom exist in the prop types but are not applied by the layout
primitives — hide responsively by conditional rendering with useBreakpoint().
Card.Section must be a direct child of Card (fragment/View wrappers break the
first/last padding detection).
gap/spacing tokens top out at '3xl' (32) — for bigger separations use numbers.
Grid breaks under static web rendering (SSR / prerender). Grid resolves
columns and GridItem span against useWindowDimensions() at render time.
During Expo Router's static web export there is no window, so width is 0, the
base value wins, and those percentage widths are baked into the exported
HTML — and hydration does not patch them, so a prerendered page stays stacked
at every viewport. For any route that is statically rendered, build breakpoint
layouts with a wrapping Flex + flexBasis instead, which needs no width
measurement:
// SSR-correct responsive grid — pure CSS wrapping, no width measurement.
// flexBasis sets the target column width; flexGrow lets the last row fill.
<Flex direction="row" wrap="wrap" gap="md">
{features.map((feature) => (
<Card
key={feature.title}
variant="elevated"
p="lg"
style={{ flexBasis: 320, flexGrow: 1 }}
>
{/* … */}
</Card>
))}
</Flex>
This is what universal-template's FeatureGrid ships, for exactly this
reason.
Grid is fine in native apps and in client-only web routes. Note that
useBreakpoint() does not have this problem — it lives in the other
breakpoint system (core/responsive), which explicitly falls back to a desktop
width when window is absent and recomputes on mount. Grid uses
core/theme/breakpoints + useWindowDimensions(), which has no such guard.
Anything this skill does not cover
This skill covers layout primitives, the responsive system, and app chrome.
Platform Blocks is much larger — 97 components, 25 charts, and 18 hooks. Do not
guess an API for something outside this scope; fetch the generated docs instead:
| What you need |
Where |
| Index of every page, one line each |
https://platform-blocks.com/llms.txt |
| One component or chart |
https://platform-blocks.com/llms/components/<Name>.md |
| One hook |
https://platform-blocks.com/llms/hooks/<useName>.md |
| Guides |
https://platform-blocks.com/llms/guides/{getting-started,accessibility,localization}.md |
| Everything in one file (~1.3 MB) |
https://platform-blocks.com/llms-full.txt |
<Name> is the exact PascalCase export name — .../llms/components/DataTable.md,
.../llms/components/AreaChart.md. Each page carries the component's full prop
table (type, required, default, description) plus runnable examples, generated
from the source, so it is authoritative where memory is not. When you are unsure
whether something exists or what it is called, read llms.txt first — it lists
every page with a one-line summary.
Import paths: components come from the package root (import { X } from '@platform-blocks/ui'). The exceptions are subpath-only: FormLayout
(@platform-blocks/ui/FormLayout), AudioPlayer
(@platform-blocks/ui/AudioPlayer), and the whole Navigation module —
NavigationContainer, createStackNavigator, createDrawerNavigator,
Screen, useNavigation, useRoute (@platform-blocks/ui/Navigation). A few
utilities also live on subpaths (e.g. validationRules on
@platform-blocks/ui/Input). A docs page existing does not guarantee a root
export — HoverCard, for instance, is internal and has no page and no export.
Notably outside this skill:
- Navigation components —
Tabs, Breadcrumbs, Pagination, Stepper,
Menu, Spotlight, TableOfContents, Tree, Link.
- Data display —
Table, DataTable, DataList, Timeline, Badge,
Chip, Avatar, Accordion, Collapse, ListGroup, Carousel, Markdown.
- Overlays and feedback —
Dialog, Popover, Tooltip, ContextMenu,
Toast, Alert, Progress, Loader, Skeleton, LoadingOverlay.
- Media —
Image, Gallery, Video, Waveform, and AudioPlayer
(subpath-only: import { AudioPlayer } from '@platform-blocks/ui/AudioPlayer').
- Install and provider wiring → the
platform-blocks-setup skill. Theme
tokens → platform-blocks-theming. Forms → platform-blocks-forms.
1---2name: platform-blocks-layout3description: Build screen layouts with the @platform-blocks/ui React Native library. Use when composing screens with Flex/Row/Column/Grid, building responsive breakpoint-based layouts (GridItem span={{base, sm, md, lg, xl}}, useBreakpoint), applying spacing system props (p, px, py, m, mt, gap), assembling app shell / navigation chrome (AppShell, defineAppLayout blueprints), or laying out content in Cards, Surfaces, and Blocks.4---56# Platform Blocks Layout78Layout primitives from `@platform-blocks/ui` for React Native (iOS/Android/Web).9Everything imports from the package root:1011```tsx12import { Flex, Row, Column, Grid, GridItem, Card, Surface, Block, AppShell } from '@platform-blocks/ui';13```1415Four reference files sit alongside this one:1617- `references/api.md` — the curated API: how the pieces compose, the union18 types, the defaults that bite. **Read this first.**19- `references/props.md` — generated, exhaustive prop tables for every component20 in this skill. Look here for the complete surface of a single prop.21- `references/icons.md` — generated, every `name` the built-in `Icon` registry22 accepts. Check it before writing `<Icon name="…">`; unlisted names render23 nothing.24- `references/patterns.md` — complete copy-paste screens.2526## Core primitives2728| Component | What it is | Key defaults |29| --- | --- | --- |30| `Flex` | Flexbox `View` | `direction="row"`, `align="flex-start"`, `justify="flex-start"`, `wrap="nowrap"`, `gap="sm"` |31| `Row` | `Flex` with `direction="row"` | `gap="sm"` |32| `Column` | `Flex` with `direction="column"` | `gap="sm"`, **`fullWidth` defaults to `true`** |33| `Grid` / `GridItem` | 12-column responsive grid | `columns={12}`, `gap={0}` (no gap by default) |34| `Block` | Polymorphic styled box (`bg`, `radius`, `shadow`, positioning) | `gap="sm"` when flex |35| `Card` | Padded surface with variants + `Card.Section` | `variant="filled"`, `padding="md"` (12px), `radius="md"` |36| `Surface` | Elevation-ladder container (`level` 0–3) | no padding, `withBorder="auto"` |37| `AppShell` | App chrome: header/navbar/aside/footer/bottomNav/main | responsive, safe-area aware |3839Typical screen skeleton:4041```tsx42<ScrollView contentContainerStyle={{ padding: 20 }}>43 <Column gap="xl">44 <Column gap="sm">45 <Title order={1}>Screen title</Title>46 <Text colorVariant="secondary">Subtitle</Text>47 </Column>48 <Card variant="elevated" p="lg">49 <Column gap="md">{/* card content */}</Column>50 </Card>51 </Column>52</ScrollView>53```5455## Spacing system props5657All layout components (Flex/Row/Column/Grid/GridItem/Card/Surface/Block/Masonry) accept58`SpacingProps`: `m mx my mt mr mb ml` and `p px py pt pr pb pl`.59Values (`SpacingValue`): a token `'xs'|'sm'|'md'|'lg'|'xl'|'2xl'|'3xl'`, a number60(raw px), `'auto'`, or `0`. Token scale: xs=4, sm=8, md=12, lg=16, xl=20, 2xl=24, 3xl=32.6162`gap` / `rowGap` / `columnGap` take a `SizeValue` — the same tokens or a number63(`gap="md"` or `gap={16}`). Percentages/strings like `'10%'` are NOT valid gap/spacing values.6465Sizing props (`LayoutProps`, on Flex/Row/Column/Card/Surface): `fullWidth`, `w`, `h`,66`maxW`, `minW`, `maxH`, `minH` (React Native `DimensionValue` — numbers or `'50%'`).67`w` wins over `fullWidth`.6869RTL is handled for you: `ml/mr/pl/pr` map to logical start/end, and `Flex` row70directions mirror automatically (opt out with `disableRTLMirroring`).7172## Responsive layouts7374**Grid span/columns** accept a plain number or a mobile-first breakpoint object with keys75`{ base, sm, md, lg, xl }` (no `xs` key here). Thresholds for Grid resolution76(`DEFAULT_BREAKPOINTS`): base 0, sm 480, md 640, lg 960, xl 1200.7778```tsx79<Grid columns={12} gap="md">80 <GridItem span={{ base: 12, md: 6, lg: 4 }}>81 <Card variant="elevated" p="md">…</Card>82 </GridItem>83</Grid>84```8586**Breakpoint hook** for conditional rendering — `useBreakpoint()` returns87`'xs' | 'sm' | 'md' | 'lg' | 'xl'` (thresholds: xs 0, sm 576, md 768, lg 992, xl 1200):8889```tsx90const breakpoint = useBreakpoint();91const isMobile = breakpoint === 'xs' || breakpoint === 'sm';92return isMobile ? <Column gap="md">{panes}</Column> : <Row gap="lg">{panes}</Row>;93```9495`BreakpointProvider` is mounted automatically by `PlatformBlocksProvider` — no setup needed.96Helpers `resolveResponsiveProp(value, width)` and `resolveResponsiveValue(value, breakpoint)`97are exported for resolving responsive objects manually.9899**AppShell responsive sizes** (`headerHeight`, `navbarWidth`, …) use `ResponsiveSize`100objects that additionally allow an `xs` key: `{ base?, xs?, sm?, md?, lg?, xl? }`.101102## Cards and surfaces103104- `Card` variants: `'filled'` (default) `'outline'` `'elevated'` `'subtle'` `'ghost'` `'gradient'`.105 Inner padding via `padding` (token/number, default `'md'`) — the spacing prop `p` also106 works and overrides it. `onPress` makes the whole card pressable. `withBorder` adds a107 1px border on any variant. `Card.Section` (direct child only) escapes the card padding108 for full-bleed images/banded rows; set `clip` on the Card to round its corners.109- `Surface` drives background + border + shadow together from an elevation `level` (0–3);110 `raised` puts a nested surface one level above its parent automatically.111- `Block` is the low-level styled box: `bg`, `radius` (incl. `'full'`), `shadow`,112 `opacity`, absolute `position`/`top`/`left`/`zIndex`, `fluid` (flex:1), polymorphic113 `component` prop.114115## App shell / navigation chrome116117Two ways to build app chrome:1181191. **Component composition** — `<AppShell header={{height:60}} navbar={{width:240, breakpoint:'md'}}>`120 with children `AppShell.Header`, `AppShell.Navbar`, `AppShell.Aside`, `AppShell.Footer`,121 `AppShell.BottomNav`, `AppShell.Main`, `AppShell.Section` — or `autoLayout` with122 `headerContent`/`navbarContent`/`bottomNavItems` props. Handles safe areas, mobile123 drawer vs desktop rail, collapse/expand-on-hover. Hooks: `useAppShell()` (layout124 context: `isMobile`, `breakpoint`, `toggleNavbar`…), `useNavbarHover()`.1251262. **Layout blueprints** — the declarative engine. `defineAppLayout({...})` describes the127 whole shell (sections as `{ component | render, props, show(ctx) }` entries, responsive128 `breakpoints`, `main` config, `overlays`); mount it once with129 `<AppLayoutProvider blueprint={...} value={{ query, pathname, navigation }}>` wrapping130 `<AppLayoutRenderer>{routes}</AppLayoutRenderer>`. Every entry receives an131 `AppLayoutRuntimeContext` (`breakpoint`, `isMobile`, `orientation`, `platform`,132 `pathname`, `query`, `theme`, `colorScheme`) so visibility and props can react to133 route and viewport. See patterns.md for a full blueprint.134135## Pitfalls (verified against source)1361371. **No `flex` prop on Flex/Row/Column** — use `grow={1}` or `style={{ flex: 1 }}`.138 (`Block` does have `fluid` for flex:1.)1392. **`Column` is `fullWidth` by default**; plain `Flex`/`Row` are not — children of a140 `Row` shrink-wrap unless you add `fullWidth` / `grow`.1413. **Flex defaults `align="flex-start"`**, not `stretch` like raw RN Views — set142 `align="stretch"` explicitly when children should fill the cross axis.1434. **`Grid` defaults `gap={0}`** (Flex defaults `gap="sm"`) — pass `gap="md"` explicitly.1445. **`GridItem` without `span` renders at span 1** (1/12 width) — always set `span`.1456. **Grid responsive keys are `{base, sm, md, lg, xl}`** — `xs` is silently ignored in146 Grid `span`/`columns` objects (only AppShell `ResponsiveSize` accepts `xs`).1477. **Two breakpoint scales exist**: Grid resolves at 480/640/960/1200; `useBreakpoint()`148 reports at 576/768/992/1200. Don't expect them to flip at exactly the same width.1498. **`hiddenFrom`/`visibleFrom` exist in the prop types but are not applied by the layout150 primitives** — hide responsively by conditional rendering with `useBreakpoint()`.1519. **`Card.Section` must be a direct child** of `Card` (fragment/View wrappers break the152 first/last padding detection).15310. `gap`/spacing tokens top out at `'3xl'` (32) — for bigger separations use numbers.15411. **`Grid` breaks under static web rendering (SSR / prerender).** `Grid` resolves155 `columns` and `GridItem` `span` against `useWindowDimensions()` at render time.156 During Expo Router's static web export there is no window, so width is `0`, the157 `base` value wins, and those percentage widths are **baked into the exported158 HTML** — and hydration does not patch them, so a prerendered page stays stacked159 at every viewport. For any route that is statically rendered, build breakpoint160 layouts with a wrapping `Flex` + `flexBasis` instead, which needs no width161 measurement:162163 ```tsx164 // SSR-correct responsive grid — pure CSS wrapping, no width measurement.165 // flexBasis sets the target column width; flexGrow lets the last row fill.166 <Flex direction="row" wrap="wrap" gap="md">167 {features.map((feature) => (168 <Card169 key={feature.title}170 variant="elevated"171 p="lg"172 style={{ flexBasis: 320, flexGrow: 1 }}173 >174 {/* … */}175 </Card>176 ))}177 </Flex>178 ```179180 This is what `universal-template`'s `FeatureGrid` ships, for exactly this181 reason.182183 `Grid` is fine in native apps and in client-only web routes. Note that184 `useBreakpoint()` does **not** have this problem — it lives in the other185 breakpoint system (`core/responsive`), which explicitly falls back to a desktop186 width when `window` is absent and recomputes on mount. `Grid` uses187 `core/theme/breakpoints` + `useWindowDimensions()`, which has no such guard.188189## Anything this skill does not cover190191This skill covers layout primitives, the responsive system, and app chrome.192Platform Blocks is much larger — 97 components, 25 charts, and 18 hooks. Do not193guess an API for something outside this scope; fetch the generated docs instead:194195| What you need | Where |196| --- | --- |197| Index of every page, one line each | `https://platform-blocks.com/llms.txt` |198| One component or chart | `https://platform-blocks.com/llms/components/<Name>.md` |199| One hook | `https://platform-blocks.com/llms/hooks/<useName>.md` |200| Guides | `https://platform-blocks.com/llms/guides/{getting-started,accessibility,localization}.md` |201| Everything in one file (~1.3 MB) | `https://platform-blocks.com/llms-full.txt` |202203`<Name>` is the exact PascalCase export name — `.../llms/components/DataTable.md`,204`.../llms/components/AreaChart.md`. Each page carries the component's full prop205table (type, required, default, description) plus runnable examples, generated206from the source, so it is authoritative where memory is not. When you are unsure207whether something exists or what it is called, read `llms.txt` first — it lists208every page with a one-line summary.209210Import paths: components come from the package root (`import { X } from211'@platform-blocks/ui'`). The exceptions are subpath-only: `FormLayout`212(`@platform-blocks/ui/FormLayout`), `AudioPlayer`213(`@platform-blocks/ui/AudioPlayer`), and the whole `Navigation` module —214`NavigationContainer`, `createStackNavigator`, `createDrawerNavigator`,215`Screen`, `useNavigation`, `useRoute` (`@platform-blocks/ui/Navigation`). A few216utilities also live on subpaths (e.g. `validationRules` on217`@platform-blocks/ui/Input`). A docs page existing does not guarantee a root218export — `HoverCard`, for instance, is internal and has no page and no export.219220Notably outside this skill:221222- **Navigation components** — `Tabs`, `Breadcrumbs`, `Pagination`, `Stepper`,223 `Menu`, `Spotlight`, `TableOfContents`, `Tree`, `Link`.224- **Data display** — `Table`, `DataTable`, `DataList`, `Timeline`, `Badge`,225 `Chip`, `Avatar`, `Accordion`, `Collapse`, `ListGroup`, `Carousel`, `Markdown`.226- **Overlays and feedback** — `Dialog`, `Popover`, `Tooltip`, `ContextMenu`,227 `Toast`, `Alert`, `Progress`, `Loader`, `Skeleton`, `LoadingOverlay`.228- **Media** — `Image`, `Gallery`, `Video`, `Waveform`, and `AudioPlayer`229 (subpath-only: `import { AudioPlayer } from '@platform-blocks/ui/AudioPlayer'`).230- **Install and provider wiring** → the `platform-blocks-setup` skill. **Theme231 tokens** → `platform-blocks-theming`. **Forms** → `platform-blocks-forms`.