Blocks
Page Title
Standard page header block with auto-generated breadcrumbs, an h1 title, optional subtitle, and a right-side action slot. Shown by default on most pages via AppDashboard.
Import
// Recommended — via AppDashboard pageTitle prop
import { AppDashboard } from "@/components/blocks/AppDashboard"
// Standalone use
import { AppPageTitle } from "@/components/blocks/AppPageTitle"Examples
With actions
Pass any elements to actions — buttons, selects, or text. The slot is right-aligned and empty by default.
Hidden
Pass pageTitle={false} to suppress the block entirely for pages that don't need a title header.
Standalone
AppPageTitle can be used directly in custom layouts outside of AppDashboard.
Site
Stay up to date to everything in your network
With icon
Pass any ReactNode to the icon prop — an <img>, SVG, or Lucide icon. The icon renders to the left of the title stack and is optional.
Primary title color
Set titleColor="primary" to render the h1 in the brand blue color. The default is "default" which uses text-foreground.
Hi Kevin! Welcome to Viana
Stay up to date to everything in your network
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | auto | Page heading rendered as an <h1>. Auto-derived from the last pathname segment when omitted. |
| subtitle | string | — | Optional description rendered below the heading. |
| breadcrumbs | AppPageTitleBreadcrumb[] | auto | Explicit breadcrumb trail. Auto-generated from window.location.pathname when omitted. |
| actions | ReactNode | — | Right-side slot. Accepts buttons, selects, or any elements. Empty by default. |
| icon | ReactNode | — | Optional icon rendered to the left of the title stack. Accepts <img>, SVG, or Lucide icons. |
| titleColor | "default" | "primary" | "default" | Controls the h1 color. "default" uses text-foreground; "primary" uses the brand blue. |
| hidden | boolean | false | Hides the entire block. |
| className | string | — | Extra classes on the root element. |
When used via AppDashboard, pass pageTitle={AppPageTitleProps} or pageTitle={false} to suppress.
Breadcrumb auto-generation
When breadcrumbs is omitted, the component reads window.location.pathname on mount and converts each path segment into a readable label. Segments are split on - and _, then title-cased (site-settings → Site Settings). The last segment has no link (current page). Pass an explicit breadcrumbs array to override when the URL structure doesn't match the desired labels.
Source
"use client"
import * as React from "react"
import { cn } from "../../lib/utils"
import {
AppBreadcrumb,
AppBreadcrumbItem,
AppBreadcrumbLink,
AppBreadcrumbList,
AppBreadcrumbPage,
AppBreadcrumbSeparator,
} from "../primitives/AppBreadcrumb"
export type AppPageTitleBreadcrumb = {
label: string
href?: string
}
export type AppPageTitleProps = {
title?: string
subtitle?: string
breadcrumbs?: AppPageTitleBreadcrumb[]
actions?: React.ReactNode
icon?: React.ReactNode
titleColor?: "default" | "primary"
hidden?: boolean
className?: string
}
function AppPageTitle({ title, subtitle, breadcrumbs, actions, hidden = false, className }: AppPageTitleProps) {
const [autoCrumbs, setAutoCrumbs] = React.useState<AppPageTitleBreadcrumb[]>([])
React.useEffect(() => {
if (breadcrumbs === undefined) {
const segments = window.location.pathname.split("/").filter(Boolean)
setAutoCrumbs(segments.map((seg, i) => ({
label: seg.split(/[-_]/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
href: i < segments.length - 1 ? "/" + segments.slice(0, i + 1).join("/") : undefined,
})))
}
}, [breadcrumbs])
if (hidden) return null
const crumbs = breadcrumbs ?? autoCrumbs
return (
<div className={cn("mb-6 flex items-start justify-between gap-4", className)}>
<div className="space-y-1 min-w-0">
{crumbs.length > 0 && (
<AppBreadcrumb>
<AppBreadcrumbList>
{crumbs.map((crumb, i) => (
<React.Fragment key={i}>
<AppBreadcrumbItem>
{crumb.href
? <AppBreadcrumbLink href={crumb.href}>{crumb.label}</AppBreadcrumbLink>
: <AppBreadcrumbPage>{crumb.label}</AppBreadcrumbPage>}
</AppBreadcrumbItem>
{i < crumbs.length - 1 && <AppBreadcrumbSeparator />}
</React.Fragment>
))}
</AppBreadcrumbList>
</AppBreadcrumb>
)}
<h1 className="text-2xl font-bold tracking-tight text-foreground">{title}</h1>
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
</div>
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
</div>
)
}
AppPageTitle.displayName = "AppPageTitle"
export { AppPageTitle }