Canvas Design System
Main Site Tokens

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

Use when: Ending any content or marketing page. Pages using the legacy 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.
Don't use when: You're inside a sub-application. Those pages close on the App Footer rail instead, and they get there on their own — the composite delegates whenever 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:

RegionContents
Level Up gridThe full app catalogue grouped by phase, via Level Up Grid
Letter CTANewsletter signup — title, subtitle, email field, and submit
Brand barLogo, 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

StateBehavior
First renderDefines WS_GLOBAL_FOOTER_RENDERED and emits the full composite, including its styles, tokens, fonts, and the toast/auth functional core.
Subsequent rendersThe guard returns early, so nothing is emitted. This is what makes double inclusion safe.
Newsletter idleEmpty field, submit ready.
Newsletter submittedThe 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

OptionTypeDefaultPurpose
$optionsarray[]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

ConcernBehavior
LandmarkA 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 linksIcon-only, each with an explicit aria-label naming the network — "LinkedIn", "Bluesky". Decorative glyphs inside carry aria-hidden="true".
Hidden textUses an .sr-only class for visually-hidden labels, so context reaches screen readers without appearing on screen.
HoneypotHidden 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.
ContrastThe 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 volumeThe 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.

ClassPurpose
.footerThe <footer> landmark
.footer-containerMax-width wrapper
.footer-brand / .footer-logo / .footer-taglineBrand block
.footer-col / .footer-linksLink columns
.footer-socialSocial link row
.footer-bottomLegal line
.cta / .cta__title / .cta__sub / .cta__ruleNewsletter block and its heading
.cta__form / .cta__row / .cta__inputSignup form
.cta__msgLive region for the submit result
.cta__noteSmall print under the form
.cta__hpHoneypot field — leave alone
.sr-onlyVisually-hidden text
.ws-level-up*The embedded app grid — see its own page

Files

FilePurpose
includes/components/helpers.phpws_footer() helper function
includes/components/footer.phpThe composite template, the render-once guard, and the bundled styles
includes/components/level-up-grid.phpThe app grid rendered inside it
includes/footer.phpLegacy page footer — now just calls ws_footer()

Live render

The real component follows, full-bleed. Everything below this line is the footer itself.

appsMore from Workshopr.io

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.

Three emails a week. No spam, unsubscribe in one click.