Skip to Content
glob.ui home
Templates

Detail page template

A detail page: breadcrumbs, a back link and a title, the content, and a sticky aside card with the facts about the item.

A template renders PageLayout, fixes part of it, and exposes props for the rest. It is copy-code: the file is yours to edit.

Preview

Anatomy names each slot and what fills it. The panel changes the props live; the fixed parts stay.

Props
import type { ReactNode } from "react"import { PageLayout, PageHeader } from "@glob-ui/page-layout"import { Card, CardContent, CardHeader, CardTitle } from "@glob-ui/card"// Edit: this is yours. The back link text and the aside heading are fixed for every detail page.const BACK_LABEL = "Back"const ASIDE_TITLE = "Details"export interface DetailPageTemplateProps {  title: ReactNode  /** A breadcrumb trail above the title. */  breadcrumbs?: ReactNode  /** Where the back link goes. Omit it to hide the link. */  backHref?: string  actions?: ReactNode  children?: ReactNode  /** Facts about the item, in a card that stays in view from `lg`. */  aside?: ReactNode}/** A detail page: a header with breadcrumbs and a back link, the content, and a sticky aside card. */export function DetailPageTemplate({ title, breadcrumbs, backHref, actions, children, aside }: DetailPageTemplateProps) {  return (    <PageLayout      aside={        aside ? (          <div data-template-part="prop" data-template-name="facts about the item">            <Card data-template-part="fixed" data-template-name="card frame">              <CardHeader>                <CardTitle as="h2">{ASIDE_TITLE}</CardTitle>              </CardHeader>              <CardContent>{aside}</CardContent>            </Card>          </div>        ) : undefined      }      header={        <div data-template-part="fixed" data-template-name="breadcrumbs, back link">          <PageHeader            title={<span data-template-part="prop" data-template-name="title">{title}</span>}            breadcrumbs={              backHref || breadcrumbs ? (                <div className="flex flex-col gap-1">                  {breadcrumbs}                  {backHref ? (                    <a href={backHref} className="w-fit text-glob-text-secondary underline-offset-4 hover:underline">                      ← {BACK_LABEL}                    </a>                  ) : null}                </div>              ) : undefined            }            actions={actions ? <div data-template-part="prop" data-template-name="actions">{actions}</div> : undefined}          />        </div>      }    >      <div data-template-part="prop" data-template-name="children" className="flex min-w-0 flex-col gap-6">        {children}      </div>    </PageLayout>  )}

How it uses the layout

The template fills these slots of PageLayout. Each slot is fixed by the template, filled by a prop, or not used.

Detail page template in PageLayout: what the template fills, and what comes in as props.
Layout slotFilled withFixed or prop
PageLayout.headerbreadcrumbs, back linkfixed
PageLayout.headertitle, actionsprop
PageLayout.contentchildrenprop
PageLayout.asidecard framefixed
PageLayout.asidefacts about the itemprop

Fixed and variable

Fixed by the template (edit the constants at the top of the file):

  • Header layout with breadcrumbs and back link
  • Aside card frame

Props (the variable part):

  • title, breadcrumbs, backHref, actions
  • children
  • aside: the facts in the card

Install

npx shadcn@latest add https://globui.dev/r/detail-page-template.json

Code

The template is one file, and a page uses it. The page sets only what varies: no heights, no overflow.

On this page