WHA Docs

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 flexible content via yax-components
  • Vanilla JS — no jQuery dependency
  • Accessible by default — semantic HTML, ARIA attributes, keyboard navigation, skip-to-content link, :focus-visible styles
  • Automatic heading semantics — the yax-common heading manager tracks heading levels across components so the document outline stays correct regardless of how editors arrange sections. Components don't hardcode <h2> or <h3> — the heading level is determined by context.
  • Lazy loading — images use loading="lazy" to defer offscreen images. Mapbox and Supercluster scripts are only loaded on pages that actually 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 are built. The yax-components mu-plugin provides a PHP component rendering system that works with ACF flexible content to let 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.

This means 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. This lets callers add classes without losing the component's base classes.
  • $props pass-through — when ACF flexible content renders a section, the field values are automatically passed as $props. 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 yax-components via a hook in functions.php:

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

This tells yax-components where to find component files when get_component() or get_components() is called. See 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 as custom font files from 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 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 are conditionally loaded only on pages with maps. Provider listing and topics load data via AJAX with nonce verification.

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

Six 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

On this page