Footer
The one site footer: the Level Up grid, the newsletter CTA, and the dark brand bar, composed into a single self-contained block.
When to Use
includes/footer.php get it automatically; redesigned pages call ws_footer() directly. A render-once guard means either route — or both — produces exactly one footer.
ws_is_app_page() matches (DESIGN.md §12.7). Never build a page-local footer, and never call Level Up Grid on a page that already ends in this composite — you'd get the grid twice.
Variants
One footer, identical everywhere, is the point — so the options array trims regions rather than restyling them. hide_level_up drops the grid, hide_phases hides named phases within it, and pages on the legacy includes/footer.php pass the same array through $ws_footer_options. Reach for these only where a page already says the same thing in its own body. The three regions it composes are:
| Region | Contents |
|---|---|
| Level Up grid | The full app catalogue grouped by phase, via Level Up Grid |
| Letter CTA | Newsletter signup — title, subtitle, email field, and submit |
| Brand bar | Logo, tagline, link columns, social links, and the legal line on the dark surface |
<?= ws_footer() ?> <?= ws_footer(['hide_level_up' => true]) ?> // legacy include, set before requiring includes/footer.php $ws_footer_options = ['hide_level_up' => true];
States
| State | Behavior |
|---|---|
| First render | Defines WS_GLOBAL_FOOTER_RENDERED and emits the full composite, including its styles, tokens, fonts, and the toast/auth functional core. |
| Subsequent renders | The guard returns early, so nothing is emitted. This is what makes double inclusion safe. |
| Newsletter idle | Empty field, submit ready. |
| Newsletter submitted | The response lands in .cta__msg, which is an aria-live="polite" region — so success and failure are both announced without stealing focus. |
| Honeypot | .cta__hp is a hidden field for spam trapping. It's visually hidden and must stay empty — don't repurpose or label it. |
Real-World Usage
The standard page close. Because the component is self-contained, a page needs nothing else at the bottom — no extra stylesheet, no toast container, no auth script.
<?php
require_once 'config.php';
$header_section = 'library';
require_once 'includes/header.php';
?>
<main>
<!-- …page content… -->
</main>
<?= ws_footer() ?>
</body></html>The same call inside an app renders the App Footer rail instead — there's no "hidden" or "compact" mode to reach for, and nothing to change per page.
Options
| Option | Type | Default | Purpose |
|---|---|---|---|
$options | array | [] | Reserved for future variants. Nothing is read from it today — passing values has no effect. |
The link columns, social links, and CTA copy are defined in the template rather than passed in. Change them there so every page moves together.
Accessibility
| Concern | Behavior |
|---|---|
| Landmark | A real <footer> element, so it's exposed as the contentinfo landmark and skippable. |
| Newsletter feedback | .cta__msg is aria-live="polite", so the submit result is announced without interrupting or moving focus. This is the right pattern. |
| Social links | Icon-only, each with an explicit aria-label naming the network — "LinkedIn", "Bluesky". Decorative glyphs inside carry aria-hidden="true". |
| Hidden text | Uses an .sr-only class for visually-hidden labels, so context reaches screen readers without appearing on screen. |
| Honeypot | Hidden from sighted users. Confirm it's also out of the tab order and unlabelled so assistive-tech users aren't prompted to fill a trap field. |
| Contrast | The legal line is white at 0.55 alpha over the ink surface, measured at roughly 5.8:1 — above the 4.5:1 AA threshold for body text. Don't reduce the alpha further. |
| Link volume | The Level Up grid contributes a dozen links at the end of every page. Keeping the footer consistent in position and structure is what makes it easy to skip past. |
Tokens
The footer bundles its own styles inline and emits the platform token sheet itself, so it renders correctly even on a page that loads nothing else. It uses the ink surface for the brand bar, the shared type and spacing scales throughout, and the brand red for the CTA — the Level Up grid inside it resolves the phase tokens, all of which are now the same red.
See Level Up Grid for the phase token detail, and Token Reference for current values.
CSS Classes
The footer's own classes are unprefixed (.footer-*, .cta__*) because the styles ship inline with the component rather than in the shared sheet. The grid inside it keeps the .ws-level-up prefix.
| Class | Purpose |
|---|---|
.footer | The <footer> landmark |
.footer-container | Max-width wrapper |
.footer-brand / .footer-logo / .footer-tagline | Brand block |
.footer-col / .footer-links | Link columns |
.footer-social | Social link row |
.footer-bottom | Legal line |
.cta / .cta__title / .cta__sub / .cta__rule | Newsletter block and its heading |
.cta__form / .cta__row / .cta__input | Signup form |
.cta__msg | Live region for the submit result |
.cta__note | Small print under the form |
.cta__hp | Honeypot field — leave alone |
.sr-only | Visually-hidden text |
.ws-level-up* | The embedded app grid — see its own page |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_footer() helper function |
includes/components/footer.php | The composite template, the render-once guard, and the bundled styles |
includes/components/level-up-grid.php | The app grid rendered inside it |
includes/footer.php | Legacy page footer — now just calls ws_footer() |
Live render
The real component follows, full-bleed. Everything below this line is the footer itself.
Level Up Your Facilitation
Learn.
Before you run the room, you read it. Steal from facilitators who've made every mistake, study the moves that worked, and stockpile exercises you can pull when the agenda goes sideways. Your reading list now is your toolkit later.
Plan.
A workshop is a sequence of decisions you make before anyone walks in: who's there, what changes by the end, where the energy spikes and dips. Block out the time, name the moves, leave room for the room. Plan tight enough to start, loose enough to follow what actually happens.
Facilitate.
The plan meets the room and the room wins. Your job is to read what's actually happening, not what you scripted, and steer with small, specific moves. Hold the timer. Surface the unsaid. Cut what's not landing.
Reflect.
The hour after the workshop is when the value either compounds or evaporates. Capture what surfaced, send the artifacts before momentum dies, and write down the one thing you'd do differently. Run enough sessions and the patterns become a craft.
A letter for facilitators who give a damn.
Workshop tips picked for the rooms you actually run. Three times a week. No "10 tricks for hybrid" listicles, no synergy slides, no hot takes dressed as frameworks.