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-componentsmu-plugin) - Vanilla JS — no jQuery dependency
- Accessible by default — semantic HTML, ARIA attributes, keyboard navigation, skip-to-content link and
:focus-visiblestyles - 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 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 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
| 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. Callers can add classes without losing the component's base classes.$propspass-through — when ACF flexible content renders a section, the field values pass through as$propsautomatically. 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 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
| 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 from custom font files in 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 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.
Nav menus
Seven 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 |
| Legal Links | legal | Footer legal/policy links |
Accessibility
The wha2025 theme ships accessible patterns by default. Keep them intact when building or changing components.
| Area | What the theme does |
|---|---|
| Skip link | A "Skip to content" link is the first focusable element (header.php) so keyboard users can bypass the nav |
| Landmarks & semantics | Semantic HTML5 (header, nav, main, footer, section, article) for screen-reader navigation |
| Screen-reader text | The .sr-only utility adds visually-hidden labels where icons or layout would otherwise omit them |
| ARIA | aria-* attributes across interactive components (menus, tabs, accordions, dialogs) for state and relationships |
| Keyboard navigation | Focus management throughout. The mobile nav traps focus while open (navigation.js); tab and grid components support arrow-key roving focus (app.js) |
| Reduced motion | Animations respect prefers-reduced-motion: reduce (e.g. sheet.css, btn.css) |
| Language | language_attributes() sets <html lang> so screen readers use the right voice |
| Icons | Decorative 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
| Check | How |
|---|---|
| Keyboard only | Tab through a page. Every interactive element reachable, visible focus, logical order, skip link works |
| Screen reader | VoiceOver (macOS) or NVDA (Windows). Headings, landmarks and labels read sensibly |
| Contrast | Browser devtools or a contrast checker on text and UI |
| Automated | Lighthouse or axe DevTools for a baseline scan. Catches ~30–40% of issues; manual testing covers the rest |
| Reduced motion | Enable "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.