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-visiblestyles - Automatic heading semantics — the
yax-commonheading 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 intoassets/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
| Directory | Purpose | Examples |
|---|---|---|
sections/ | Full page sections — these are the ACF flexible content layouts | hero, 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 templates | card, btn, accordion, tabs, stars, tooltip, pagination, navbar |
content/ | Content display wrappers | article, 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:joinsuffix means this prop's value gets concatenated with (not replaced by) anyclassvalue passed in. This lets callers add classes without losing the component's base classes.$propspass-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
- Create a PHP file in
components/sections/(e.g.,my-section.php) - Start with
set_props()to define the component's inputs - Add a matching ACF flexible content layout named
my-sectionto the "Components" field group - 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
| Token | Color | Hex | Usage |
|---|---|---|---|
primary | #e21d5e | Primary brand color, CTAs | |
secondary | #942e5a | Headings, accents | |
tertiary | #daf0f4 | Backgrounds, highlights | |
accent | #DCD22C | Alerts, emphasis | |
dark | #282f3e | Body text, footer | |
light | #ffffff | White |
Breakpoints
| Token | Width | Notes |
|---|---|---|
xs | 480px | Small phones |
sm | 600px | Large phones |
md | 782px | Tablets (matches WP admin bar breakpoint) |
lg | Content size from theme.json | Desktop |
xl | Wide size from theme.json | Wide 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.js→assets/dist/js/app.js - CSS:
assets/css/app.css→assets/dist/css/app.css(via PostCSS + Tailwind) - Editor CSS:
assets/css/editor-style.css→assets/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.
Nav Menus
Six registered menu locations:
| Location | Slug | Usage |
|---|---|---|
| Primary Menu | primary | Main navigation |
| Meta Menu | meta | Top bar (login, phone, etc.) |
| Footer Callouts | footer | Footer CTA sections |
| For Patients | patients | Patient resources submenu |
| About WHA | about | About submenu |
| For Providers | providers | Provider resources submenu |