feat(components): shared Heading Twig component for H1-H6#98
Open
martinydeAI wants to merge 1 commit into
Open
feat(components): shared Heading Twig component for H1-H6#98martinydeAI wants to merge 1 commit into
martinydeAI wants to merge 1 commit into
Conversation
Adds `templates/components/Heading.html.twig` accepting a `level` prop (1-6), an optional `size` token (xl / lg / md / sm; defaults chosen from `level`), and a `class` slot for layout extras like margins. Centralises the `font-display text-[clamp(...)] ...` token salad that several templates were hand-rolling. Refactors the call sites that duplicated the markup: - templates/assistant/show.html.twig (hero h1) - templates/security/login.html.twig (compact-size h1) - templates/components/PageHeader.html.twig (hero h1) - templates/components/Hero.html.twig (hero h1) - templates/components/EmptyState.html.twig (small h2) - templates/components/Filter/Rail.html.twig (section h2) The two card-internal h3s in `CardRail/Card` and `Catalog/AssistantCard` keep their own markup because they ride a parent `group` for `group-hover:text-primary` - that coupling is a card-component concern, not a heading concern. Refs #92. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
yepzdk
reviewed
Jun 22, 2026
| md: 'font-display text-2xl font-medium tracking-tight text-ink', | ||
| sm: 'font-display text-xl font-semibold text-ink', | ||
| } %} | ||
| {% set resolvedSize = size ?? (level == 1 ? 'xl' : (level == 2 ? 'md' : 'sm')) %} |
Contributor
There was a problem hiding this comment.
level and size are fully decoupled — nothing stops <twig:Heading level="4" size="xl"> rendering an <h4> bigger than an <h1> elsewhere on the page. Explicit size is already live: login uses size="lg" and EmptyState size="sm".
Should we cap size to a per-level ceiling — a heading can shrink, never out-size a higher level? Something like:
| level | default | allowed sizes |
|---|---|---|
| 1 | xl |
xl, lg |
| 2 | md |
lg, md, sm |
| 3–6 | sm |
sm |
That keeps both current overrides (1→lg, 2→sm) valid while ruling out the inversion. Note lg is never auto-reachable today — only via explicit size — so a level→size table would also give it a natural home. Non-blocking; fine as a follow-up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Links to issues
Closes #92.
Description
Several templates were hand-rolling the same
<h1>/<h2>markupwith
font-display text-[clamp(...)] font-medium leading-tight tracking-tight text-inksalad. Extract a sharedHeadingTwigcomponent that picks the tag from a
levelprop (1–6) and thevisual treatment from a
sizetoken, with sensible defaults perlevel.
templates/components/Heading.html.twig—level(1–6),size(xl|lg|md|sm, auto fromlevel),classslot for extras (margins, layout hooks).
templates/assistant/show.html.twig(hero h1)templates/security/login.html.twig(compact-size h1)templates/components/PageHeader.html.twig(hero h1)templates/components/Hero.html.twig(hero h1)templates/components/EmptyState.html.twig(small h2)templates/components/Filter/Rail.html.twig(section h2)The two card-internal h3s in
CardRail/CardandCatalog/AssistantCardkeep their own markup because they ride aparent
groupelement forgroup-hover:text-primary— thatcoupling is a card-component concern, not a heading concern.
Pulling it into
Headingwould force the prop API to absorbhover-coupling state.
Screenshot of the result
n/a — every refactored call site preserves the exact class string
that was there before (verified by diff). Visual output is
unchanged.
Checklist
Verified locally:
task coding-standards-twig-check— green (after acoding-standards-twig-applypass to drop quotes on the hashkeys, per project preference).
task coding-standards-php-check— green (no PHP changed).task coding-standards-markdown-check— green.task test-coverage— could not run locally due to a DockerDesktop / MariaDB OOM situation on the host. The change is
template-only and no test asserts on the exact class string, so
CI should be green; happy to re-run once the host is healthy.
Additional comments or questions
The
sizetoken names (xl/lg/md/sm) intentionallymatch Tailwind's mental model rather than naming them after their
call-site role (e.g.
hero/auth/section/subsection).That keeps the component reusable without inventing a new
vocabulary; the level→size default mapping captures the common case.
Details - AI specificities
templates/components/and is invokedvia
<twig:Heading ...>(Twig Components bundle), matching theexisting
Eyebrow,Hero,PageHeader, etc. conventions.have a single source of truth.
develop. Theunmerged user-management PRs (feat(user): add name and status fields per ADR 006 (#45, #83) #86–feat(registration): anonymous self-signup with email-domain allow-list (#62) #90, chore(tests): suppress NotFoundHttpException error log from test output #96) introduce
profile/*andregistration/*templates that also duplicatethe heading markup; those call sites can adopt the component
once their PRs rebase onto a develop that carries this change.