layout

package
v1.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 12 Imported by: 5

Documentation

Overview

Package layout provides page-level layout components including base HTML shell, containers, and structural templates.

HTMX CDN version pinning and SRI hash lookup.

Index

Examples

Constants

View Source
const (
	// DefaultThemeColor is the default light-mode theme color (indigo-600)
	DefaultThemeColor = "#4f46e5"
	// DefaultDarkThemeColor is the default dark-mode theme color (indigo-950)
	DefaultDarkThemeColor = "#1e1b4b"
)
View Source
const HTMXNone = "none"

HTMXNone is the sentinel value for PageProps.HTMXSrc that renders NO htmx runtime at all (backlog #282). Choose it when a page uses no htmx-powered components (or wires everything through Datastar): Base then emits no htmx <script>, no CDN preconnect/dns-prefetch hints, and no SRI — smaller pages, zero framework bytes. Every library component degrades gracefully without the runtime: wired anchors/forms fall back to plain navigation or no-op, and unwired markup is unaffected.

View Source
const HTMXSelfHost = "self"

HTMXSelfHost is the sentinel value for PageProps.HTMXSrc that triggers inline embedding of the HTMX source (v2.0 default). When HTMXSrc is set to HTMXSelfHost, the HTMX script is rendered inline via a <script> tag with the embedded source, avoiding any external CDN request.

Variables

This section is empty.

Functions

func AppShell added in v0.19.0

func AppShell(props AppShellProps) templ.Component

AppShell renders a sidebar + header + content application shell. It is the 2D layout primitive for admin panels and dashboards: a fixed-width sidebar on desktop, an optional sticky header, and a flexible main area. Use it to replace the hand-rolled `grid lg:grid-cols-[16rem_1fr]` snippet every consumer rebuilds.

AppShell renders INSIDE the `<main>` landmark provided by `layout.Base` — it does NOT emit its own `<main>` or skip-link (Base owns those, including the WCAG 2.4.1 Bypass Block requirement).

@layout.Base(props) {
   @layout.AppShell(layout.AppShellProps{
      Sidebar:  navSidebar,
      Header:   navHeader,
      Content:  dashboardContent(),
   })
}

Mobile navigation

The desktop Sidebar is hidden below the breakpoint — invisible on mobile. Pass a `display.Drawer` (or any mobile nav pattern) to MobileNav and AppShell will render it below the breakpoint only. This keeps layout free of the display import (respecting the import graph: layout → icons,utils only).

Grid-blowout safety

The main column is `minmax(0, 1fr)` (not bare `1fr`). Without the `minmax`, a wide `<table>` or long URL in the content forces the column wider than the viewport, causing page-wide horizontal scroll. See ADR-0016 and the "minmax(0, 1fr) grid blowout footgun" recipe.

Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.AppShell(layout.DefaultAppShellProps())
}

func AppShellBreakpointIsValid added in v1.18.0

func AppShellBreakpointIsValid(b AppShellBreakpoint) bool

AppShellBreakpointIsValid reports whether b is a defined breakpoint (the zero value counts as defined: it means "use the default lg").

func AsidePositionIsValid added in v0.19.0

func AsidePositionIsValid(p AsidePosition) bool

AsidePositionIsValid reports whether p is a recognized AsidePosition.

func Base

func Base(props PageProps) templ.Component

Base renders a complete HTML5 base layout with HTMX, theme support, and meta tags Use with children... to inject page content into the body

@layout.Base(props) {
  <h1>My Page</h1>
}
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.Base(layout.DefaultPageProps())
}

func Container added in v0.19.0

func Container(props ContainerProps) templ.Component

Container renders a centered max-width wrapper with responsive horizontal padding. Use it as the single source of truth for page content width and page gutter — replacing the hand-rolled `max-w-6xl mx-auto px-4 sm:px-6 lg:px-8` snippet repeated in every consumer.

Defaults

Width=ContainerWidthLG (max-w-7xl). The responsive gutter (px-4 sm:px-6 lg:px-8) is on by DEFAULT — including in a bare `ContainerProps{...}` struct literal, where the zero value is the padded common case. Set `NoPad: true` only for edge-to-edge content (maps, full-bleed tables) where the consumer manages padding. (Historical note: before NoPad existed, the flag was `Pad bool` with a documented default of true that struct literals silently violated — every literal had to remember `Pad: true` or lost the gutter. NoPad restores the Go idiom that the zero value is the default.)

Examples

Default dashboard width:

@layout.Container(layout.DefaultContainerProps()) {
   @display.Grid(display.DefaultGridProps()) { ... }
}

Long-form article:

@layout.Container(layout.ContainerProps{Width: layout.ContainerWidthProse}) {
   { articleBody }
}

Edge-to-edge map or chart (consumer manages padding):

@layout.Container(layout.ContainerProps{Width: layout.ContainerWidthFull, NoPad: true}) {
   { mapComponent }
}
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.Container(layout.DefaultContainerProps())
}

func ContainerWidthIsValid added in v0.19.0

func ContainerWidthIsValid(w ContainerWidth) bool

ContainerWidthIsValid reports whether w is a recognized ContainerWidth.

func MetaRefresh added in v1.22.0

func MetaRefresh(delaySeconds int, url string) templ.Component

MetaRefresh renders a `<meta http-equiv="refresh">` tag that reloads or redirects the page without JavaScript — the zero-JS retry/stale-session pattern (dnsblockd's login guard and allow-list retry pages both use it).

With url empty the page reloads itself after delay seconds; otherwise the browser navigates to url. Negative delays clamp to 0 (immediate).

@layout.MetaRefresh(5, "/login?retry=1")
@layout.MetaRefresh(30, "") // reload this page every 30s

func Minimal

func Minimal(props MinimalProps) templ.Component

Minimal renders a minimal HTML5 document without HTMX or external dependencies. Defaults Locale to "en" when empty (valid HTML lang attribute). Useful for static pages, emails, or PDF generation

@layout.Minimal(layout.MinimalProps{Title: "My Title"}) {
  <h1>Hello</h1>
}
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.Minimal(layout.DefaultMinimalProps())
}

func Script added in v0.7.0

func Script(nonce string, src string, attrs templ.Attributes) templ.Component

Script renders a CSP-safe external `<script src="...">` tag with the nonce applied automatically. Use it instead of a raw `<script>` tag so the nonce can never be forgotten — under a strict Content-Security-Policy a forgotten nonce silently breaks the script in the browser with no error in your logs.

The omit-empty nonce rule applies (issues #7/#9/#24): an empty nonce omits the attribute entirely — `nonce=""` is the dead-script class (strict-CSP browsers reject it) and, on external scripts, a pointless footgun.

Pass optional attributes (async, defer, type="module", crossorigin, …) via attrs. Do not pass `nonce` via attrs; the nonce parameter owns it.

@layout.Script(nonce, "/static/app.js", nil)
@layout.Script(nonce, "/static/app.js", templ.Attributes{"defer": true})
@layout.Script(nonce, "/static/tailwind.min.js", templ.Attributes{"type": "module"})
Example
package main

import (
	"bytes"
	"context"
	"fmt"

	"github.com/larsartmann/templ-components/layout"
)

func main() {
	var buf bytes.Buffer

	_ = layout.Script("nonce-abc", "/static/app.js", nil).Render(context.Background(), &buf)
	fmt.Println(buf.String())
	// Output will contain a CSP-safe script tag with nonce
}

func SidebarWidthIsValid added in v0.19.0

func SidebarWidthIsValid(w SidebarWidth) bool

SidebarWidthIsValid reports whether w is a recognized SidebarWidth.

func Split added in v0.19.0

func Split(props SplitProps) templ.Component

Split renders a 2-column main + aside layout. It is the second most common 2D pattern after AppShell: article+sidebar, detail+metadata, primary content + related links.

Aside semantics (landmark opt-in)

By default the aside column renders a plain <div> — a two-column layout grid, not a landmark. Set AsideLandmark: true (or name it via AsideLabel) to render a semantic <aside> complementary landmark; name landmarks with AsideLabel so they do not add unnamed entries to screen-reader rotors.

Both columns use minmax(0,1fr) per grid track to prevent grid blowout (see ADR-0016). The layout stacks to a single column below md (mobile).

Set ContainerAware: true to make the 2-column collapse respond to the parent container's width (CSS @container) instead of the viewport. Useful when Split is placed inside a sidebar, card body, drawer, or other constrained layout. See ADR-0018.

RTL / logical ordering

AsidePosition uses logical (Start/End) names, not physical (Left/Right). In LTR: Start=left, End=right. In RTL (dir="rtl"): Start=right, End=left. Source order + CSS grid auto-placement handles the mirror — no physical left/right utilities are emitted.

@layout.Split(layout.SplitProps{
   Main:   articleBody,
   Aside:  tocWidget,
   Ratio:  layout.SplitRatio1To3,
})
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.Split(layout.DefaultSplitProps())
}

func SplitRatioIsValid added in v0.19.0

func SplitRatioIsValid(r SplitRatio) bool

SplitRatioIsValid reports whether r is a recognized SplitRatio.

func Stack added in v0.19.0

func Stack(props StackProps) templ.Component

Stack renders a vertical flex container with typed gap spacing. It is the single source of truth for vertical rhythm — replacing the repeated `space-y-4` / `space-y-6` strings scattered across consumer pages.

Stack is the 1D vertical counterpart to Grid. Use Stack when only the vertical axis matters (a column of cards, a list of form fields, a stack of alerts). Use Grid when both rows AND columns matter (card dashboards, multi-column layouts). See ADR-0016.

@layout.Stack(layout.DefaultStackProps()) {
   @display.Card(...)
   @display.Card(...)
}
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.Stack(layout.DefaultStackProps())
}

func StackGapIsValid added in v0.19.0

func StackGapIsValid(g StackGap) bool

StackGapIsValid reports whether g is a recognized StackGap.

func Stylesheet added in v0.7.0

func Stylesheet(href string, attrs templ.Attributes) templ.Component

Stylesheet renders a CSP-safe `<link rel="stylesheet">` tag. Companion to Script — ensures consistent, auditable external resource loading. While stylesheets are not subject to script-src nonce restrictions, this helper provides a single pattern for all external resource tags.

Pass optional attributes (media, crossorigin, integrity) via attrs.

@layout.Stylesheet("/static/app.css", nil)
@layout.Stylesheet("/static/print.css", templ.Attributes{"media": "print"})
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.Stylesheet("/app.css", nil)
}

func ThemeScript

func ThemeScript(nonce string) templ.Component

ThemeScript renders the inline script that sets the dark mode class before paint. Also sets `color-scheme` so native scrollbars, form controls, and <details> match the active theme. Include this in your base template head to prevent FOUC.

Only needed for the TOGGLE dark mode strategy (user-controlled .dark class). If you use OS-following (prefers-color-scheme), you do NOT need this script — Tailwind v4's default dark: variant handles it automatically.

Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.ThemeScript("")
}

func ThemeToggle

func ThemeToggle(ariaLabel string, nonce string) templ.Component

ThemeToggle renders a theme toggle button with sun/moon icons. Uses role="switch" + aria-checked so screen-reader users know the active mode. Requires Tailwind CSS dark: variants and the ThemeScript in your base layout.

Only needed for the TOGGLE dark mode strategy (user-controlled .dark class). If you use OS-following (prefers-color-scheme), dark mode is automatic — there is nothing to toggle and this component is unnecessary.

@layout.ThemeToggle("Toggle theme", nonce)
Example
package main

import (
	"github.com/larsartmann/templ-components/layout"
)

func main() {
	_ = layout.ThemeToggle("Toggle theme", "")
}

Types

type AppShellBreakpoint added in v1.18.0

type AppShellBreakpoint string

AppShellBreakpoint is the viewport breakpoint at which AppShell switches from the mobile (single-column + MobileNav) to the desktop (sidebar grid) layout. The zero value resolves to AppShellBreakpointLG, the historical default.

const (
	// AppShellBreakpointUnspecified is the zero value; it renders as lg.
	AppShellBreakpointUnspecified AppShellBreakpoint = ""
	// AppShellBreakpointMD shows the sidebar from 768px up.
	AppShellBreakpointMD AppShellBreakpoint = "md"
	// AppShellBreakpointLG shows the sidebar from 1024px up (the default).
	AppShellBreakpointLG AppShellBreakpoint = "lg"
	// AppShellBreakpointXL shows the sidebar from 1280px up.
	AppShellBreakpointXL AppShellBreakpoint = "xl"
)

type AppShellProps added in v0.19.0

type AppShellProps struct {
	utils.BaseProps

	// Sidebar renders in the left grid column on desktop (lg+). Hidden on
	// mobile. Typically navigation.SidebarNav, but any templ.Component works.
	Sidebar templ.Component
	// MobileNav renders below lg only (lg:hidden). Optional. Use it to host a
	// display.Drawer or other mobile navigation pattern. Nil = no mobile nav.
	MobileNav templ.Component
	// Header renders inside the content column, sticky to the top when
	// StickyHeader is true. Typically navigation.Nav. Optional.
	Header templ.Component
	// Footer renders inside the content column, after Content, pinned to the
	// bottom of the viewport (mt-auto) at every breakpoint — the shell is a
	// full-height flex column below the grid breakpoint and a stretching grid
	// above it. Optional.
	Footer templ.Component
	// Content is the main body of the shell. Required.
	Content templ.Component
	// SidebarWidth controls the desktop sidebar column width via a CSS custom
	// property (--tc-sidebar-w). Default SidebarWidthMD (16rem). The main
	// column is always minmax(0, 1fr) — never bare 1fr (grid-blowout guard).
	SidebarWidth SidebarWidth
	// Breakpoint selects the viewport width at which the desktop sidebar
	// appears and the MobileNav hides — the hamburger breakpoint. Default
	// (zero value) keeps the historical lg: (1024px); md:/xl: cover
	// tablet-first and wide-dashboard shells (M22/F102).
	Breakpoint AppShellBreakpoint
	// StickyHeader pins the Header to the top of the content column on scroll.
	// Default true. Set to false for static headers.
	StickyHeader bool
	// Container, when true (default), wraps Content in a layout.Container at
	// ContainerWidth. Set to false when Content manages its own width
	// (e.g. an edge-to-edge table or map).
	Container bool
	// ContainerWidth is the max-width applied to the Content wrapper when
	// Container is true. Default ContainerWidthLG.
	ContainerWidth ContainerWidth
}

AppShellProps configures a sidebar + content application shell. This is the 2D layout primitive every admin dashboard rebuilds by hand: a fixed-width sidebar on desktop, a sticky optional header, and a flexible main column.

AppShell renders INSIDE the `<main>` landmark provided by `layout.Base` — it does NOT emit its own `<main>` or skip-link (Base owns those). Use it as the body content of Base:

@layout.Base(props) {
   @layout.AppShell(layout.AppShellProps{
      Sidebar:  navigation.SidebarNav(...),
      Header:   navigation.Nav(...),
      Content:  dashboardContent(),
   })
}

Mobile

The desktop sidebar is `hidden lg:block` — invisible below the lg breakpoint. For mobile navigation, render a separate `display.Drawer` (or any other mobile pattern) and pass it as the MobileNav slot. AppShell will render that slot only below lg. This keeps layout free of the display import and gives the consumer full control over mobile UX.

Empty-slot contract

Every slot is optional; nil renders predictably:

Sidebar    nil → no sidebar wrapper, NO --tc-sidebar-w style, single-column
             shell (the two-track grid is only emitted when a sidebar
             exists — emitting it anyway collapses the shell; fixed
             2026-09-08 after the dashboard recipe rendered ~16rem wide)
Header     nil → no <header> element
MobileNav  nil → nothing rendered below lg
Footer     nil → no <footer> element
Content    nil → empty content column (the shell frame still renders)

Embedded contexts and min-h-dvh

The shell stretches to the full viewport height (`min-h-dvh`) because it is designed as the direct child of `layout.Base`'s <main>. When embedding an AppShell inside a card or other non-page container (like the demo does), override the height via Class: `utils.Class` merges consumer classes last, so `Class: "min-h-0"` (or an explicit height) wins over the shell default.

func DefaultAppShellProps added in v0.19.0

func DefaultAppShellProps() AppShellProps

DefaultAppShellProps returns sensible defaults: MD sidebar, sticky header, Content wrapped in a Container at LG width.

type AsidePosition added in v0.19.0

type AsidePosition string

AsidePosition controls whether the Aside renders before (start) or after (end) the Main content. Both are logical positions — they auto-mirror in RTL contexts because the grid uses source order, not physical left/right.

const (
	// AsidePositionEnd renders Aside AFTER Main (default — sidebar on the
	// right in LTR, left in RTL).
	AsidePositionEnd AsidePosition = "end"
	// AsidePositionStart renders Aside BEFORE Main (sidebar on the left in
	// LTR, right in RTL).
	AsidePositionStart AsidePosition = "start"
	// AsidePositionDefault is the canonical default (End).
	AsidePositionDefault AsidePosition = AsidePositionEnd
)

type ContainerProps added in v0.19.0

type ContainerProps struct {
	utils.BaseProps

	// Width controls the max-width. Default is ContainerWidthLG.
	// Use ContainerWidthProse for long-form articles.
	Width ContainerWidth
	// NoPad suppresses the responsive horizontal gutter
	// (px-4 sm:px-6 lg:px-8). The gutter is ON by default — the zero
	// value is the padded common case, so struct literals keep it
	// without setting anything. Set NoPad to true only when the
	// consumer manages padding (e.g. edge-to-edge maps inside a
	// ContainerWidthFull wrapper, or nesting inside a Card/Grid).
	NoPad bool
}

ContainerProps configures a content Container — a centered max-width wrapper with responsive horizontal padding. This is the single source of truth for page content width; use it to replace the repeated `max-w-6xl mx-auto px-4 sm:px-6 lg:px-8` snippet.

All new page content should be wrapped in a Container. AppShell already wraps its main content slot in a Container by default — set AppShellProps .Container=false to opt out (e.g. when the page itself manages width).

func DefaultContainerProps added in v0.19.0

func DefaultContainerProps() ContainerProps

DefaultContainerProps returns sensible defaults: LG width. The responsive gutter needs no explicit opt-in — it is the zero-value default.

type ContainerWidth added in v0.19.0

type ContainerWidth string

ContainerWidth is a typed enum for the maximum width of a Container. Unknown values fall back to ContainerWidthLG (graceful degradation — never panic).

const (
	// ContainerWidthSM narrows the container to max-w-3xl (48rem) — for
	// sign-in forms, focused single-purpose pages, and short articles.
	ContainerWidthSM ContainerWidth = "sm"
	// ContainerWidthMD is max-w-5xl (64rem) — for two-column layouts and
	// medium-density pages.
	ContainerWidthMD ContainerWidth = "md"
	// ContainerWidthLG is max-w-7xl (80rem) — the application default for
	// dashboards, list pages, and admin panels.
	ContainerWidthLG ContainerWidth = "lg"
	// ContainerWidthXL is max-w-[90rem] — for wide dashboards and data-heavy
	// layouts that need more horizontal real estate than LG.
	ContainerWidthXL ContainerWidth = "xl"
	// ContainerWidthFull is max-w-full — full-bleed edge-to-edge content.
	// Pair with explicit px-* padding on the consumer side.
	ContainerWidthFull ContainerWidth = "full"
	// ContainerWidthProse is max-w-prose (65ch) — for long-form articles and
	// documentation where line length must stay readable.
	ContainerWidthProse ContainerWidth = "prose"
	// ContainerWidthDefault is the canonical default (LG).
	ContainerWidthDefault ContainerWidth = ContainerWidthLG
)

type HTMXVersion added in v0.6.0

type HTMXVersion string

HTMXVersion is a pinned HTMX main-script version. Use the exported constants (e.g. HTMXVersion2_0_10) for compile-time safety; custom versions can be constructed via HTMXVersion("x.y.z") but will render without SRI (no integrity hash).

const (
	// HTMXVersion2_0_10 is the htmx main-script version DefaultPageProps pins.
	HTMXVersion2_0_10 HTMXVersion = "2.0.10"
)

type MinimalProps

type MinimalProps struct {
	Title  string
	Locale string
	// SEO optionally emits the crawler/SEO head tags (robots noindex,
	// canonical, hreflang alternates, JSON-LD) with the same semantics as
	// PageProps.SEO. Zero value emits nothing — Minimal stays
	// dependency-free unless a tag is explicitly requested.
	SEO SEOMeta
	// HeadContent, when set, renders verbatim into <head> after the SEO
	// tags — the Minimal twin of PageProps.HeadContent: critical inline
	// styles, fonts, or extra meta tags without graduating to Base.
	HeadContent templ.Component
}

MinimalProps configures a minimal HTML5 document without HTMX or external dependencies

func DefaultMinimalProps

func DefaultMinimalProps() MinimalProps

DefaultMinimalProps returns sensible defaults for MinimalProps

type PageProps

type PageProps struct {
	Title       string
	Description string
	Locale      string
	OGImage     string
	// OGType sets the Open Graph type for the og:type meta tag. Defaults to
	// "website" when empty. Common values: "website", "profile", "article".
	OGType         string
	ThemeColor     string
	DarkThemeColor string
	// CSSPath auto-injects `<link rel="stylesheet" href={CSSPath}>` into the
	// page `<head>`. DefaultPageProps sets it to "/app.css". Set CSSPath to ""
	// to suppress the stylesheet tag entirely — for example when you load
	// Tailwind via the browser Play CDN, embed styles inline, or serve your
	// own stylesheet through HeadContent.
	CSSPath string
	Favicon string
	// HTMXVersion controls auto-injection of the htmx `<script>` tag.
	// DefaultPageProps sets it to HTMXVersion2_0_10, which emits
	// `<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.10" ...>`.
	// Set HTMXVersion to "" (the zero value) to disable auto-injection — for
	// example when you bundle htmx yourself, serve it from a local path via
	// HeadContent, or don't use htmx at all.
	HTMXVersion HTMXVersion
	// HTMXUseSRI adds an `integrity` attribute (subresource integrity) to the
	// auto-injected htmx script. Only meaningful when HTMXVersion is non-empty.
	HTMXUseSRI bool
	// HTMXCDN overrides the CDN base URL for htmx scripts. When empty, defaults
	// to "https://cdn.jsdelivr.net/npm". Set to e.g. "https://unpkg.com" to use
	// a different CDN, or a self-hosted origin. Both the htmx main script and
	// the response-targets extension derive their URLs from this value.
	HTMXCDN string
	// HTMXNone renders NO htmx runtime at all: no script, no preconnect — for
	// pages that use no htmx-powered components (backlog #282). Components
	// degrade gracefully without the runtime.
	// HTMXSrc, when non-empty, switches the htmx main script from the CDN URL
	// to a self-hosted path. The value becomes the <script src> attribute verbatim.
	// Example: "/static/htmx.min.js". When set, HTMXVersion/HTMXCDN/HTMXUseSRI
	// are ignored for the main script (no SRI is emitted because the asset is
	// same-origin). The CDN response-targets extension is also suppressed — a
	// self-hoster manages extensions too. Add your own <script> tag for it if
	// needed. See ADR-0007.
	HTMXSrc string
	// HTMXResponseTargets controls whether the htmx response-targets extension
	// is loaded from the CDN alongside htmx. Defaults to true for backward
	// compatibility; set to false to skip the extra request. Ignored when
	// HTMXSrc is set (self-host implies you manage extensions).
	HTMXResponseTargets bool
	// SkipLinkText overrides the default "Skip to main content" text for the
	// accessibility skip link. Useful for i18n.
	SkipLinkText string
	// HTMLDataAttrs adds extra attributes to the <html> element (e.g.
	// data-nonce for Datastar's opt-in CSP mode, which reads root-level
	// attributes off document.documentElement). Keys are rendered as-is.
	// Pass nil for no extra attributes — the zero value keeps existing pages
	// byte-identical.
	HTMLDataAttrs templ.Attributes
	// BodyDataAttrs adds extra attributes to the <body> element (e.g.
	// data-language). Keys are rendered as-is. Pass nil for no extra attributes.
	BodyDataAttrs templ.Attributes
	BodyClass     string
	Nonce         string
	// SEO carries crawler/SEO head tags (noindex, canonical, hreflang
	// alternates, JSON-LD). Zero value emits nothing — existing pages keep
	// byte-identical output.
	SEO         SEOMeta
	HeadContent templ.Component
	Footer      templ.Component
	// SecurityHeaders is a deprecated no-op: HTTP security headers cannot be
	// expressed as meta tags (browsers ignore meta http-equiv for
	// X-Content-Type-Options and Referrer-Policy, and the latter was never a
	// registered http-equiv, so the emitted tags were invalid HTML that
	// failed HTML validation). Send real headers at the server instead. The
	// field remains for API compatibility and emits nothing.
	SecurityHeaders bool
	// NoMainWrapper, when true, omits the `<main id="main-content">` wrapper
	// and renders children directly under <body> — for pages whose content is
	// itself a full landmark component (errorpage.ErrorPage and
	// errorpage.NotFound404 render their own <main>; nesting a second <main>
	// is an HTML violation and an axe/html-validate failure). The skip link
	// still targets #main-content, so give your own landmark that ID
	// (errorpage.NotFound404Props.BaseProps.ID accepts it, plus a tabindex
	// Attr for skip-link focus). Zero value keeps the wrapper — existing
	// pages render byte-identically.
	NoMainWrapper bool
	// NoThemeScript, when true, skips the inline dark-mode preload script
	// Base emits in <head> (the ThemeScript that reads localStorage['theme']
	// and sets the .dark class + color-scheme before paint). For consumers
	// running a strict CSP that allows no inline scripts: ship the same
	// logic as a same-origin, render-blocking <script src> via HeadContent,
	// or drop the script entirely when the page only follows
	// prefers-color-scheme (the script is only needed for the toggle dark
	// strategy — see ThemeScript's doc). Zero value keeps the script —
	// existing pages render byte-identically.
	NoThemeScript bool
}

PageProps configures the base page layout

func DefaultPageProps

func DefaultPageProps() PageProps

DefaultPageProps returns sensible defaults for PageProps.

Two fields auto-inject `<head>` tags and are the most common defaults to override when integrating with an existing asset pipeline:

  • CSSPath ("/app.css") — set to "" if you serve styles another way.
  • HTMXVersion (HTMXVersion2_0_10) — set to "" to suppress the htmx CDN tag.

HTMXResponseTargets defaults to true (loads the response-targets extension alongside htmx); set to false to skip the extra request when unused.

type SEOAlternate added in v1.15.0

type SEOAlternate struct {
	Lang string
	URL  string
}

SEOAlternate is one language-alternate URL for hreflang annotation. Lang is the hreflang tag ("en", "de", or "x-default"); URL must be absolute.

type SEOMeta added in v1.15.0

type SEOMeta struct {
	// NoIndex emits <meta name="robots" content="noindex"> for transient
	// pages (tailored variants, operator surfaces) that must stay out of
	// search results.
	NoIndex bool
	// Canonical emits <link rel="canonical">; empty omits the tag. Should
	// be an absolute URL.
	Canonical string
	// Alternates emits one <link rel="alternate" hreflang> per entry, in
	// order. Empty omits all.
	Alternates []SEOAlternate
	// JSONLD is embedded VERBATIM as <script type="application/ld+json">
	// via templ.Raw — it bypasses templ's auto-escaping by design. Only
	// pass trusted, correctly serialized JSON (marshal your struct with
	// encoding/json before handing it here); never interpolate user input.
	JSONLD string
}

SEOMeta carries the crawler/SEO head tags consumers otherwise hand-roll: robots noindex, canonical URL, hreflang alternates, and JSON-LD structured data. Empty fields emit nothing.

type SidebarWidth added in v0.19.0

type SidebarWidth string

SidebarWidth is a typed enum for the desktop sidebar column width of an AppShell. Unknown values fall back to SidebarWidthMD.

const (
	// SidebarWidthSM is 12rem (w-48) — compact icon-heavy sidebars.
	SidebarWidthSM SidebarWidth = "sm"
	// SidebarWidthMD is 16rem (w-64) — the default, matches SidebarNav.
	SidebarWidthMD SidebarWidth = "md"
	// SidebarWidthLG is 20rem (w-80) — wide sidebars with labels + descriptions.
	SidebarWidthLG SidebarWidth = "lg"
	// SidebarWidthAuto sizes the sidebar to its content via `auto` in the grid
	// template. Use when the sidebar (e.g. SidebarNav) sets its own width and
	// the grid should not constrain it. CAUTION with SidebarNav specifically:
	// SidebarNav hardcodes `w-64` (16rem). Under SidebarWidthAuto the grid
	// track sizes to that element, which is fine — but under SidebarWidthSM
	// (12rem) the fixed `w-64` sidebar OVERFLOWS its 12rem track (the demo hit
	// this: it now uses the MD default). Either widen SidebarWidth, override
	// SidebarNav's width via its Class, or use Auto and let `w-64` win.
	SidebarWidthAuto SidebarWidth = "auto"
	// SidebarWidthDefault is the canonical default (MD).
	SidebarWidthDefault SidebarWidth = SidebarWidthMD
)

type SplitProps added in v0.19.0

type SplitProps struct {
	utils.BaseProps

	// Main is the primary content column. Required.
	Main templ.Component
	// Aside is the secondary content column (sidebar, metadata, related
	// links). Required.
	Aside templ.Component
	// AsidePosition controls whether Aside renders before or after Main.
	// Default AsidePositionEnd (aside after main in source order + visual).
	AsidePosition AsidePosition
	// AsideLabel, when set, names the aside landmark (aria-label). Setting it
	// renders the column as a semantic <aside> complementary landmark — a
	// labeled non-landmark element is invalid HTML-ARIA (aria-label on a
	// roleless generic), so the label implies the landmark. Without it, an
	// unnamed <aside> would show up unlabeled in screen-reader landmark
	// rotors. Empty keeps the plain element (see AsideLandmark).
	AsideLabel string
	// AsideLandmark, when true, renders the aside column as a semantic
	// <aside> complementary landmark instead of the default plain <div>.
	// Use it when the column genuinely holds complementary content (related
	// links, metadata, TOC); leave it false for plain two-column layouts so
	// screen-reader landmark rotors stay clean. Zero value = <div>.
	AsideLandmark bool
	// Ratio controls the relative column widths. Default SplitRatio1To3
	// (main 2/3, aside 1/3).
	Ratio SplitRatio
	// Gap controls spacing between the two columns. Default GridGapLG (1.5rem).
	Gap string
	// ContainerAware, when true, wraps the Split in a @container div and swaps
	// md: breakpoint classes for @md: so the 2-column collapse responds to the
	// parent container's width, not the viewport. Useful when Split is placed
	// inside a sidebar, card body, drawer, or other constrained layout. Default
	// false (viewport breakpoints). See ADR-0018.
	ContainerAware bool
}

SplitProps configures a 2-column content + aside layout — the second most common 2D pattern after AppShell. Use it for article+sidebar widgets, detail+metadata, or any main-content-with-secondary-content layout.

Both columns use minmax(0,1fr) per grid track to prevent grid blowout from wide content (see ADR-0016). The layout collapses to a single stacked column below the md breakpoint (mobile-friendly by default).

func DefaultSplitProps added in v0.19.0

func DefaultSplitProps() SplitProps

DefaultSplitProps returns sensible defaults: plain layout column, aside at end, 1To3 ratio, gap-6.

type SplitRatio added in v0.19.0

type SplitRatio string

SplitRatio is a typed enum for the relative widths of the Main and Aside columns in a Split. Unknown values fall back to SplitRatio1To3.

const (
	// SplitRatio1To3 — Main takes 2/3, Aside takes 1/3 (default, classic
	// article+sidebar pattern).
	SplitRatio1To3 SplitRatio = "1-3"
	// SplitRatio1To4 — Main takes 3/4, Aside takes 1/4 (wide main, narrow aside).
	SplitRatio1To4 SplitRatio = "1-4"
	// SplitRatio1To2 — Main and Aside are equal width.
	SplitRatio1To2 SplitRatio = "1-2"
	// SplitRatioDefault is the canonical default (1To3).
	SplitRatioDefault SplitRatio = SplitRatio1To3
)

type StackGap added in v0.19.0

type StackGap string

StackGap is a typed enum for vertical spacing between stacked children. Unknown values fall back to StackGapMD. Mirrors the GridGap shape so the two composition primitives share a vocabulary.

const (
	StackGapNone StackGap = "none"
	StackGapSM   StackGap = "sm" // space-y-2 (0.5rem)
	StackGapMD   StackGap = "md" // space-y-4 (1rem) — default
	StackGapLG   StackGap = "lg" // space-y-6 (1.5rem)
	StackGapXL   StackGap = "xl" // space-y-8 (2rem)
	// StackGapDefault is the canonical default (MD).
	StackGapDefault StackGap = StackGapMD
)

type StackProps added in v0.19.0

type StackProps struct {
	utils.BaseProps

	// Gap controls the vertical space between children. Default StackGapMD
	// when zero or unknown.
	Gap StackGap
}

StackProps configures a vertical stack of children with consistent spacing. Use Stack to replace repeated `space-y-N` strings — it is the 1D vertical counterpart to the 2D Grid primitive. Stack is flex-col (NOT grid): both axes do not matter, only the vertical rhythm. See ADR-0016.

func DefaultStackProps added in v0.19.0

func DefaultStackProps() StackProps

DefaultStackProps returns sensible defaults: MD gap.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL