Layouts
The kit does not ship page shells. Components fill the box you give them and adapt to its width, so a layout is a few flex containers around them. The recipes below match the layout stories in the repository Storybook (layouts/*).
Rules for every layout
- Give the chat a bounded height: a flex column with
minHeight: 0on every level down toAgentChat. Without it the message list grows with its content and the composer scrolls away. contentWidthsets the message column and composer width. Use a number such as760on pages and"100%"in panels and widgets. The default is 420px.- Below about 720px of container width, pass
wrapLinesso code and diffs wrap, and move side panes into aDrawer. Measure the container withuseElementSize, not the viewport: the same screen can be a page or a panel. alignComposerlines the composer andstatusBarup with the text of the messages;topFadesoftens the top edge under a header.- Separate zones with background and a 1px
--ae-borderline, not with cards.
Full-page chat
One centered column: a 48px header aligned with the column, the feed without frames and the composer pinned to the bottom. withSearch adds Mod+F search; stickyPrompt keeps the prompt of a long answer visible.
import { ActionIcon, Box, Group, Text } from "@mantine/core";
import { IconShare2 } from "@tabler/icons-react";
import { AgentChat } from "@sinups/ai-kit";
const COLUMN = 760;
export function ChatPage({ chat }: { chat: ChatState }) {
return (
<Box h="100dvh" style={{ display: "flex", flexDirection: "column", minHeight: 0 }}>
<Group component="header" h={48} px="md" maw={COLUMN} w="100%" mx="auto" wrap="nowrap">
<Text size="sm" fw={500} truncate style={{ flex: 1 }}>
Add retry to token refresh
</Text>
<ActionIcon variant="subtle" color="gray" aria-label="Share">
<IconShare2 size={16} />
</ActionIcon>
</Group>
<AgentChat
{...chat}
contentWidth={COLUMN}
collapseToolRuns
alignComposer
topFade
withSearch
stickyPrompt
style={{ flex: 1, minHeight: 0 }}
/>
</Box>
);
}New chat
emptyState replaces the empty feed. The welcome layout keeps the composer at the bottom and lists starter actions above it. The center layout centers the greeting and the composer, with suggestion pills above the composer. Both switch to the regular feed after the first message.
const welcome = {
avatar: <IconSparkles size={22} />,
title: "How can I help you today?",
description: "Ask about the code, fix a bug or plan a change.",
actions: [
{ id: "explain", label: "Explain this repository", icon: <IconBook2 /> },
{ id: "tests", label: "Find flaky tests", icon: <IconBug />, badge: "New" },
],
};
// Welcome: greeting and actions in the message area, composer at the bottom
<AgentChat {...chat} contentWidth={760} alignComposer emptyState={welcome} />
// Center: greeting and composer centered; suggestion pills render above the composer
<AgentChat
{...chat}
contentWidth={760}
emptyState={{
layout: "center",
title: "What should we work on?",
suggestions: [
{ id: "explain", label: "Explain this repository" },
{ id: "tests", label: "Find flaky tests" },
],
}}
/>Chat with an inspector
A resizable, collapsible pane beside the chat for DiffReview or BackgroundTasksPanel. On phones open the same content in a bottom Drawer (position="bottom", size="92%"). Pane components take a header prop so the pane title sits in their own header row.
import { Splitter } from "@mantine/core";
import { AgentChat, DiffReview } from "@sinups/ai-kit";
<Splitter withHandle={false} lineSize={1} handleColor="var(--ae-border)" style={{ flex: 1, minHeight: 0 }}>
<Splitter.Pane defaultSize={62} min="420px">
<AgentChat {...chat} contentWidth={760} alignComposer topFade />
</Splitter.Pane>
<Splitter.Pane defaultSize={38} min="360px" collapsible collapseThreshold="240px">
<DiffReview
changes={changes}
decisions={decisions}
onAccept={(change) => accept(change.path)}
onReject={(change) => reject(change.path)}
header={<Text size="sm" fw={500}>Changes</Text>}
/>
</Splitter.Pane>
</Splitter>Settings page
SettingsLayout provides section navigation beside the content when wide and a list with a back action when narrow. Sections with fill: true give full height to panels that scroll themselves, such as McpSettingsPanel. For the same content in a dialog use SettingsModal.
import { useState } from "react";
import { Switch } from "@mantine/core";
import {
McpSettingsPanel,
SettingRow,
SettingsLayout,
SettingsSection,
UsagePanel,
type SettingsNavItem,
} from "@sinups/ai-kit";
const SECTIONS: SettingsNavItem[] = [
{ id: "general", label: "General" },
{ id: "mcp", label: "MCP servers", fill: true },
{ id: "usage", label: "Usage" },
];
export function SettingsPage() {
const [activeId, setActiveId] = useState("general");
return (
<SettingsLayout title="Settings" sections={SECTIONS} activeId={activeId} onActiveIdChange={setActiveId}>
{activeId === "general" && (
<SettingsSection title="Chat">
<SettingRow
label="Send with Enter"
description="Use Shift+Enter for a new line"
control={<Switch aria-label="Send with Enter" defaultChecked />}
/>
</SettingsSection>
)}
{activeId === "mcp" && <McpSettingsPanel servers={servers} />}
{activeId === "usage" && <UsagePanel {...usage} />}
</SettingsLayout>
);
}Widget
A floating assistant on a host page. See Embedding the launcher for the React and Shadow DOM setups.
<ChatLauncher title="Assistant">
<AgentChat {...chat} contentWidth="100%" wrapLines alignComposer emptyState={welcome} />
</ChatLauncher>