Page
Use PageHeader at the top of AppLayoutMain (see AppLayout)
for the page’s own title and controls, and PageContent below it for the page
body. The application-wide top bar is AppHeader.
Code
import { useRef, useState } from "react";
import { AppLayout, AppLayoutContent, AppLayoutMain, Badge, 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, ListIcon, MapIcon, MoreHorizontalIcon, PageContent, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderEyebrow, PageHeaderHeading, PageHeaderMedia, PageHeaderTitle, PageHeaderToolbar, RadioGroup, RadioGroupItem, SearchIcon, TabsNav, TabsNavLink,} 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 ( <AppLayout> <AppLayoutContent> <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"> Account cards and tables go here. </p> </PageContent> </AppLayoutMain> </AppLayoutContent> </AppLayout> );}Composition
Section titled “Composition”AppLayoutMain├── PageHeader│ ├── PageHeaderHeading│ │ ├── PageHeaderMedia (optional)│ │ └── PageHeaderContent│ │ ├── PageHeaderEyebrow (optional)│ │ ├── PageHeaderTitle│ │ └── PageHeaderDescription (optional)│ ├── PageHeaderToolbar (optional)│ │ ├── TabsNav (optional)│ │ ├── ButtonGroup (optional)│ │ ├── Filter (optional)│ │ └── PageHeaderActions│ │ ├── Button (primary actions)│ │ ├── DropdownMenu (two or more secondary actions)│ │ └── InputGroup (search)│ └── FilterChips (optional)└── PageContent (optional)Toolbar controls keep this order: TabsNav when the page has
sub-pages, ButtonGroup for view switches, and the
Filter popup. When the header has no toolbar row, place
PageHeaderActions inside PageHeaderHeading instead — the actions then share
the title’s line. The applied-filter row is FilterChips from
Filter.
PageContent is optional: content that needs to run edge-to-edge skips it and
sits directly in AppLayoutMain instead, as PageHeader’s sibling — see
Pages.
Code examples live in Pages.
PageHeader
Section titled “PageHeader”Page-level header at the top of the main content region, stacking up to three
rows: heading, toolbar, and applied-filter chips. Renders a <header>
element and accepts standard <header> props.
PageHeaderActions
Section titled “PageHeaderActions”Right-aligned action cluster at the end of the toolbar row; in a header
without a toolbar row, place it in the heading row instead. Holds only
actions and search: primary actions as standalone buttons, secondary actions
collapsed into a dropdown menu when there is more than one, and the search
input last. Every other control belongs on the toolbar’s left side. Renders a
<div> element and accepts standard <div> props.
PageHeaderContent
Section titled “PageHeaderContent”Text column for the eyebrow, title, and description. Renders a <div>
element and accepts standard <div> props.
PageHeaderDescription
Section titled “PageHeaderDescription”Supporting line under the title. Renders a <p> element and accepts standard
<p> props.
PageHeaderEyebrow
Section titled “PageHeaderEyebrow”Context line above the title, e.g. the retailer a page is scoped to. Renders
a <p> element and accepts standard <p> props.
PageHeaderHeading
Section titled “PageHeaderHeading”Heading row grouping the media, text content, and — in a header without a
toolbar row — the actions. Renders a <div> element and accepts standard
<div> props.
PageHeaderMedia
Section titled “PageHeaderMedia”Partner logo slot; sizes a nested img itself. Renders a <div> element and
accepts standard <div> props.
PageHeaderTitle
Section titled “PageHeaderTitle”Page title. Renders an <h1> element and accepts standard <h1> props.
PageHeaderToolbar
Section titled “PageHeaderToolbar”Toolbar row for page-level controls such as tab navigation, view switches,
and filter triggers — they fill the row’s left side, in that order, with
actions and search kept to PageHeaderActions at the end. Renders a <div>
element and accepts standard <div> props.
PageContent
Section titled “PageContent”Page body below PageHeader, holding everything the page renders. Renders a
<div> element and accepts standard <div> props.