Presentation · themes · CSS boundaries
Style the whole interface, one vocabulary at a time¶
Want to try the styles? Open the interactive Styles Explorer to compare themes, browse real components, inspect tokens, and export CSS without a backend. For the visual QA app with all the appearance and emphasis buttons, open the Component Explorer.
Hedron styling starts with semantic Python props and ends as ordinary HTML, CSS, and
data-hedron-* markers. Use the built-ins for the common 80%, a DesignSystem for
brand-wide decisions, and scoped CSS when a component genuinely owns a visual detail.
This page is a visual tour of the styling surface: see the result first, then copy the smallest useful Python pattern.
What is new in 0.60
0.60 completes the custom-theme platform on top of the 0.59 CSS foundation: absolute CSS Color
4 values with deterministic sRGB fallbacks, immutable ThemeSpec / ThemePatch authoring,
registry-derived validation profiles, accessibility-mode mappings, bounded recipe families,
server-first theme preferences, and zero-application-CSS contracts for the remaining product
surfaces. The complete capability matrix—including the 0.59 CSS foundation and its
Progressive, Experimental, and Deferred tiers—is in Modern CSS in 1.0.
The 0.60 styling upgrade in one view¶
| Need | 0.60 path | What remains application-owned |
|---|---|---|
| Use a brand palette | DesignSystem.brand() or a registered Theme |
Product identity and content |
| Author a reusable theme | Immutable ThemeSpec through ThemeBuilder |
Which tokens and visual decisions to publish |
| Layer a bounded change | ThemePatch / ThemeSpec.apply_patches() |
Review and approval of the change |
| Validate a theme | validate_theme_spec() or style conform |
The profile that matches the package surface |
| Ship a theme | Data-only package_theme() archive |
License metadata and distribution |
| Select a preference | ThemePreference + ThemePicker |
Persistence, authorization, and the POST route |
| Style product chrome and workflows | Brand, ToastHost, ConnectorFlow, ScrollRegion |
Content, state, and actions |
The rule is simple: use the smallest bounded contract that expresses the intent. A custom theme may add tokens, modes, accessibility mappings, and presentation recipes; it may not add behavior, private selector APIs, remote assets, or arbitrary CSS values.
The styling stack¶
There are four layers. Start at the top and move down only when the layer above cannot express the intent.
<span class="sg-recipe-arrow" aria-hidden="true">→</span>
<div class="sg-recipe-node"><strong>Scoped CSS</strong><small>component-owned detail</small>
</div>
| Layer | Use it for | Examples |
|---|---|---|
DesignSystem / Theme |
Brand and app-wide decisions | Accent palette, geometry, typography, dark mode |
| Shared presentation props | Repeated component intent | appearance="soft", density="compact", gap="lg" |
StyleRecipe |
Semantic feature defaults | primary_action, form_surface, metadata |
Component styles.css |
A component's unique visual language | A callout rail, a chart legend, a custom animation |
The shortest path
If you are styling a built-in, look for a semantic prop first. If several features need the same combination of props, make a recipe. If the style belongs to one component and has no useful semantic vocabulary, colocate scoped CSS with that component.
Hedron styling and CSS: the crosswalk¶
The table below maps Hedron's public styling features to the CSS concepts they compile to or intentionally stand in for. It describes the Hedron 0.60 contract, not the support level of a particular browser. Not implemented means that Hedron does not currently own a public API or styling contract for the CSS feature; it is not a promise that an arbitrary application stylesheet can safely use it.
| Area | Hedron feature | Equivalent CSS feature(s) | Hedron status | Boundary or fallback |
|---|---|---|---|---|
| Foundation | default_styles=True |
Bundled reset, base rules, component selectors, custom properties | Supported | Disable with default_styles=False; application and component CSS remain separate. |
| Foundation | Shared presentation props: size, density, appearance, emphasis, width, shape |
data-hedron-* selectors; color, background, border, border-radius, box-shadow, sizing |
Supported | Closed vocabularies keep themes and components interoperable. |
| Foundation | Controls: Button, LinkButton, IconButton |
inline-flex, padding, line-height, :hover, :focus-visible, :disabled |
Supported | Native control semantics and focus behavior remain authoritative. |
| Foundation | Form controls and validation states | appearance, field sizing, :focus-visible, :invalid, error/help selectors |
Supported | Labels, errors, and native keyboard behavior are not styling concerns. |
| Foundation | Surface and Card |
background, border, border-radius, box-shadow, padding |
Supported | appearance, padding, density, and elevation are finite props. |
| Foundation | Alert, Badge, Status, StateView |
Semantic color/border/background plus non-color state indicators | Supported | Tone never relies on color alone; loading/error/empty branches stay complete. |
| Foundation | Typography roles on Text, Heading, and Typography |
font-family, font-size, font-weight, line-height, letter-spacing |
Supported | Use roles instead of arbitrary per-element font sizes. |
| Foundation | Modern typography and local font assets | text-wrap: balance, text-wrap: pretty, hyphens, overflow-wrap, font-size-adjust, variable fonts |
Supported | System fallback stacks and ordinary wrapping remain the compatibility path. |
| Foundation | overflow and lines= |
overflow, overflow-wrap, word-break, text-overflow, line clamping |
Supported | wrap, break, truncate, and clip are explicit; clamping never invents content. |
| Layout | Stack and Inline |
Flexbox: display: flex, flex-direction, align-items, gap |
Supported | Named gap tokens are CSP-safe. |
| Layout | Grid, FormGrid, and GridItem |
CSS Grid: grid-template-columns, grid-column, gap |
Supported | Breakpoint maps are mobile-first and preserve DOM/read order. |
| Layout | Container with query="inline-size" |
container-type: inline-size, container-name, @container |
Supported | Viewport/static layout remains the fallback. |
| Layout | Responsive maps: base, sm, md, lg, xl |
@media (min-width: ...) |
Supported | Breakpoints are finite; arbitrary media-query strings are not a Python API. |
| Layout | gap, padding, track, width, and content-width tokens |
gap, padding, max-inline-size, inline-size, minmax() |
Supported | Unsupported lengths fail diagnostically instead of silently remapping. |
| Layout | SplitView, MasterDetail, PageHeader, ActionGroup |
Grid/flex tracks, minmax(), clamp(), align-*, justify-* |
Supported | Components own responsive collapse and preserve source order. |
| Layout | Responsive Table policies |
overflow, responsive table wrappers, @media layout adjustments |
Supported | scroll, stack, and priority are bounded policies; table semantics remain intact. |
| Layout | ScrollRegion |
overflow: auto, logical overflow, scrollbar affordance |
Supported | Bounded scrolling must not replace list, table, log, or timeline semantics. |
| Layout | Intrinsic and fluid sizing | min(), max(), clamp(), minmax(), fit-content(), aspect-ratio |
Supported | Ordinary lengths and static layout provide the compatibility path. |
| Layout | Dynamic viewport and safe-area behavior | svh, lvh, dvh, env(safe-area-inset-*) |
Supported | Static viewport and zero-inset fallbacks remain usable. |
| Layout | Logical and bidirectional layout | Logical properties/values, direction, writing-mode, margin-inline, inset-block |
Supported | Physical properties are limited to documented exceptions. |
| Layout | Grid alignment enhancement | subgrid for grid-template-columns / grid-template-rows |
Progressive | Ordinary independent Grid/FormGrid tracks are the fallback. |
| Theme | Theme and DesignSystem tokens |
CSS custom properties such as --color-*, --space-*, --font-* |
Supported | Tokens are the dynamic theming path; values are validated and emitted deterministically. |
| Theme | ThemeBuilder, immutable ThemeSpec, ThemePatch |
Canonical custom-property declarations and finite token overrides | Supported | Theme packages are data-only; no executable hooks or runtime CSS injection. |
| Theme | Light/dark modes and ColorMode |
[data-theme], explicit mode selectors, @media (prefers-color-scheme: dark) |
Supported | Explicit markers and duplicated token rules are the baseline. |
| Theme | Finite theme variants | [data-hedron-variant="..."] and additive custom-property overrides |
Supported | Variant names are validated; arbitrary selectors are not generated from user input. |
| Theme | StyleScope |
Inherited custom properties and scoped data-hedron-* markers |
Supported | Scope changes presentation only; it does not change behavior or DOM order. |
| Theme | StyleRecipe |
Reusable selector/property combinations resolved before rendering | Supported | Recipes are family-scoped and explicit component props win. |
| Theme | Server-first ThemePreference and ThemePicker |
Theme/color-mode markers, custom-property selection, color-scheme hints |
Supported | The server owns allowlisting, persistence, and authorization; the optional boot helper only reduces flash. |
| Theme | Brand geometry, density, elevation, motion, and navigation presets | border-radius, spacing variables, box-shadow, transition-duration, width variables |
Supported | Presets compile to bounded tokens, not arbitrary CSS declarations. |
| Theme | Modern absolute colors and generated palettes | color-mix(), hwb(), lab(), lch(), oklab(), oklch(), color() |
Supported | A canonical sRGB fallback precedes enhanced wide-gamut output. |
| Theme | Accessibility theme modes | @media (forced-colors: active), prefers-contrast, prefers-reduced-transparency |
Supported | Semantic focus, contrast, print, and non-color fallbacks remain required. |
| Compiler | Component styles.css and StyleSymbols |
Local class/keyframe names, :global(...), deterministic source maps |
Supported | Local symbols are rewritten; remote resources and unsafe globals are rejected. |
| Compiler | CSS grammar-aware compilation | Selectors, declarations, descriptors, strings, comments, URLs, custom identifiers | Supported | Malformed or ambiguous syntax fails during the build, not at runtime. |
| Compiler | Nested component CSS | CSS Nesting Level 1, &, nested conditional rules |
Supported | Hedron emits browser-valid scoped CSS. |
| Compiler | Conditional and future at-rules | @media, @supports, @container, @scope, @starting-style |
Supported | Safe syntax is preserved; unsafe or ambiguous rewrites are rejected. |
| Compiler | Local styles and assets | @import, url(), @font-face, image-set() |
Supported | Registered local assets resolve at build time; remote imports do not. |
| Cascade | Hedron layer order | @layer reset, tokens, base, components, utilities, overrides |
Supported | One deterministic cascade authority prevents competing theme registries. |
| Cascade | Specificity containment and relational selectors | :where(), :is(), :not(), :has(), specificity normalization |
Supported | Public semantic markers are preferred over private selector theming. |
| Cascade | Native scope enhancement | @scope, :scope |
Progressive | Hedron's marker/token boundary is the compatibility fallback. |
| Overlay | Dialog, Popover, ContextMenu, Tooltip, ToastHost |
<dialog>, Popover API, :popover-open, ::backdrop, top layer, z-index tokens |
Supported | Native/details/static flow remains usable when enhanced APIs are unavailable. |
| Overlay | Logical overlay placement and collision | inset-*, position-area, position-try-fallbacks |
Progressive | Hedron's finite placement vocabulary falls back to logical flow or bounded positioning. |
| Product surfaces | AppShell, Brand, AppFooter, ConnectorFlow, and workflow chrome |
Grid/flex shell layout, logical positioning, overflow, status and connector selectors | Supported | Components render presentation state supplied by the application; they do not own auth, execution, polling, or authorization. |
| Motion | Built-in motion presets and reduced motion | transition, animation, @media (prefers-reduced-motion: reduce) |
Supported | motion="none" and preference media produce a stable, immediate presentation. |
| Motion | Entry/exit transitions | @starting-style, transition-behavior: allow-discrete |
Progressive | Open/closed state changes remain correct without the enhancement. |
| Motion | View-transition enhancement | view-transition-name, view-transition-class, View Transitions API |
Progressive | Ordinary navigation or HTMX swaps preserve focus, history, and server state. |
| Motion | Scroll-driven animation | animation-timeline, scroll(), view(), timeline-scope |
Experimental | Decorative only; static complete presentation is the fallback. |
| Media | Interaction preferences | prefers-reduced-motion, forced-colors, prefers-contrast, hover, pointer |
Supported | The non-enhanced path remains keyboard-usable and non-color-dependent. |
| Media | Print presentation | @media print, break-*, print-color-adjust |
Supported | Source order, links, forms, data, statuses, and page breaks remain readable. |
| Performance | Containment and deferred rendering | contain, content-visibility, contain-intrinsic-size |
Progressive | Fully rendered content is the fallback; use only in benchmarked surfaces. |
| Unimplemented CSS | Style queries | @container style(...) |
Progressive | No public Hedron style-query API; internal adaptation is bounded, with semantic markers and inherited tokens as the fallback. |
| Unimplemented CSS | CSS masonry | masonry track sizing / masonry layout |
Not implemented | Use ordinary Grid; masonry is outside the 0.60 contract. |
| Unimplemented CSS | Houdini worklets | paint(), layout(), Paint/Layout Worklets |
Not implemented | Hedron does not add a browser-execution boundary for styling. |
| Unimplemented CSS | Free-form styling from Python | Arbitrary inline style, property-value DSLs, utility-string classes |
Not implemented | Use semantic props, tokens, recipes, or component styles.css. |
| Unimplemented CSS | Runtime style injection | Client-side <style> injection or runtime CSS compilation |
Not implemented | Production CSS is built, fingerprinted, and served as an asset. |
| Unimplemented CSS | Remote style dependencies | Remote @import, automatic remote fonts, implicit remote assets |
Not implemented | Register and fingerprint local assets explicitly. |
| Unimplemented CSS | Private selector theming | Arbitrary descendant/private selectors as a theme API | Not implemented | Use public parts, slots, tokens, markers, and documented component contracts. |
For the machine-readable capability inventory, including gates and exact fallback language, see
modern-css-inventory-059.toml
and the Modern CSS in 1.0 guide.
1. Start with the shared vocabulary¶
The presentation vocabulary is intentionally finite. That gives themes one predictable set of hooks and makes invalid combinations fail early instead of becoming one-off CSS.
The most-used values are:
Controls: appearance and emphasis are separate¶
appearance describes the treatment; emphasis describes the meaning. Keeping them
separate lets one theme make a primary action solid, a secondary action outlined, and a
danger action high-contrast without inventing a new prop for every combination.
from hedron import Button
Button("Save changes", appearance="solid", emphasis="primary", size="md")
Button("Cancel", appearance="outline", emphasis="secondary")
Button("Delete workspace", appearance="solid", emphasis="danger")
Legacy variant="primary|secondary|danger" remains supported. Prefer the shared
appearance / emphasis vocabulary when you want the full presentation system to
coordinate the result.
2. Build hierarchy with layout primitives¶
Layout components keep spacing and responsive behavior in the same vocabulary as the rest of the design. Named gaps are CSP-safe and preserve DOM order.
from hedron import Grid, GridItem, Heading, Inline, Stack, Text
Stack(
Heading("Deployment history", level=2),
Text("Recent releases and their current state."),
Inline("Filter", "Export", gap="sm"),
gap="lg",
)
Grid(
GridItem(Text("Overview"), span={"base": 1, "md": 2}),
Text("Activity"),
columns={"base": 1, "md": 3},
gap="md",
)
Grid and FormGrid accept breakpoint maps. The base value is the mobile-first
default; sm, md, lg, and xl progressively enhance it. GridItem changes span,
not reading order.
3. Give surfaces depth without hand-written CSS¶
Use Surface for a visual grouping and Card when you also need header/body/footer
slots. Appearance, padding, density, and elevation are explicit, inspectable choices.
from hedron import Card, Surface, Text
Surface(
Text("Queue is healthy", role="body"),
appearance="raised",
padding="lg",
elevation="md",
)
Card(
Text("42 deployments this month"),
title="Release activity",
appearance="raised",
padding="md",
elevation="sm",
)
The shared components own their baseline CSS. You can still add class_ for a local
hook, but a class should refine a semantic component rather than replace it.
4. Treat typography as a role, not a font-size hunt¶
Text, Heading, and Typography accept roles such as display, title, body,
label, caption, and mono. Roles let themes retune hierarchy globally.
from hedron import Heading, Text
Heading("Release activity", level=2, role="title")
Text("42,018", role="display", as_="strong")
Text("Updated 2 minutes ago", role="caption", overflow="truncate")
Keep the native heading level logical. Use role to change its visual treatment; do not
skip from h2 to h5 because the smaller size looks right.
Overflow is also a design decision¶
Use wrap for ordinary copy, break for long identifiers, truncate for compact
single-line metadata, and clip only when the content is intentionally decorative.
When lines= is supplied, it creates a bounded multi-line clamp.
5. Make forms feel like part of the same system¶
FormGrid gives fields a responsive column map. Pair it with FormField, labels, and
the built-in controls so focus rings, spacing, and validation states stay consistent.
from hedron import FormField, FormGrid, TextInput
FormGrid(
FormField(
name="name",
label="Workspace name",
control=TextInput("name", required=True),
),
FormField(
name="region",
label="Region",
control=TextInput("region", value="us-east"),
),
columns={"base": 1, "md": 2},
gap="md",
)
For a complete POST, add Form, a CSRF field, and
SubmitButton. Styling should never be the reason
to remove a native label, focusable control, or validation message.
6. Use status and state components consistently¶
Tone carries meaning; appearance carries treatment. A success badge and a success alert can share the same semantic tone while taking different amounts of attention.
from hedron import Alert, Badge, Status
Badge("Succeeded", tone="success", size="sm")
Alert("The connector needs new credentials.", tone="danger", appearance="soft")
Status("Processing", tone="info", size="sm")
For full-page loading, empty, permission, offline, and error branches, use
StateView. Make every branch a valid and styled state;
HTMX swaps should not turn a polished screen into an unstyled fragment.
7. Create a brand with DesignSystem¶
DesignSystem.brand() turns a small set of inputs into a coordinated Theme
with light/dark palettes, accessibility checks, geometry, density, typography, motion,
elevation, and navigation width.
from hedron import DesignSystem, Hedron, StyleRecipe
design = DesignSystem.brand(
"northstar",
accent="#2563eb",
geometry="soft",
typography="system-sans",
density="comfortable",
elevation="subtle",
motion="calm",
navigation="default",
)
app = Hedron(
title="Northstar",
theme=design,
session_secret="replace-in-production",
)
The accent is a safe color seed, not arbitrary CSS. Hedron can adjust a generated value when
needed to satisfy the required contrast pairs and records that adjustment in the design plan.
In 0.60 the accent may be a hex string or an absolute Color; variables, gradients, URLs,
and other arbitrary CSS are rejected. DesignSystem also accepts an existing Theme through
from_theme() when your organization already owns the token contract.
Preview and audit the result¶
hedron theme check
hedron theme check --theme aurora --format json
hedron --app app:app style explain --format human
hedron --app app:app style preview --output .artifacts/northstar-gallery --mode all
hedron --app app:app style diff default northstar
style preview writes a deterministic, data-free gallery. It is useful in design review
and CI artifacts; it does not execute application callbacks or expose application data.
The canonical 0.60 custom-theme path¶
Use ThemeBuilder for fluent authoring, but treat the immutable ThemeSpec it produces as the
source of truth. Start with the narrowest validation profile that covers the package or app; move
to forms, data, workflow, or complete when the theme intentionally covers those surfaces.
from hedron import Hedron, ThemeBuilder, validate_theme_spec
spec = (
ThemeBuilder("northstar")
.tokens(
{
"color.bg": "#ffffff",
"color.fg": "#111827",
"color.muted": "#4b5563",
"color.border": "#d1d5db",
"color.accent": "#2563eb",
"color.focus": "#1d4ed8",
"color.danger": "#b91c1c",
"font.family": "system-ui",
"font.size": "1rem",
"space.unit": "0.25rem",
"motion.duration": "160ms",
"focus.ring": "3px solid #1d4ed8",
}
)
.mode("dark", **{"color.bg": "#111827", "color.fg": "#f9fafb"})
.accessibility_mode("forced-colors", {"color.focus": "Highlight"})
.profile("core")
.build()
)
report = validate_theme_spec(spec, profile="core")
if not report.ok:
raise RuntimeError(report.to_dict())
app = Hedron(title="Northstar", theme=spec.to_theme(), session_secret="replace-in-production")
Color accepts safe absolute CSS colors such as Color.oklch(0.68, 0.18, 275) and records a
deterministic sRGB fallback. It does not accept variables, gradients, URLs, relative colors, or
arbitrary CSS. ThemeSpec is immutable and serializable, so fingerprints, reports, and generated
assets can be reproduced in CI.
For a reviewed variation, apply an explicit, bounded patch. The base fingerprint prevents a patch from silently landing on the wrong theme revision:
from hedron import Color, ThemePatch
review_patch = ThemePatch(
name="review-accent",
base=spec.name,
base_fingerprint=spec.fingerprint,
tokens={"color.accent": Color.oklch(0.62, 0.16, 285).to_hex()},
)
review_spec = spec.apply_patches(review_patch)
To distribute a theme, use the data-only package format. Packages contain no executable hooks, remote assets, or runtime CSS injection:
from hedron import conformance_report, load_theme_package, package_theme
package = package_theme(spec, profile="core", licenses=("MIT",))
loaded = load_theme_package(package)
assert conformance_report(loaded, profile="core")["ok"]
The equivalent file-oriented workflow is:
hedron style init --name northstar --output themes/northstar.json
hedron style conform --spec themes/northstar.json --profile core
hedron style package --spec themes/northstar.json --profile core \
--license MIT --output dist/northstar.hdt
8. Apply semantic style recipes¶
Recipes package a meaningful combination of presentation props. They are family-scoped, immutable, and conservative: an explicit prop on the component always wins.
from hedron import Button, DesignSystem, StyleRecipe, Surface
primary = StyleRecipe.control(
"team_primary",
appearance="solid",
emphasis="primary",
size="md",
)
panel = StyleRecipe.surface(
"team_panel",
appearance="raised",
padding="md",
elevation="sm",
)
design = DesignSystem.brand(
"team",
accent="#087f75",
recipes=(primary, panel),
)
create = design.apply("team_primary", Button("Create pipeline"))
explicit_outline = design.apply(
"team_primary",
Button("Create pipeline", appearance="outline"), # explicit wins
)
panel_view = design.apply("team_panel", Surface("Recent runs"))
The built-in catalog includes recipes such as primary_action, secondary_action,
destructive_action, form_surface, dashboard_panel, dense_data, inline_status,
and metadata. Custom recipes stay within one family: control, surface, data,
status, or content.
9. Scope a theme, mode, or density to a subtree¶
StyleScope is deliberately small. It marks a subtree with a registered theme, a color
mode, and/or a density. It does not create hidden descendant recipe defaults.
default · lightComfortable density for a primary workspace.
Readyaurora · dark · compactCompact dark context for an embedded surface.
Previewfrom hedron import StyleScope, Text
StyleScope(
Text("Embedded preview", role="title"),
Text("Uses the aurora dark palette at compact density."),
theme="aurora",
color_mode="dark",
density="compact",
)
Use the smallest meaningful boundary. A page-level theme belongs on Hedron; a preview,
embedded report, or mounted surface may justify a StyleScope.
10. Use the 0.60 product-surface contracts¶
0.60 finishes four commonly hand-styled surfaces. They expose finite presentation props and framework-owned fallback behavior, so they remain legible in print, forced colors, reduced motion, narrow layouts, and no-JavaScript rendering.
from hedron import (
Brand,
ConnectorFlow,
ConnectorNode,
ConnectorTrack,
ScrollRegion,
ToastHost,
)
brand = Brand(
"Northstar",
href="/",
subtitle="Operations workspace",
subtitle_overflow="break",
)
toast_host = ToastHost(
placement="bottom-end",
position="fixed",
width="field",
max_width="md",
gap="sm",
)
flow = ConnectorFlow(
ConnectorNode("Source", kind="source", state="ready"),
ConnectorTrack(label="Transfer"),
ConnectorNode("Warehouse", kind="target", state="running"),
appearance="soft",
background="dots",
overflow="auto",
min_size="md",
)
events = ScrollRegion(
"Recent events",
axis="block",
size="md",
affordance="always",
label="Recent events",
)
Keep Brand and ToastHost in the document shell so fragment swaps do not remove product
identity or the reserved #hedron-toast target. Keep ConnectorFlow children in reading order;
its visual direction and collapse behavior must never be the only way to understand the workflow.
Use ScrollRegion to bound overflow without replacing the semantics of a list, table, timeline,
or log. See the focused component pages for the complete finite prop sets:
Brand, ToastHost,
ConnectorFlow, and ScrollRegion.
Server-first theme preferences¶
ThemePreference is an allowlisted value, not a user-provided CSS string. Resolve it at the
server boundary, render ThemePicker as a native form, and let the application own persistence,
authorization, and the POST route. The optional boot helper only reads bounded local values to
reduce a preference flash; it does not replace server-rendered state.
from hedron import ThemePicker, ThemePreference, resolve_theme_preference
preference = resolve_theme_preference(
request.cookies.get("hedron-theme"),
request.cookies.get("hedron-color-mode"),
allowed_themes=("default", "aurora"),
)
picker = ThemePicker(
themes=("default", "aurora"),
selected=preference,
action="/preferences/theme",
csrf_token=csrf_token,
)
For custom hosts, pass the resolved preference through page asset injection so the framework emits theme and color-mode markers before page content. Invalid or stale values fall back to the allowlisted default; never construct a selector, CSS declaration, or persistence key from raw request input.
11. Add CSS only where the component owns the detail¶
For a custom component, colocate styles.css beside the component and use the documented
style-symbol binding. Hedron rewrites local classes and keyframes to collision-free
identifiers; :global(...) is explicit when you intentionally target the host.
The visual detail belongs to this callout component, so its rail and tint live with the component.
from collections.abc import Mapping
from hedron_core import NodeLike, StyleSymbols, html, styles_from_manifest
def render_callout(symbols: Mapping[str, str]) -> NodeLike:
styles: StyleSymbols = styles_from_manifest(symbols, component_id="app:Callout")
return html.div("Build completed", class_=styles.root)
Maintained complete component reference: Full code on GitHub
For a complete plugin component, register the stylesheet through the plugin manifest; see plugin authoring and themes and scoped styles. Do not copy the docs gallery CSS into the application.
Density is a first-class mode¶
Density changes rhythm, not meaning. Use it for a whole workspace or a contained data surface, and keep labels and controls understandable at every setting.
A production styling checklist¶
Use this sequence when a screen is ready for review:
- Give the page a semantic structure: landmarks, heading levels, labels, and DOM order.
- Use shared components and presentation props before adding a class.
- Replace repeated prop combinations with a named
StyleRecipe. - Set the brand through
DesignSystem.brand()or a registeredTheme. - Test both light and dark modes, at least one compact/spacious context, and narrow widths.
- Preserve focus visibility, text contrast, readable overflow, and reduced-motion behavior.
- Run
hedron theme checkand, for a zero-application-CSS surface,hedron style check --zero-app-css PATH. - Render one full page and one HTMX fragment so swapped regions retain the same styling contract.
What styling does not do
Styling does not authorize an action, validate input, protect CSRF, or make an inaccessible DOM accessible. Keep security and interaction boundaries in the server-side component and action APIs. See accessibility, security, and testing.
Reference map¶
- Presentation API — shared vocabulary and progressive styling APIs
- Modern CSS in 1.0 — current production boundary and checklist
- Themes and scoped styles — themes, CSS layers, and
styles.css - StyleScope — subtree theme/mode/density boundaries
- Component demos — visual pages for every built-in
- CLI reference —
theme check,style explain,style preview, andstyle diff