Screens and layout
Arrange content, headers and tabs.
Example
import { navigate, Screen, Text } from "ink";
import { moreHoriz } from "ink/icons";
export default function Home() {
return <Screen
title="Home"
rightAction={{
icon: moreHoriz,
onPress: () => navigate("/actions"),
}}
>
<Text>Welcome to Ink!</Text>
</Screen>;
}Create app/actions.tsx for the header button’s destination. Screen provides padding, navigation and scrolling.

Header actions
Use leftAction and rightAction for header buttons. Back takes precedence over leftAction on detail screens.
import { Screen, navigate } from "ink";
import { graphicEq, sort } from "ink/icons";
<Screen
title="Albums"
leftAction={{ icon: sort, onPress: () => navigate("/albums-sort") }}
rightAction={{ icon: graphicEq, onPress: () => navigate("/playing") }}
>
{/* Screen content */}
</Screen>
Centred content
<Screen title="Home" centered>
<Text>Welcome to Ink!</Text>
</Screen>
Stacks
Use Stack to group content. Set axis=“horizontal” for a row.
Set onPress to make the whole group tappable, including gaps between its children. Use haptic= to disable feedback for that action.
<Stack gap={8}>
<Text>London</Text>
<Text size={18}>United Kingdom</Text>
</Stack>
<Stack axis="horizontal" justify="space-between">
<Text>Low</Text>
<Text>High</Text>
</Stack>
Space between
A vertical Stack with justify=“space-between” fills the content area when it is the only child of Screen.
import { Screen, Stack, Text } from "ink";
<Screen title="About">
<Stack align="stretch" justify="space-between">
<Text>My app</Text>
<Stack align="center">
<Text size={18}>Powered by</Text>
<Text>Ink Framework</Text>
</Stack>
</Stack>
</Screen>
Pinned content
header keeps content above a scrolling list.
<Screen title="Items" header={<Button onPress={addItem}>Add item</Button>}>
<List items={items} keyExtractor={item => item.id}
renderItem={item => <Text>{item.name}</Text>} />
</Screen>

Tabs
Declare tabs in _layout.tsx. Each name refers to a neighbouring page.
import { Tabs } from "ink";
import { homeFilled, settingsFilled } from "ink/icons";
export default function Layout() {
return <Tabs>
<Tabs.Screen name="index" icon={homeFilled} />
<Tabs.Screen name="settings" icon={settingsFilled} />
</Tabs>;
}See Navigation for tab actions, page structure and history.

Spacing and appearance
Screens and lists share a default gap of 47 logical units. Stack accepts gap, align and justify. Use gap={0} for compact groups; do not add padding around content already padded by the screen.
Background
Set background to a #RRGGBB colour to fill the screen, including its header. Text keeps the current colour scheme. The background follows colour-scheme inversion, like Canvas colours.
<Screen title="Railcard" background="#D35600">
<Text>Card details</Text>
</Screen>
Bottom spacing
Set bottomInset={false} to let custom controls reach the bottom edge. The default leaves 20 logical units below the content.
<Screen title="Controls" bottomInset={false}>
<Stack justify="space-between" gap={0}>
<Text>Page content</Text>
<Canvas width={350} height={60}>
<Rectangle x={0} y={0} width={350} height={60} fill="#FFFFFF" />
<CanvasText x={0} y={0} width={350} height={60} colour="#000000">Controls</CanvasText>
</Canvas>
</Stack>
</Screen>

Colour scheme
Use a toggle to switch between light and dark mode.
import { Screen, Toggle, setColourScheme, useColourScheme } from "ink";
export default function Appearance() {
const scheme = useColourScheme();
return <Screen title="Appearance">
<Toggle label="Light mode" value={scheme === "light"}
onChange={light => setColourScheme(light ? "light" : "dark")} />
</Screen>;
}
The choice lasts for the app session. Use Store to save it between sessions. Photos and codes retain their original colours.
Wide content
Use wide to reduce horizontal margins to 20 units. Ink reserves space for the scrollbar when needed.
<Screen title="Albums" wide>
<List items={albums} keyExtractor={album => album.id} renderItem={renderAlbum} />
</Screen>

Loading images
Screens wait up to one second for their first visible images before appearing. Failed images do not block the screen. Images reached by scrolling load normally.
To show content immediately:
<Screen title="Show" waitForImages={false}>
<Image src={show.artwork} width={100} height={100} fit="cover" />
<Text>{show.title}</Text>
</Screen>
