<!-- Skal docs — markdown mirror of https://skal.run/docs/components.html -->

# Components

Every built-in, with the JSX you write and the native render it produces — screenshots straight off the iOS Simulator, regenerated from the [gallery app](https://github.com/skal-multiplatform/skal/tree/main/examples/gallery). Capitalized tags compile to binary ops; styling is props; `testID` and `semanticLabel` ride Flutter's real semantics tree.

[Content](#content) · [Layout](#layout) · [Input](#input) · [Lists](#lists) · [Navigation](#navigation) · [Effects](#effects)

## Content

### <Text> → Text 

Styled text — size, weight, family, color, alignment, maxLines, overflow.

```jsx
<Text label="The quick brown fox" fontSize={24} fontWeight={800} color="#FF1C1C1E" />
<Text label="jumps over the lazy dog." fontSize={15} color="#FF8E8E93" />
<Text label="Truncates with an ellipsis when it runs out of space to work with"
      maxLines={1} textOverflow={1} fontSize={14} color="#FF0A84FF" />
```

![Text rendered natively on iOS](https://skal.run/assets/components/00-text.png)

### <RichText> → Text.rich 

Inline spans — each Text child becomes a styled TextSpan.

```jsx
<RichText>
  <Text label="Rich text " fontSize={17} color="#FF1C1C1E" />
  <Text label="mixes " fontSize={17} color="#FF0A84FF" fontWeight={800} />
  <Text label="size, " fontSize={23} color="#FFFF453A" fontWeight={700} />
  <Text label="weight " fontSize={17} color="#FF30D158" fontWeight={800} />
  <Text label="and colour inline." fontSize={17} color="#FF1C1C1E" />
</RichText>
```

![RichText rendered natively on iOS](https://skal.run/assets/components/01-richtext.png)

### <Image> → Image 

Network, file://, or asset:// sources; BoxFit via contentScale.

```jsx
<Image src="https://picsum.photos/seed/skal/640/360"
       width="fill" height={180} contentScale={1} cornerRadius={14} />
```

![Image rendered natively on iOS](https://skal.run/assets/components/02-image.png)

### <Canvas> → CustomPaint 

A chainable 2-D drawing context — rects, circles, paths, text. Reads signals; redraws when they change.

```jsx
<Canvas width={320} height={180} draw={(c) => {
  [60, 110, 80, 140, 95].forEach((v, i) =>
    c.fillStyle(i === 3 ? '#FF0A84FF' : '#FFBF5AF2')
     .fillRect(24 + i * 58, 170 - v, 40, v));
  c.fillStyle('#FF1C1C1E').fontSize(13).fillText('rendered on a signal', 24, 24);
}} />
```

![Canvas rendered natively on iOS](https://skal.run/assets/components/03-canvas.png)

### <ActivityIndicator> → CircularProgressIndicator 

Indeterminate spinner; color and size are props.

```jsx
<ActivityIndicator color="#FF0A84FF" width={36} />
```

![ActivityIndicator rendered natively on iOS](https://skal.run/assets/components/04-activityindicator.png)

### <ProgressBar> → LinearProgressIndicator 

Determinate with progress 0..1, indeterminate without.

```jsx
<ProgressBar progress={0.65} color="#FF0A84FF" />
<ProgressBar color="#FFBF5AF2" />   {/* indeterminate */}
```

![ProgressBar rendered natively on iOS](https://skal.run/assets/components/05-progressbar.png)

## Layout

### <Box> → Container 

The decorated box — padding, background, corners, borders, shadows, tap/hover/drag handlers.

```jsx
<Box padding={18} background="#FFFFFFFF" cornerRadius={16}
     borderWidth={1} borderColor="#FFE5E5EA" shadow={8}
     onTap={() => setLiked(!liked())}>
  <Text label="Tap me" fontSize={15} color="#FF1C1C1E" />
</Box>
```

![Box rendered natively on iOS](https://skal.run/assets/components/06-box.png)

### <Column> → Column 

Vertical flex; gap, padding, alignment; children flex with weight.

```jsx
<Column gap={10}>
  <Box height={44} background="#FF0A84FF" cornerRadius={10} />
  <Box height={44} background="#FF30D158" cornerRadius={10} />
  <Box height={44} background="#FFFF9F0A" cornerRadius={10} />
</Column>
```

![Column rendered natively on iOS](https://skal.run/assets/components/07-column.png)

### <Row> → Row 

Horizontal flex; weight distributes leftover space.

```jsx
<Row gap={10}>
  <Box weight={1} height={64} background="#FF0A84FF" cornerRadius={10} />
  <Box weight={2} height={64} background="#FF30D158" cornerRadius={10} />
  <Box weight={1} height={64} background="#FFFF9F0A" cornerRadius={10} />
</Row>
```

![Row rendered natively on iOS](https://skal.run/assets/components/08-row.png)

### <Stack> → Stack 

Layered children; top/right/bottom/left props position them absolutely.

```jsx
<Stack width="fill" height={160}>
  <Box width="fill" height={160} background="#FF0A84FF" cornerRadius={16} />
  <Box top={12} right={12} padding={8} background="#FFFFFFFF" cornerRadius={10}>
    <Text label="badge" fontSize={12} color="#FF0A84FF" />
  </Box>
  <Box bottom={12} left={12} padding={10} background="#66000000" cornerRadius={10}>
    <Text label="caption over the fill" fontSize={13} color="#FFFFFFFF" />
  </Box>
</Stack>
```

![Stack rendered natively on iOS](https://skal.run/assets/components/09-stack.png)

### <Wrap> → Wrap 

Flow layout — children wrap to the next line; gap sets both spacings.

```jsx
<Wrap gap={8}>
  <For each={['solid', 'flutter', 'bun', 'jsc', 'zero-copy', 'bridge', 'native']}>
    {(t) => <Box padding={10} background="#FFF2F2F7" cornerRadius={999}>
      <Text label={t} fontSize={13} color="#FF1C1C1E" /></Box>}
  </For>
</Wrap>
```

![Wrap rendered natively on iOS](https://skal.run/assets/components/10-wrap.png)

### <ScrollView> → SingleChildScrollView 

Eager scrolling for short content; axis 0 = vertical, 1 = horizontal. Use ListView for long lists.

```jsx
<ScrollView axis={1} gap={10} height={90}>
  <For each={colors}>
    {(c) => <Box width={90} height={90} background={c} cornerRadius={14} />}
  </For>
</ScrollView>
```

![ScrollView rendered natively on iOS](https://skal.run/assets/components/11-scrollview.png)

## Input

### <Button> → ElevatedButton 

label + onClick; disable with enabled={false}.

```jsx
<Button label="Continue" onClick={() => submit()} />
<Button label="Disabled" enabled={false} />
```

![Button rendered natively on iOS](https://skal.run/assets/components/12-button.png)

### <TextInput> → TextField 

Controlled text field — onChange per keystroke, onSubmit on Enter, secureEntry for passwords.

```jsx
<TextInput value={name()} placeholder="Your name…"
           onChange={setName} onSubmit={(v) => save(v)} />
```

![TextInput rendered natively on iOS](https://skal.run/assets/components/13-textinput.png)

### <Switch> → Switch 

Boolean toggle; the app owns the state.

```jsx
<Switch checked={on()} onChange={setOn} />
```

![Switch rendered natively on iOS](https://skal.run/assets/components/14-switch.png)

### <Checkbox> → Checkbox 

Checked/unchecked; onChange(bool).

```jsx
<Checkbox checked={done()} onChange={setDone} />
```

![Checkbox rendered natively on iOS](https://skal.run/assets/components/15-checkbox.png)

### <Radio> → Radio 

One option in a group the app owns — onChange fires with true.

```jsx
<For each={options}>
  {(opt, i) => <Row gap={8}>
    <Radio checked={sel() === i()} onChange={() => setSel(i())} />
    <Text label={opt} fontSize={14} />
  </Row>}
</For>
```

![Radio rendered natively on iOS](https://skal.run/assets/components/16-radio.png)

### <Chip> → FilterChip 

Toggleable filter chip — label, checked, onChange(bool).

```jsx
<Chip label="Solid" checked={f().solid} onChange={(v) => toggle('solid', v)} />
```

![Chip rendered natively on iOS](https://skal.run/assets/components/17-chip.png)

### <SegmentedButton> → SegmentedButton 

One-of-N selector; children are the segment labels.

```jsx
<SegmentedButton activeTab={seg()} onChange={setSeg}>
  <Text label="Day" fontSize={13} />
  <Text label="Week" fontSize={13} />
  <Text label="Month" fontSize={13} />
</SegmentedButton>
```

![SegmentedButton rendered natively on iOS](https://skal.run/assets/components/18-segmentedbutton.png)

### <Dropdown> → DropdownButton 

Select from a menu; children are the options.

```jsx
<Dropdown activeTab={dd()} onChange={setDd}>
  <Text label="Low" fontSize={14} />
  <Text label="Medium" fontSize={14} />
  <Text label="High" fontSize={14} />
</Dropdown>
```

![Dropdown rendered natively on iOS](https://skal.run/assets/components/19-dropdown.png)

### <Slider> → Slider 

Continuous value between min and max.

```jsx
<Slider value={vol()} min={0} max={100} onChange={setVol} />
```

![Slider rendered natively on iOS](https://skal.run/assets/components/20-slider.png)

### <Stepper> → Stepper 

Multi-step flows; each Step has a title and a body.

```jsx
<Stepper activeTab={step()} onChange={setStep}>
  <Step title="Account"><Text label="Name, email, password." fontSize={13} /></Step>
  <Step title="Profile"><Text label="Photo and a short bio." fontSize={13} /></Step>
  <Step title="Done"><Text label="Review and finish." fontSize={13} /></Step>
</Stepper>
```

![Stepper rendered natively on iOS](https://skal.run/assets/components/21-stepper.png)

## Lists

### <ListView> → ListView.builder 

Lazy, virtualized list — thousands of rows stay smooth. axis 1 for horizontal.

```jsx
<ListView gap={8} height={260}>
  <For each={rows()}>
    {(r) => <Box padding={14} background="#FFFFFFFF" cornerRadius={12}>
      <Text label={r} fontSize={14} color="#FF1C1C1E" /></Box>}
  </For>
</ListView>
```

![ListView rendered natively on iOS](https://skal.run/assets/components/22-listview.png)

### <ListTile> → ListTile 

title, subtitle, leading/trailing icons, onTap — the settings-row workhorse.

```jsx
<ListTile title="Notifications" subtitle="Sounds, banners, badges"
          leadingIcon="notifications" trailingIcon="chevron_right"
          onTap={() => open('notifications')} />
```

![ListTile rendered natively on iOS](https://skal.run/assets/components/23-listtile.png)

### <LazyGrid> → GridView.builder 

Virtualized grid; crossAxisCount columns, aspectRatio per cell.

```jsx
<LazyGrid crossAxisCount={3} aspectRatio={1} gap={10} height={240}>
  <For each={items}>
    {(c) => <Box background={c} cornerRadius={14} />}
  </For>
</LazyGrid>
```

![LazyGrid rendered natively on iOS](https://skal.run/assets/components/24-lazygrid.png)

### <ReorderableListView> → ReorderableListView.builder 

Drag rows to reorder; onReorder(from, to) updates your source array.

```jsx
<ReorderableListView height={230} gap={8} onReorder={(from, to) => move(from, to)}>
  <For each={rows()}>
    {(r) => <Box padding={14} background="#FFFFFFFF" cornerRadius={12}>
      <Text label={r} fontSize={14} color="#FF1C1C1E" /></Box>}
  </For>
</ReorderableListView>
```

![ReorderableListView rendered natively on iOS](https://skal.run/assets/components/25-reorderablelistview.png)

### <PageView> → PageView 

Swipeable pages; activeTab is the page index.

```jsx
<PageView activeTab={page()} onChange={setPage} height={200}>
  <Box background="#FF0A84FF" cornerRadius={16} />
  <Box background="#FF30D158" cornerRadius={16} />
  <Box background="#FFFF9F0A" cornerRadius={16} />
</PageView>
```

![PageView rendered natively on iOS](https://skal.run/assets/components/26-pageview.png)

### <AnimatedList> → AnimatedList 

Items animate in and out as your array changes.

```jsx
<AnimatedList height={220}>
  <For each={items()}>
    {(t) => <Box padding={12} background="#FFFFFFFF" cornerRadius={12}>
      <Text label={t} fontSize={14} color="#FF1C1C1E" /></Box>}
  </For>
</AnimatedList>
```

![AnimatedList rendered natively on iOS](https://skal.run/assets/components/27-animatedlist.png)

### <Dismissible> → Dismissible 

Swipe a row away; onDismiss removes it from your data.

```jsx
<Dismissible onDismiss={() => remove(item.id)}>
  <Box padding={14} background="#FFFFFFFF" cornerRadius={12}>
    <Text label="Swipe me away" fontSize={14} color="#FF1C1C1E" />
  </Box>
</Dismissible>
```

![Dismissible rendered natively on iOS](https://skal.run/assets/components/28-dismissible.png)

## Navigation

### <Tabs> → NavigationBar + IndexedStack 

Bottom tab navigation; every tab stays mounted (state survives switching).

```jsx
<Tabs activeTab={tab()} onChange={setTab} height="fill">
  <Tab title="Home" icon="grid"><Home /></Tab>
  <Tab title="Search" icon="explore"><Search /></Tab>
  <Tab title="Profile" icon="storage"><Profile /></Tab>
</Tabs>
```

![Tabs rendered natively on iOS](https://skal.run/assets/components/29-tabs.png)

### <ExpansionTile> → ExpansionTile 

Collapsible section; the host animates open/close.

```jsx
<ExpansionTile title="Advanced settings">
  <Box padding={14}>
    <Text label="Hidden until expanded." fontSize={13} />
  </Box>
</ExpansionTile>
```

![ExpansionTile rendered natively on iOS](https://skal.run/assets/components/30-expansiontile.png)

### <SliverAppBar> → CustomScrollView + SliverAppBar 

Collapsing header inside a sliver scroll — pinned, floating, or both.

```jsx
<CustomScrollView height={320}>
  <SliverAppBar title="Collapsing" height={120} sliverMode="pinned" />
  <SliverList>
    <For each={rows}>{(r) => <ListTile title={r} />}</For>
  </SliverList>
</CustomScrollView>
```

![SliverAppBar rendered natively on iOS](https://skal.run/assets/components/31-sliverappbar.png)

### <BottomSheet> → DraggableScrollableSheet 

Drag-resizable sheet anchored to the bottom of a Stack.

```jsx
<Stack width="fill" height={340}>
  <Box width="fill" height={340} background="#FF0A84FF" cornerRadius={16} />
  <BottomSheet initialSize={0.45} minSize={0.2} maxSize={0.9}>
    <Column padding={16} gap={8} background="#FFFFFFFF">
      <Text label="Drag me up" fontSize={15} fontWeight={700} />
    </Column>
  </BottomSheet>
</Stack>
```

![BottomSheet rendered natively on iOS](https://skal.run/assets/components/32-bottomsheet.png)

## Effects

### <BackdropFilter> → BackdropFilter 

Frosted-glass blur over whatever is behind it in a Stack.

```jsx
<Stack width="fill" height={200}>
  {/* colorful content… */}
  <Box top={50} left={40} right={40} height={100}>
    <BackdropFilter blurRadius={14}>
      <Text label="frosted" fontSize={16} />
    </BackdropFilter>
  </Box>
</Stack>
```

![BackdropFilter rendered natively on iOS](https://skal.run/assets/components/33-backdropfilter.png)

### <InteractiveViewer> → InteractiveViewer 

Pinch-zoom and pan around a child.

```jsx
<InteractiveViewer minScale={0.5} maxScale={4}>
  <Image src="…" width="fill" height={220} />
</InteractiveViewer>
```

![InteractiveViewer rendered natively on iOS](https://skal.run/assets/components/34-interactiveviewer.png)

### <CrossFade> → AnimatedSwitcher 

Cross-fades between children when the content swaps.

```jsx
<CrossFade>
  {day() ? <SunCard /> : <MoonCard />}
</CrossFade>
```

![CrossFade rendered natively on iOS](https://skal.run/assets/components/35-crossfade.png)

### <DragItem & DropZone> → Draggable + DragTarget 

Native drag-and-drop; the drop zone receives the item id.

```jsx
<DragItem dragData="card-1">
  <Box padding={14} background="#FFFFFFFF" cornerRadius={12}>
    <Text label="Drag me" fontSize={14} />
  </Box>
</DragItem>
<DropZone onDrop={(id) => setDropped(id)}>
  <Box padding={24} borderWidth={2} borderColor="#FF0A84FF" cornerRadius={12}>
    <Text label={dropped() ?? 'Drop here'} fontSize={14} />
  </Box>
</DropZone>
```

![DragItem & DropZone rendered natively on iOS](https://skal.run/assets/components/36-dragitem-dropzone.png)

## Not pictured

`Navigator`/`Screen` and `Hero` are navigation transitions (see [createRouter](architecture.html)), `Drawer` is the edge-swipe panel, `SafeArea` is inset plumbing, and dialogs/snackbars/pickers are imperative calls (`showDialog`, `showActionSheet`, `showSnackbar`, `showDatePicker`, `showTimePicker`) — one-liners from `skal/renderer`. Web escape hatches: `HtmlEmbed` and `FlutterEmbed`.

[Previous← Architecture](architecture.html) [NextState & the Store →](state.html)
