Pages
Every page sits in AppLayoutMain (see Layout): a PageHeader for
the page’s own title and controls, above a PageContent for the body — both
documented together as Page. PageContent’s content
varies by page — the most common shape is a table, documented in
Table pages.
Main header and content
Section titled “Main header and content”PageHeader stacks up to three rows: a heading, a toolbar — with a
TabsNav when the page has sub-pages — and, always
last, applied filter chips from Filter.
PageHeaderActions holds only actions and search: primary actions as standalone
buttons, two or more secondary actions collapsed into a
DropdownMenu behind an ellipsis trigger, and
search last — on narrow viewports it wraps below the buttons and takes the full
row. Every other control — tabs, button groups, filter triggers — sits before
it, on the toolbar’s left side.
Code
import { useRef, useState } from "react";
import { AppHeader, AppHeaderLeading, AppHeaderNotifications, AppHeaderTrailing, AppLayout, AppLayoutContent, AppLayoutMain, Badge, Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, FieldIcon, Filter, FilterApply, FilterButton, FilterChip, FilterChipRemove, FilterChips, FilterClearAll, FilterContent, FilterFooter, FilterHeader, FilterNav, FilterNavFooter, FilterNavItem, FilterNavSaved, FilterOption, FilterOptionList, FilterPane, FilterPaneBack, FilterPaneClear, FilterPaneFooter, FilterPaneHeader, FilterPaneTitle, FilterSave, FilterSaveDialog, FilterSavedItem, FilterSavedItemButton, InputGroup, InputGroupAddon, InputGroupInput, LayoutDashboardIcon, ListIcon, MapIcon, MoreHorizontalIcon, PageContent, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderEyebrow, PageHeaderHeading, PageHeaderMedia, PageHeaderTitle, PageHeaderToolbar, RadioGroup, RadioGroupItem, SearchIcon, Sidebar, SidebarBrand, SidebarContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, TabsNav, TabsNavLink, UsersIcon,} from "@falcon/ui-kit";
const statuses = ["Any status", "Active", "Inactive"];const regions = ["Midwest", "Great Plains", "Delta", "Southeast"];
interface Selection { status: string; regions: string[];}
interface SavedFilter { id: number; name: string; selection: Selection;}
function countFilters(selection: Selection) { return ( (selection.status === "Any status" ? 0 : 1) + (selection.regions.length > 0 ? 1 : 0) );}
export function Example() { const [open, setOpen] = useState(false); const [category, setCategory] = useState("Status"); const [draft, setDraft] = useState<Selection>({ status: "Active", regions: ["Midwest"], }); const [applied, setApplied] = useState(draft); const [saved, setSaved] = useState<SavedFilter[]>([]); const [saveOpen, setSaveOpen] = useState(false); const nextId = useRef(1);
const draftCount = countFilters(draft);
function clearDraft() { setDraft({ status: "Any status", regions: [] }); }
function clearStatus() { setDraft({ ...draft, status: "Any status" }); }
function clearRegions() { setDraft({ ...draft, regions: [] }); }
function apply() { setApplied(draft); setOpen(false); }
function applyBoth(next: Selection) { setApplied(next); setDraft(next); }
const footer = ( <FilterFooter> <FilterClearAll onClick={clearDraft} /> <FilterApply count={draftCount} onClick={apply} /> </FilterFooter> );
return ( <SidebarProvider defaultOpen> <AppLayout> <Sidebar> <SidebarBrand logo={<FieldIcon />} name="AgVend" />
<SidebarContent> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton icon={<LayoutDashboardIcon size={16} />}> Dashboard </SidebarMenuButton> </SidebarMenuItem>
<SidebarMenuItem> <SidebarMenuButton icon={<UsersIcon size={16} />}> Accounts </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarContent> </Sidebar>
<AppLayoutContent> <AppHeader> <AppHeaderLeading> <SidebarTrigger />
<Breadcrumb> <BreadcrumbList> <BreadcrumbItem> <BreadcrumbLink href="#accounts">Accounts</BreadcrumbLink> </BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbPage>All Accounts</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </AppHeaderLeading>
<AppHeaderTrailing> <AppHeaderNotifications aria-label="Notifications" count={3} /> </AppHeaderTrailing> </AppHeader>
<AppLayoutMain> <PageHeader> <PageHeaderHeading> <PageHeaderMedia> <FieldIcon /> </PageHeaderMedia>
<PageHeaderContent> <PageHeaderEyebrow>Agtegra Cooperative</PageHeaderEyebrow> <PageHeaderTitle> Accounts <Badge variant="secondary">288</Badge> </PageHeaderTitle> <PageHeaderDescription> Last updated today </PageHeaderDescription> </PageHeaderContent> </PageHeaderHeading>
<PageHeaderToolbar> <TabsNav aria-label="Accounts"> <TabsNavLink href="#active" aria-current="page"> Active </TabsNavLink> <TabsNavLink href="#leads">Leads</TabsNavLink> <TabsNavLink href="#notes">Notes</TabsNavLink> </TabsNav>
<ButtonGroup> <Button variant="outline"> <ListIcon /> List </Button> <Button variant="outline"> <MapIcon /> Map </Button> </ButtonGroup>
<Filter open={open} onOpenChange={(nextOpen) => { if (nextOpen) { setDraft(applied); } setOpen(nextOpen); }} > <FilterButton>Filter</FilterButton>
<FilterContent> <FilterHeader />
<FilterNav> <FilterNavSaved active={category === "Saved"} applied={saved.length > 0} onClick={() => setCategory("Saved")} />
<FilterNavItem active={category === "Status"} applied={draft.status !== "Any status"} values={ draft.status !== "Any status" ? [draft.status] : [] } onClick={() => setCategory("Status")} > Status </FilterNavItem>
<FilterNavItem active={category === "Region"} applied={draft.regions.length > 0} values={draft.regions} onClick={() => setCategory("Region")} > Region </FilterNavItem> </FilterNav>
{category === "Saved" && ( <FilterPane> <FilterPaneHeader> <FilterPaneBack /> <FilterPaneTitle>Saved</FilterPaneTitle> </FilterPaneHeader>
{saved.length === 0 ? ( <p className="tw:flex-1 tw:px-4 tw:py-2 tw:text-[0.8rem] tw:text-muted-foreground"> No saved filters yet. Use Save next to your active filter chips to add one. </p> ) : ( <FilterOptionList> {saved.map((filter) => ( <FilterSavedItem key={filter.id}> <FilterSavedItemButton onClick={() => { applyBoth(filter.selection); setOpen(false); }} > {filter.name} </FilterSavedItemButton> </FilterSavedItem> ))} </FilterOptionList> )}
{footer} <FilterPaneFooter onClear={clearDraft} /> </FilterPane> )}
{category === "Status" && ( <FilterPane> <FilterPaneHeader> <FilterPaneBack /> <FilterPaneTitle>Status</FilterPaneTitle>
{draft.status !== "Any status" && ( <FilterPaneClear onClick={clearStatus} /> )} </FilterPaneHeader>
<FilterOptionList> <RadioGroup value={draft.status} onValueChange={(status) => setDraft({ ...draft, status }) } className="tw:gap-0.5" > {statuses.map((status) => ( <FilterOption key={status}> <RadioGroupItem value={status} /> {status} </FilterOption> ))} </RadioGroup> </FilterOptionList>
{footer} <FilterPaneFooter onClear={clearStatus} /> </FilterPane> )}
{category === "Region" && ( <FilterPane> <FilterPaneHeader> <FilterPaneBack /> <FilterPaneTitle>Region</FilterPaneTitle>
{draft.regions.length > 0 && ( <FilterPaneClear onClick={clearRegions} /> )} </FilterPaneHeader>
<FilterOptionList> {regions.map((region) => ( <FilterOption key={region}> <Checkbox checked={draft.regions.includes(region)} onCheckedChange={(checked) => setDraft({ ...draft, regions: checked ? [...draft.regions, region] : draft.regions.filter( (r) => r !== region, ), }) } /> {region} </FilterOption> ))} </FilterOptionList>
{footer} <FilterPaneFooter onClear={clearRegions} /> </FilterPane> )}
<FilterNavFooter> <FilterClearAll onClick={clearDraft}> Clear filters </FilterClearAll> <FilterApply count={draftCount} onClick={apply} /> </FilterNavFooter> </FilterContent> </Filter>
<PageHeaderActions> <Button>Create lead</Button>
<DropdownMenu> <DropdownMenuTrigger render={ <Button variant="outline" aria-label="More actions" /> } > <MoreHorizontalIcon /> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuItem>Import accounts</DropdownMenuItem> <DropdownMenuItem>Export accounts</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu>
<InputGroup> <InputGroupAddon> <SearchIcon /> </InputGroupAddon> <InputGroupInput placeholder="Search accounts" /> </InputGroup> </PageHeaderActions> </PageHeaderToolbar>
{countFilters(applied) > 0 && ( <> <FilterChips> {applied.status !== "Any status" && ( <FilterChip category="Status" values={[applied.status]}> <FilterChipRemove aria-label="Remove status filter" onClick={() => applyBoth({ ...applied, status: "Any status" }) } /> </FilterChip> )}
{applied.regions.length > 0 && ( <FilterChip category="Region" values={applied.regions}> <FilterChipRemove aria-label="Remove region filter" onClick={() => applyBoth({ ...applied, regions: [] })} /> </FilterChip> )}
<FilterSave onClick={() => setSaveOpen(true)} /> </FilterChips>
<FilterSaveDialog open={saveOpen} onOpenChange={setSaveOpen} onSave={(name) => { setSaved([ ...saved, { id: nextId.current++, name, selection: applied }, ]); }} /> </> )} </PageHeader>
<PageContent> <p className="tw:text-sm tw:text-muted-foreground"> Accounts across every retailer go here. </p> </PageContent> </AppLayoutMain> </AppLayoutContent> </AppLayout> </SidebarProvider> );}Without PageContent
Section titled “Without PageContent”PageContent sits on a tinted backdrop that offsets its content from the page’s
edges. That offset is optional: content that needs to run edge-to-edge skips
PageContent and sits directly in AppLayoutMain, as PageHeader’s sibling. A
single content block takes tw:flex-1 so it still fills the remaining height; a
table page is an exception — see Table pages — where the whole
page scrolls as one unit instead.
Code
import { AppHeader, AppHeaderLeading, AppHeaderNotifications, AppHeaderTrailing, AppLayout, AppLayoutContent, AppLayoutMain, Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, FieldIcon, LayoutDashboardIcon, PageHeader, PageHeaderTitle, Sidebar, SidebarBrand, SidebarContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, UsersIcon,} from "@falcon/ui-kit";
export function Example() { return ( <SidebarProvider defaultOpen> <AppLayout> <Sidebar> <SidebarBrand logo={<FieldIcon />} name="AgVend" />
<SidebarContent> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton icon={<LayoutDashboardIcon size={16} />}> Dashboard </SidebarMenuButton> </SidebarMenuItem>
<SidebarMenuItem> <SidebarMenuButton icon={<UsersIcon size={16} />}> Accounts </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarContent> </Sidebar>
<AppLayoutContent> <AppHeader> <AppHeaderLeading> <SidebarTrigger />
<Breadcrumb> <BreadcrumbList> <BreadcrumbItem> <BreadcrumbLink href="#">Home</BreadcrumbLink> </BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbPage>Field map</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </AppHeaderLeading>
<AppHeaderTrailing> <AppHeaderNotifications aria-label="Notifications" count={3} /> </AppHeaderTrailing> </AppHeader>
<AppLayoutMain> <PageHeader> <PageHeaderTitle>Field map</PageHeaderTitle> </PageHeader>
<div className="tw:flex tw:flex-1 tw:items-center tw:justify-center tw:bg-muted tw:text-sm tw:text-muted-foreground"> Edge-to-edge content goes here, flush with every side. </div> </AppLayoutMain> </AppLayoutContent> </AppLayout> </SidebarProvider> );}Header without a toolbar
Section titled “Header without a toolbar”Every header row is optional, including PageHeaderToolbar itself — the row
holding tabs, button groups, and filter triggers. Without any of those, that row
disappears entirely; it’s not just empty. PageHeaderActions (buttons, a
collapsed dropdown, search) isn’t part of the toolbar row itself, so it isn’t
skipped along with it — it moves up into the heading row and shares the title’s
line instead of sitting below it.
Code
import { AppHeader, AppHeaderLeading, AppHeaderNotifications, AppHeaderTrailing, AppLayout, AppLayoutContent, AppLayoutMain, Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, FieldIcon, InputGroup, InputGroupAddon, InputGroupInput, LayoutDashboardIcon, MoreHorizontalIcon, PageContent, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderHeading, PageHeaderTitle, SearchIcon, Sidebar, SidebarBrand, SidebarContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, UsersIcon,} from "@falcon/ui-kit";
export function Example() { return ( <SidebarProvider defaultOpen> <AppLayout> <Sidebar> <SidebarBrand logo={<FieldIcon />} name="AgVend" />
<SidebarContent> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton icon={<LayoutDashboardIcon size={16} />}> Dashboard </SidebarMenuButton> </SidebarMenuItem>
<SidebarMenuItem> <SidebarMenuButton icon={<UsersIcon size={16} />}> Accounts </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarContent> </Sidebar>
<AppLayoutContent> <AppHeader> <AppHeaderLeading> <SidebarTrigger />
<Breadcrumb> <BreadcrumbList> <BreadcrumbItem> <BreadcrumbLink href="#orders">Orders</BreadcrumbLink> </BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbPage>All Orders</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </AppHeaderLeading>
<AppHeaderTrailing> <AppHeaderNotifications aria-label="Notifications" count={3} /> </AppHeaderTrailing> </AppHeader>
<AppLayoutMain> <PageHeader> <PageHeaderHeading> <PageHeaderContent> <PageHeaderTitle>Orders</PageHeaderTitle> <PageHeaderDescription> Orders across all locations </PageHeaderDescription> </PageHeaderContent>
<PageHeaderActions> <Button>Create order</Button>
<DropdownMenu> <DropdownMenuTrigger render={ <Button variant="outline" aria-label="More actions" /> } > <MoreHorizontalIcon /> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuItem>Import orders</DropdownMenuItem> <DropdownMenuItem>Export orders</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu>
<InputGroup> <InputGroupAddon> <SearchIcon /> </InputGroupAddon> <InputGroupInput placeholder="Search orders" /> </InputGroup> </PageHeaderActions> </PageHeaderHeading> </PageHeader>
<PageContent> <p className="tw:text-sm tw:text-muted-foreground"> Order lines, totals, and delivery details go here. </p> </PageContent> </AppLayoutMain> </AppLayoutContent> </AppLayout> </SidebarProvider> );}