layout

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 5 Imported by: 5

Documentation

Overview

templ: version: v0.3.1020

templ: version: v0.3.1020

templ: version: v0.3.1020

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

templ: version: v0.3.1020

templ: version: v0.3.1020

HTMX CDN version pinning and SRI hash lookup.

templ: version: v0.3.1020

templ: version: v0.3.1020

templ: version: v0.3.1020

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"
)

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 lg:block` — invisible on mobile. Pass a `display.Drawer` (or any mobile nav pattern) to MobileNav and AppShell will render it below lg 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.

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>
}

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), Pad=true (px-4 sm:px-6 lg:px-8).

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, Pad: false}) {
   { mapComponent }
}

func ContainerWidthIsValid added in v0.19.0

func ContainerWidthIsValid(w ContainerWidth) bool

ContainerWidthIsValid reports whether w is a recognized ContainerWidth.

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>
}

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.

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.

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).

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,
})

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(...)
}

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"})

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.

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)

Types

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. 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
	// 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.

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
	// Pad enables the responsive horizontal padding
	// (px-4 sm:px-6 lg:px-8). Default is true. Set to false when the
	// consumer manages padding (e.g. inside a Card or Grid).
	Pad 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 with responsive padding enabled.

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
}

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
	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
	// 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 response-targets extension still uses HTMXCDN unless
	// HTMXResponseTargets is false. See ADR-0007.
	HTMXSrc string
	// HTMXResponseTargets controls whether the htmx response-targets extension
	// is loaded alongside htmx. Defaults to true for backward compatibility;
	// set to false to skip the extra request when you don't use it.
	HTMXResponseTargets bool
	BodyClass           string
	Nonce               string
	HeadContent         templ.Component
	Footer              templ.Component
	// SecurityHeaders controls whether security meta tags are included
	SecurityHeaders 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 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.
	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
	// 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
}

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: 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