WHA Docs
Development

Theme System

The wha2025 theme — Tailwind CSS, Laravel Mix, PHP component system, ACF flexible content and template hierarchy.

Overview

The active theme is wha2025, located at src/wp-content/themes/wha2025/. It's a custom theme built on:

  • Tailwind CSS v3 with PostCSS (import, tailwindcss/nesting, autoprefixer)
  • Laravel Mix (Webpack) for asset compilation
  • PHP component system driven by ACF (Advanced Custom Fields) flexible content via the component system (the yax-components mu-plugin)
  • Vanilla JS — no jQuery dependency
  • Accessible by default — semantic HTML, ARIA attributes, keyboard navigation, skip-to-content link and :focus-visible styles
  • Automatic heading semantics — the shared utilities' heading manager tracks heading levels across components so the document outline stays correct no matter how editors arrange sections. Components don't hardcode <h2> or <h3>; the level is set by context.
  • Lazy loading — images use loading="lazy" to defer offscreen images. Mapbox and Supercluster scripts only load on pages that render maps.

The legacy theme wha (Grunt-based) still exists in the repo but should never be modified.

Theme organization

  • assets/ — Source CSS, JS, images, icons and fonts. Compiles into assets/dist/ via Laravel Mix. Compiled output is tracked in git.
  • components/ — PHP components in three categories (see Component system below)
  • functions/ — PHP includes: ACF config, query helpers, data handlers (data/providers.php, etc.) and AJAX endpoints (api/providers.php, etc.)
  • Template files — WordPress template hierarchy with custom templates per post type (single-wha_providers.php, etc.)

Component system

This is the core of how pages get built. It's a PHP rendering system that works with ACF flexible content, letting content editors assemble pages from pre-built sections.

How pages are built

A page template is minimal. Most are just three lines:

<?php
get_header();
get_components();
get_footer();

get_components() iterates through the ACF flexible content field ("Components") on the current post. Each layout the editor added maps to a PHP file in components/sections/. The layout name hero loads components/sections/hero.php, cards loads components/sections/cards.php, and so on.

So page structure is controlled by the content editor in wp-admin, not hardcoded in template files.

Component types

DirectoryPurposeExamples
sections/Full page sections — these are the ACF flexible content layoutshero, copy, copy-with-media, cards, find-a-provider, find-a-location, insurance, services-carousel, recent-posts, provider-ratings
ui/Reusable UI elements — called by sections or templatescard, btn, accordion, tabs, stars, tooltip, pagination, navbar
content/Content display wrappersarticle, article.single

The props pattern

Every component starts with set_props(), which defines its expected inputs with defaults:

<?php
$props = set_props([
    'class:join' => 'card',
    'title'      => '',
    'excerpt'    => '',
    'image'      => '',
    'href'       => '',
    'cta'        => 'Learn more',
    'cta_theme'  => 'primary',
], $props);
?>

Key features of the props system:

  • Defaults — every prop has a default value, so components render safely even with missing data
  • class:join — the special :join suffix means this prop's value gets concatenated with (not replaced by) any class value passed in. Callers can add classes without losing the component's base classes.
  • $props pass-through — when ACF flexible content renders a section, the field values pass through as $props automatically. No manual mapping needed.

Rendering components manually

Sections render automatically via get_components(), but UI components are called explicitly:

<?php get_component('ui/card', [
    'title' => get_the_title(),
    'image' => get_the_post_thumbnail_url(),
    'href'  => get_the_permalink(),
]); ?>

Components can nest. A sections/cards component loops through posts and renders ui/card for each one.

Adding a new section

  1. Create a PHP file in components/sections/ (e.g., my-section.php)
  2. Start with set_props() to define the component's inputs
  3. Add a matching ACF flexible content layout named my-section to the "Components" field group
  4. The new section immediately appears as an option for content editors in wp-admin

Available sections

The theme ships with 26 section components:

cards, copy, copy-with-media, find-a-location, find-a-provider, footer, hero, instagram-feed, insurance, life-stages-overview, myhealth-login, posts, provider-ratings, recent-posts, related-providers, related-services, resources, screenings-prevention, search-results, section-not-found, services, services-carousel, services-overview, tabbed-content, topics, topics-terminology

Theme registration

The theme registers its component directory with the component system via a hook in functions.php:

add_action('yax/components/register', function () {
    // registers components/sections/ and components/ui/
});

This tells the component system where to find component files when get_component() or get_components() is called. See the Developer Reference for the full function API.

Tailwind configuration

Custom design tokens defined in tailwind.config.js:

Colors

TokenColorHexUsage
primary#e21d5ePrimary brand color, CTAs
secondary#942e5aHeadings, accents
tertiary#daf0f4Backgrounds, highlights
accent#DCD22CAlerts, emphasis
dark#282f3eBody text, footer
light#ffffffWhite

Breakpoints

TokenWidthNotes
xs480pxSmall phones
sm600pxLarge phones
md782pxTablets (matches WP admin bar breakpoint)
lgContent size from theme.jsonDesktop
xlWide size from theme.jsonWide desktop

Font

The site uses Gentona as its primary font family (font-base), loaded from custom font files in assets/fonts/.

Laravel Mix

webpack.mix.js configures the build pipeline:

  • JS: assets/js/app.jsassets/dist/js/app.js
  • CSS: assets/css/app.cssassets/dist/css/app.css (via PostCSS + Tailwind)
  • Editor CSS: assets/css/editor-style.cssassets/dist/css/editor-style.css
  • Static copies: img/, icons/, fonts/assets/dist/
  • Production: Adds content-hash versioning via mix-manifest.json

The build-icons.js script copies SVG icons from the lucide-static and simple-icons npm packages into assets/dist/icons/ during the build.

JavaScript

All JS is vanilla (no jQuery). Mapbox GL JS and Supercluster only load on pages with maps. Provider listing and topics load data via AJAX with nonce verification.

See the Developer Reference for the icon system, asset versioning and query helpers.

Seven registered menu locations:

LocationSlugUsage
Primary MenuprimaryMain navigation
Meta MenumetaTop bar (login, phone, etc.)
Footer CalloutsfooterFooter CTA sections
For PatientspatientsPatient resources submenu
About WHAaboutAbout submenu
For ProvidersprovidersProvider resources submenu
Legal LinkslegalFooter legal/policy links

Accessibility

The wha2025 theme ships accessible patterns by default. Keep them intact when building or changing components.

AreaWhat the theme does
Skip linkA "Skip to content" link is the first focusable element (header.php) so keyboard users can bypass the nav
Landmarks & semanticsSemantic HTML5 (header, nav, main, footer, section, article) for screen-reader navigation
Screen-reader textThe .sr-only utility adds visually-hidden labels where icons or layout would otherwise omit them
ARIAaria-* attributes across interactive components (menus, tabs, accordions, dialogs) for state and relationships
Keyboard navigationFocus management throughout. The mobile nav traps focus while open (navigation.js); tab and grid components support arrow-key roving focus (app.js)
Reduced motionAnimations respect prefers-reduced-motion: reduce (e.g. sheet.css, btn.css)
Languagelanguage_attributes() sets <html lang> so screen readers use the right voice
IconsDecorative SVGs are marked aria-hidden by get_icon(); meaningful icons get an .sr-only label

An AccessiBe widget adds visitor-controlled adjustments (contrast, text size, motion) on top, but it doesn't replace accessible markup. The theme is accessible on its own.

When building or changing components, keep these intact: a visible focus state, a label (visible or .sr-only) on every control, correct ARIA for any custom widget, and prefers-reduced-motion handling on new animations. New images need real alt text. Editorial guidance lives in Accessibility.

Testing

CheckHow
Keyboard onlyTab through a page. Every interactive element reachable, visible focus, logical order, skip link works
Screen readerVoiceOver (macOS) or NVDA (Windows). Headings, landmarks and labels read sensibly
ContrastBrowser devtools or a contrast checker on text and UI
AutomatedLighthouse or axe DevTools for a baseline scan. Catches ~30–40% of issues; manual testing covers the rest
Reduced motionEnable "reduce motion" in OS settings. Animations should calm or stop

Automated tools catch a fraction of real issues. Keyboard and screen-reader testing of the key flows (find a provider, view a location, schedule an appointment) is where the meaningful problems surface.

On this page