ORA Capital — Design System

🍱

Get Started

This design system is the shared language for the site. Every component, layout, colour, and rule lives here first. There are four schools of thought on how to use the system.


1. A rule-setting environment

This is where conventions are decided and written down — naming patterns, escaping rules, token scales, spacing units.


2. A pattern library

Before building something new, check here first. Established patterns — buttons, containers, layouts, typography — are documented with working markup so you can copy the example rather than re-solving a problem that's already been solved. If a pattern exists here, use it; don't roll your own variant in a one-off template.


3. A scratchpad for new components

The design system is also where new components get built and tested in isolation, away from live content and real templates. A section here is allowed to be unfinished while it's actively being worked on — but leave yourself a plain note at the top of the section saying so (e.g. "WIP — not finalised, layout still being tested") so nobody mistakes a half-built idea for an established pattern.

Remove the note once the component is settled and ready to be treated as a reliable pattern.


4. A stable fixture for visual regression testing

BackstopJS (and any other visual regression tooling) treats this page as a fixed reference — it screenshots what's here and diffs future builds against it. That only works if the content is static and predictable, which is why every section is built with hardcoded dummy data rather than live or dynamic content.

Two consequences follow from this:

Never wire a design system section to get_field(), get_posts(), or any other live query — that's what makes a screenshot comparison reliable in the first place.

Be aware: reordering sections, renaming a section's name slug, or changing markup that a screenshot baseline depends on will register as a visual diff, even if nothing is actually broken.


Bootstrap utility classes

Bootstrap's utility classes (.mx-auto, .d-flex, .text-center, and so on) are available and fine to reach for directly in markup for one-off layout tweaks — there's no need to write a bespoke class for something Bootstrap already solves in one place.

Never select a utility class from inside a component's own .scss file. A component should only ever style its own BEM classes. Utility classes belong in markup, not as selectors in component CSS — see copilot-instructions.md → "Don't target utility or typography classes as selectors in component CSS".

Breakpoint utilities specifically

Bootstrap's responsive suffixes (-sm, -md, -lg, -xl) are not a problem to use in markup, but they get hard to read once a chain of them builds up on one element — p-7 p-lg-9 px-lg-32 doesn't tell you much at a glance about what's actually changing or why.

Inside component or layout SCSS, prefer the hand-coded @include bp() mixin with the theme's named breakpoint variables instead of reaching for a Bootstrap breakpoint suffix:

@include bp($tablet) { padding: var(--spacer-9); }

The named variables ($tablet, $desktop, etc., defined in sass/variables-settings/_layout-settings.scss) read closer to plain English than lg/xl, and keep responsive logic in one legible place rather than spread across a string of utility classes.

Testing

The theme ships with two layers of automated testing: PHPUnit for PHP logic, and BackstopJS for visual regression. Neither requires a CI server to be useful — both run locally before you push.


Unit tests (PHPUnit)

Covers core theme functions that other code relies on — is_arg(), get_arg(), add_classes(), h_tag(), and breadcrumb helpers. Run the full suite with:

vendor/bin/phpunit

Tests live in /tests/Unit/. Requires composer install to have been run first, since PHPUnit and the WordPress stubs it type-checks against come from /vendor.

Add a test alongside the existing ones whenever you add a new template helper function — this is the layer that catches a logic regression before it ever reaches a browser.


Visual regression testing (BackstopJS)

BackstopJS screenshots a list of URLs at three viewports — phone, tablet, and desktop — and diffs new screenshots against an approved reference set. This design system page, and every other section on this page, is part of that default URL list — which is why design system sections must stay static rather than pulling from live content (see "Get Started" → A stable fixture for visual regression testing).

See the Visual Regression Testing section of the theme's README.md for first-time setup and the commands to run it.

What it won't catch

Real images are stubbed out with a static placeholder (backstop_data/engine_scripts/interceptImages.js) so screenshots stay deterministic between runs — lazy-loaded and randomly-selected media can't cause a false diff. That means a content or photo change won't register here. Layout, spacing, colour, and typography changes will.

Adding coverage

As real pages get built, add their paths to the items array in backstop-urls.json (not the local-only copy) so the whole team benefits from the same coverage. Design system sections are already listed via ?ds_page= query params — keep that in sync if a section's name slug ever changes.

Template Functions

Array argument validation is_arg() and get_arg()

Used to safely read values from $args arrays passed into template parts via get_template_part().

Principles

  • Absence is the default. A component receiving a minimal $args array should always render sensibly on its own. Only pass what differs from the default.
  • Presence is an explicit override. If a key exists with a meaningful value, the component acts on it.
  • Elvis sets the bridge. Use get_arg() ?: 'default' when a fallback value is needed. Never pass false or empty values to communicate "off" — just omit the key.
  • No boolean flags. Avoid passing true or false as option values. Use presence vs absence instead — is_arg() is your boolean check.

What counts as a value

Value passed in get_arg() returns Example
nullfalseget_arg( 'label', array( 'label' => null ) ) // false
falsefalseget_arg( 'label', array( 'label' => false ) ) // false
''falseget_arg( 'label', array( 'label' => '' ) ) // false
array()falseget_arg( 'items', array( 'items' => array() ) ) // false
Key missingfalseget_arg( 'label', array() ) // false
00get_arg( 'count', array( 'count' => 0 ) ) // 0
Any stringThe stringget_arg( 'label', array( 'label' => 'Submit' ) ) // 'Submit'
Any non-empty arrayThe arrayget_arg( 'items', array( 'items' => array( 'a', 'b' ) ) ) // array( 'a', 'b' )

Template Part - section-zoo.php

	
	/*------------------------------------*\
		# EXAMPLE ARRAY
	\*------------------------------------*/

	$animal_args = array(
		'animals'     => array( 'dog', 'deer', 'cat' ),
		'title'       => get_field( 'title' ),
		'sub_title'   => 'Animals',
	);

	get_template_part( 'template-parts/component', 'animals', $animals_args );
	

Template Part - component-animals.php

	
	/*------------------------------------*\
		# EXAMPLE OUTPUT
	\*------------------------------------*/

	$is_title     = is_arg( 'title', $args ); // This will return false because get_field('title') doesn't exist.
	$is_nothing   = is_arg( 'something', $args ); // This will return false.
	$is_animals   = is_arg( 'animals', $args ); // This will return truthy as an array.

	$sub_title    = get_arg( 'sub_title', $args ); // This will return 'Animals'.

	$animals      = get_arg( 'animals', $args );

	if ( $animals && is_array( $animals ) ) {
		foreach ( $animals as $animal ) {
			echo $animal;
		}
	}
	

What returns false

Both functions treat the following as "not set" — get_arg() returns false and is_arg() returns false for all of these:

	
	$args = array(
		'label'  => null,         // not set
		'label'  => false,        // not set — indistinguishable from missing
		'label'  => '',           // not set — no content
		'items'  => array(),      // not set — no content
	);
	

The integer 0 is the exception — it is a real value and passes through.

Set defaults with Elvis

Because missing and empty values all return false, the Elvis operator is all you need for defaults.

	
	$label       = get_arg( 'label', $args ) ?: 'Read more';
	$button_text = get_arg( 'button_text', $args ) ?: 'Submit';
	$title_tag   = get_arg( 'title_tag', $args ) ?: 'h2';
	

Use is_arg() for conditional blocks

Use is_arg() when you need to decide whether to render a block at all.

	
	if ( is_arg( 'animals', $args ) ) {
		$animals = get_arg( 'animals', $args );
		foreach ( $animals as $animal ) {
			echo esc_html( $animal );
		}
	}
	

Attribute output from associative array get_attributes()

This is used in component_button() to add more attributes that don't have specific arguments.

	
	$attributes = array(
		'data-url'  => 'http://lateralaspect.com.au',
		'data-id'   => 'latasp',
	);

	get_attributes( $attributes );

	// Returns: data-url="http://lateralaspect.com.au" data-id="latasp"
	

Heading hierarchy offset h_tag()

Returns the correct heading tag for a component based on the context it is placed in. Write headings as if the component stands alone — pass the base level from $args and every heading steps correctly.

Accepts both 'h2' and '2' as input for either argument. Clamps at h6.

Formula

	
	output = natural_level + ( base_level - 1 )

	h_tag( '2', 'h1' ) // h2 — no offset
	h_tag( '2', 'h2' ) // h3 — shifted by 1
	h_tag( '2', 'h4' ) // h5 — shifted by 3
	h_tag( '3', 'h5' ) // h6 — clamped at h6
	

Component usage

	
	/* = VIEW LOGIC
	----------------------------------------------- */

	$title_tag = get_arg( 'title_tag', $args ) ?: 'h2';

	$h1 = h_tag( '1', $title_tag ); // Primary heading
	$h2 = h_tag( '2', $title_tag ); // Secondary heading

	/* = MARKUP
	----------------------------------------------- */
	?>

		<div class="c-component">
			<<?= $h1; ?> class="c-component__title">
				<?= esc_html( $main_heading ); ?>
			</<?= $h1; ?>>

			<<?= $h2; ?> class="c-component__subtitle">
				<?= esc_html( $sub_heading ); ?>
			</<?= $h2; ?>>
		</div>
	

Passing the base level in

	
	// Caller sets the base heading level for the component.
	// Default to 'h2' if not provided.

	$component_args = array(
		'title_tag' => 'h2', // or 'h3', 'h4', etc.
		'heading'   => 'Main Title',
	);

	get_template_part( 'template-parts/component', 'example', $component_args );
	

Handing off to a nested component

	
	// Pass h_tag() result as the child's base level.

	$child_args = array(
		'title_tag' => h_tag( '2', $title_tag ), // Child starts one level down.
	);

	get_template_part( 'template-parts/component', 'child', $child_args );
	

Inline SVG output render_svg()

Outputs an SVG file inline from the theme's assets/img/ directory. Use this instead of <img> tags for icons so they can be coloured via currentColor and avoid an extra HTTP request. Pass the filename without the .svg extension.

	
	// Basic usage — outputs the SVG inline
	<?= render_svg( 'arrow' ); ?>

	// Inside component_icon() — preferred for icons used in components/buttons
	component_icon( 'arrow', 'icon--up icon--currentColor' );
	component_icon( 'arrow', 'icon--mirror icon--currentColor' );

	// Never hardcode the theme path — use get_stylesheet_directory_uri() if you must use <img>
	<img src="<?= esc_url( get_stylesheet_directory_uri() ); ?>/assets/img/logo.png" alt="Logo">
	

SVG file preparation

  • Convert strokes to fills in Illustrator: Object > Expand before exporting.
  • Compress with SVGOMG after export.
  • Always add width="" and height="" attributes matching the viewBox values — e.g. viewBox="0 0 22 22" width="22" height="22".
  • Prefer path elements with fill over line/stroke elements so currentColor inheritance works correctly.
	
	<!-- DO — fill-based path, currentColor-ready -->
	<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" width="22" height="22">
	  <path fill="#678884" d="M21,22c..."/>
	</svg>

	<!-- DON'T — stroke-based lines, won't inherit currentColor reliably -->
	<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.828 22.828">
	  <line stroke="#678884" stroke-width="2" .../>
	</svg>
	

Add classes to existing class list add_classes()

This function merges new classes with existing classes, removing duplicates and handling both string and array inputs.

	
	// Adding string classes to string classes
	$existing_classes = 'c-component c-component--primary';
	$new_classes      = 'c-component--large is-active';
	$result           = add_classes( $new_classes, $existing_classes );
	// Returns: 'c-component c-component--primary c-component--large is-active'

	// Adding array classes to string classes
	$existing_classes = 'c-component c-component--primary';
	$new_classes      = array( 'c-component--large', 'is-active' );
	$result           = add_classes( $new_classes, $existing_classes );
	// Returns: 'c-component c-component--primary c-component--large is-active'

	// Handles duplicates automatically
	$existing_classes = 'c-component c-component--primary';
	$new_classes      = 'c-component c-component--secondary';
	$result           = add_classes( $new_classes, $existing_classes );
	// Returns: 'c-component c-component--primary c-component--secondary' (duplicate 'c-component' removed)
	

Colors

Usage

  • Apply in HTML using a class name .color-white .bg-white
  • or in CSS with a CSS Variable color: var(--color-white);

Typography

Display & Heading Styles

Display and heading classes use Newsreader 400. Sizes are mobile / desktop (1240px+). Click Copy on any snippet.

.f-display-1 56 / 96 px · LH 1.1 · LS 0 · 400 Display 1 — The quick brown fox
<h2 class="f-display-1">Display 1</h2>
.f-display-2 48 / 64 px · LH 1.1 · LS 0 · 400 Display 2 — The quick brown fox
<h2 class="f-display-2">Display 2</h2>
.f-display-3 36 / 48 px · LH 1.1 · LS 0 · 400 Display 3 — The quick brown fox
<h2 class="f-display-3">Display 3</h2>
.f-display-4 40 / 64 px · LH 1.25 · LS 0 · 400 H1 — The quick brown fox
<h2 class="f-display-4">H1</h2>
.f-display-5 36 / 48 px · LH 1.25 · LS 0 · 400 H2 — The quick brown fox
<h2 class="f-display-5">H2</h2>
.f-display-6 32 / 36 px · LH 1.25 · LS 0 · 400 H3 — The quick brown fox
<h2 class="f-display-6">H3</h2>
.f-display-7 24 / 32 px · LH 1.25 · LS 0 · 400 H4 — The quick brown fox
<h2 class="f-display-7">H4</h2>
.f-display-8 20 / 24 px · LH 1.25 · LS 0 · 400 H5 — The quick brown fox
<h2 class="f-display-8">H5</h2>
.f-display-9 16 / 20 px · LH 1.25 · LS 0 · 400 H6 — The quick brown fox
<h2 class="f-display-9">H6</h2>

Sub Heading

Arial 700, uppercase, tracked — for eyebrows / label text above a display heading.

.f-subheading 14 px · LH 1.25 · LS 0.120em · 700 · uppercase Sub Heading label
<span class="f-subheading">Sub Heading label</span>

Body

Body classes use Arial 400, line-height 1.6.

.f-body-1 18 / 20 px · LH 1.6 · 400

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

<p class="f-body-1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
.f-body-2 16 px · LH 1.6 · 400

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

<p class="f-body-2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
.f-body-3 14 px · LH 1.6 · 400

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

<p class="f-body-3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
.f-body-button 14 px · LH 1.6 · 600 · applied automatically by component_button()

Do not add manually — component_button() applies this to .c-button__text / .c-link__text.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.

<p class="f-body-button">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>

Label

For tags and meta labels (e.g. news insights category tag).

.f-label 12 px · LH 1 · LS 0.120em · 700 · uppercase

Tags & labels (p-xs · 12 px)

<span class="f-label">Tags & labels</span>

Icons

Usage

Render icons through component_icon() so the wrapper and aria-hidden stay consistent. Pass a slug (with or without the icon- prefix). Use classes for colour — typically icon--currentColor.

// Standalone icon.
component_icon( 'arrow-ne', 'icon--currentColor' );

// Inside a button — component_button() adds .c-button__icon automatically.
component_button(
	array(
		'title' => 'Explore what we do',
		'url'   => '#',
	),
	'c-button-1',
	'arrow-ne',
	'icon--currentColor'
);

Header navbar: menu chevrons use inline SVG via ora_capital_navbar_icon( 'chevron-down' ) — not component_icon(). FAQ accordion chevrons are inline SVG in component-faqs.php.

ORA CTAs use dedicated arrow-ne / arrow-ne-footer slugs (not CSS rotation modifiers).

Loading methods

Production icons load from assets/img/inline-icon-{slug}.svg.php via component_icon().

Component inline icon (preferred)

component_icon( 'arrow-ne', 'icon--currentColor' );

Inline SVG template part (fallback)

get_template_part( 'assets/img/inline', 'icon-arrow-ne.svg' );

Icon catalogue

Five inline icons used across components, header, and footer. Click Copy on any snippet.

arrow-ne

Light-background CTAs (hero primary, split-text, split-media, cta-a, bento, news), news card arrow, person modal LinkedIn, 404 back. Header client-login dropdown items.

component_icon( 'arrow-ne', 'icon--currentColor' );
arrow-ne-footer

Dark-background CTAs (hero secondary, footer LinkedIn). Header client login buttons (desktop, tablet, mobile). Different default rotation — see _button.scss.

component_icon( 'arrow-ne-footer', 'icon--currentColor' );
arrow

People card link indicator. Base horizontal arrow — use .icon--up, .icon--mirror for direction.

component_icon( 'arrow', 'icon--currentColor' );
email

Person modal email action (via component_button).

component_icon( 'email', 'icon--currentColor' );
close

Person modal dismiss control.

component_icon( 'close', 'icon--currentColor' );

Buttons

Usage

All site CTAs use component_button() with c-button-1. Typography (f-body-button) and BEM elements (.c-button__text, .c-button__icon) are applied automatically.

Only one style variant exists: .c-button-1. Modifiers: .c-button--dark, .c-button--mirror.

Default — .c-button-1

Light-background CTAs: hero primary, cta-a, bento active card.

component_button(
	array(
		'title' => 'Explore what we do',
		'url'   => '#',
	),
	'c-button-1',
	'arrow-ne',
	'icon--currentColor'
);

Dark — .c-button-1.c-button--dark (arrow-ne-footer)

Dark sections: hero secondary, mobile client login.

component_button(
	array(
		'title' => 'Discover ORA',
		'url'   => '#',
	),
	'c-button-1 c-button--dark',
	'arrow-ne-footer',
	'icon--currentColor'
);

Dark — .c-button-1.c-button--dark (arrow-ne)

Dark or tinted sections: split-text, split-media, news-insights, bento inactive cards, person modal LinkedIn.

component_button(
	array(
		'title' => 'Discover ORA',
		'url'   => '#',
	),
	'c-button-1 c-button--dark',
	'arrow-ne',
	'icon--currentColor'
);

On dark — .c-button-1 (no --dark modifier)

Footer LinkedIn button and desktop/tablet client login. White label on dark background without inverting.

component_button(
	array(
		'title' => 'LinkedIn',
		'url'   => '#',
	),
	'c-button-1',
	'arrow-ne-footer',
	'icon--currentColor'
);

Mirror — .c-button-1.c-button--mirror

404 page back link only. Icon on the left.

component_button(
	array(
		'title' => 'Back to Home page',
		'url'   => '#',
	),
	'c-button-1 c-button--mirror',
	'arrow-ne',
	'icon--currentColor'
);

Dark + email icon

Person modal email action.

component_button(
	array(
		'title' => 'Email',
		'url'   => '#',
	),
	'c-button-1 c-button--dark',
	'email',
	'icon--currentColor'
);

Header — client login

Desktop navbar uses a <button> inside .c-button-group. Tablet uses .c-button-group--tablet.

<div class="c-button-group" id="desktop-cl">
	<?php
	component_button(
		array(
			'title' => 'Client Login',
		),
		'c-button-1',
		'arrow-ne-footer',
		'icon--currentColor',
		array(
			'id'            => 'desktop-cl-btn',
			'aria-haspopup' => 'true',
			'aria-expanded' => 'false',
			'aria-controls' => 'panel-cl',
		),
		'button'
	);
	?>
</div>

ACF link field

Pass an ACF link array directly as the first argument.

component_button( get_field( 'link' ), 'c-button-1', 'arrow-ne', 'icon--currentColor' );

Links

Usage

Inline text links use component-specific markup — not component_button(). Each pattern pairs a typography class (f-body-1 / f-body-2) with a component BEM class.

CTA links use component_button() — see the Buttons section.

Footer link — .c-site-footer__link

Site footer contact, legal, and back-to-top links. Arial 700 · text span + animated line.

<a href="#" class="f-body-2 c-site-footer__link">
	<span class="c-site-footer__link-text">Contact us</span>
	<span class="c-site-footer__link-line" aria-hidden="true"></span>
</a>

Split list nav — .c-split-list-a__link

Section navigation in split-list-a. Arial 700 · underline on hover via navbar-underline-after.

<a href="#" class="f-body-1 c-split-list-a__link">
	<span class="c-split-list-a__link-label">Section link</span>
</a>

Split list nav (label only) — .c-split-list-a__link--text

Title-only nav item when no URL is set. Same typography as links; no hover underline or keyboard focus.

Section label
<span class="f-body-1 c-split-list-a__link c-split-list-a__link--text">
	<span class="c-split-list-a__link-label">Section label</span>
</span>

Breadcrumb — .c-page-banner__bc-link

Page banner and 404 breadcrumbs. Same underline pattern as navbar links.

<nav class="c-page-banner__breadcrumbs" aria-label="Breadcrumb">
	<a href="#" class="f-body-2 c-page-banner__bc-link">
		<span class="c-page-banner__bc-link-label">Home</span>
	</a>
	<span class="c-page-banner__bc-sep" aria-hidden="true"></span>
	<span class="f-body-2 c-page-banner__bc-current" aria-current="page">Current page</span>
</nav>

Navbar — .nav-link

Header primary navigation. Label span receives underline on hover.

<a href="#" class="nav-link" role="listitem">
	<span class="nav-link-label">About</span>
</a>

Breadcrumbs

Default

Pass the array directly when you need full control.

A full breadcrumb trail with intermediate levels.

	
		$demo_items_full = array(
			ora_capital_breadcrumbs_home_item(),
			array( 'title' => 'Blog',          'url' => home_url( '/blog/' ) ),
			array( 'title' => 'Category Name', 'url' => home_url( '/blog/category/' ) ),
			array( 'title' => 'Current Page',  'url' => '' ),
		);
	

Short trail

Two-level — common on pages sitting directly under home.

	
		$demo_items_short = array(
			ora_capital_breadcrumbs_home_item(),
			array( 'title' => 'Current Page', 'url' => '' ),
		);
	

Helper functions

Each helper returns a ready-to-use $items array for component_breadcrumbs().

get_auto_breadcrumbs()

Auto-detects the page context and delegates to the correct helper. This is the recommended single call for most templates. It covers:

  • Pages — walks the parent hierarchy
  • Single posts and custom post types — appends the archive
  • Taxonomy archives (category, tag, custom) — walks the term parent hierarchy
  • Date archives — year → month → day chain
  • Post type and posts page archives
  • Search results — includes the query in the label
  • 404 pages
  • Front page — returns an empty array (no breadcrumbs rendered)

Yoast SEO is used as the data source when active, with each context's manual helper as a fallback.

<?php component_breadcrumbs( get_auto_breadcrumbs() ); ?>

Live output on this page:


get_page_breadcrumbs()

Walks up the page parent hierarchy. Use on Pages.

<?php component_breadcrumbs( get_page_breadcrumbs() ); ?>

Live output on this page (Design System has no parents, so only two items):


get_post_breadcrumbs()

Appends an archive item between Home and the current post. For the built-in post type it uses the Posts Page from Settings → Reading; for custom post types it uses the registered archive link.

<?php component_breadcrumbs( get_post_breadcrumbs() ); ?>

get_yoast_breadcrumbs()

Pulls Yoast SEO's breadcrumb data and maps it into the standard $items format — you get Yoast's logic (primary category, custom breadcrumb titles, complex hierarchies) rendered with your own markup and BEM classes. Returns false if Yoast is not active.

Recommended pattern — Yoast when available, manual fallback otherwise:

<?php
$items = get_yoast_breadcrumbs() ?: get_page_breadcrumbs();
component_breadcrumbs( $items );
?>

Live output on this page via Yoast:

Yoast breadcrumbs are not enabled. Enable them under Yoast SEO → Search Appearance → Breadcrumbs.


get_taxonomy_archive_breadcrumbs()

Builds a trail for category, tag, and custom taxonomy archive pages. Walks up the term parent hierarchy automatically.

<?php component_breadcrumbs( get_taxonomy_archive_breadcrumbs() ); ?>

get_date_archive_breadcrumbs()

Builds a trail for year, month, and day archive pages — only as deep as the current archive goes.

<?php component_breadcrumbs( get_date_archive_breadcrumbs() ); ?>

get_search_breadcrumbs()

Builds a trail for search results pages, including the current search query in the label.

<?php component_breadcrumbs( get_search_breadcrumbs() ); ?>

get_404_breadcrumbs()

Builds a two-item trail for 404 error pages.

<?php component_breadcrumbs( get_404_breadcrumbs() ); ?>

Space and Gaps

Spacers

Spacers are fixed and should be used for layouts: external spacing and gutters.

See Bootstrap 5.2 Spacing

See $spacers in _layout-settings.scss

Gaps

Gaps are relative to font-size and are used with .l-stack

See $gaps in _layout-settings.scss

Basics

Boxes and Colors

Colors further down the chain take precedence otherwise they will inherit the parent color.

Boxes have padding on all sides and usually have different padding at different screen sizes. Inline Bootstrap style equivalent would be
class="p-16 p-lg-32 p-xl-64"
or whatever combination you might need.

Below are two example boxes but customise everything to suit the design you are working on in layout/_box.scss

.box-1 .l-stack .color-[color] .bg-[color]

Lorem Ipsum Dolar Sit Emit

Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas faucibus mollis interdum. Donec sed odio dui.

Sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas faucibus mollis interdum. Donec sed odio.

.box-2 .l-stack .color-[color] .bg-[color]

Lorem Ipsum Dolar Sit Emit

Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas faucibus mollis interdum. Donec sed odio dui.

Sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas faucibus mollis interdum. Donec sed odio.

Images

Image loaded by WordPress attachment ID.

<?php $image_args = array (
  'id' => 779,
  'size' => 'medium',
  'classes' => 
  array (
    0 => 'first-class',
    1 => 'second-class',
  ),
); ?>

Image loaded by URL.

<?php $image_args = array (
  'url' => 'https://picsum.photos/800/450',
  'alt' => 'A placeholder image from Picsum Photos',
); ?>
A placeholder image from Picsum Photos

ID and URL both provided — ID wins.

When a valid attachment ID is present it takes precedence over the URL.

<?php $image_args = array (
  'id' => 779,
  'url' => 'https://picsum.photos/800/450',
  'alt' => 'A placeholder image from Picsum Photos',
  'size' => 'medium',
); ?>

Invalid ID with URL — URL used as source.

When the ID does not resolve to an attachment, the URL is used instead of triggering a fallback.

<?php $image_args = array (
  'id' => '0000',
  'url' => 'https://picsum.photos/800/450',
  'alt' => 'A placeholder image from Picsum Photos',
  'size' => 'medium',
); ?>
A placeholder image from Picsum Photos

Image that fails to load with no fallback, revealing the .bg-loading state.

<?php $image_args = array (
  'id' => '0000',
  'size' => 'medium',
); ?>

Image that doesn't exist with fallback .c-no-image.

<?php $image_args = array (
  'id' => '0000',
  'size' => 'medium',
  'fallback' => true,
  'fallback_type' => 'event',
); ?>

Containers

Containers use a max width. The wrapper around it is what will create the outside gutter; this helps to isolate max width and the gutter to avoid doing math.

Set the default max width using $container-max-widths in sass/variables-settings/_layout-settings.scss

Never put a background colour and a max-width on the same element. Background goes on the outer wrapper so it spans the full viewport width; the inner wrapper (.max-w-* + .mx-auto) restricts and centres the content. See copilot-instructions.md → "Containers and Full-Width Backgrounds" for the full pattern.

Containers with .l-container--{name}

Give every flexible-content section its own .l-container--{name} modifier (e.g. .l-container--hero, .l-container--split) even if it needs no special styling yet. Naming the section type means a specific section-to-section spacing relationship can be targeted later in _stack.scss with an adjacent-sibling selector, without retrofitting a class onto existing markup.

.l-container--hero — full width, flex-centred, responsive padding built in.
.l-container--full — same base class, padding removed via modifier.

.max-w-* + .mx-auto is the default inner wrapper. .container can be used instead when a section wants breakpoint-stepped width restriction (from $container-max-widths) rather than a single fixed value — .container's own padding-x is 0 in this theme, so it nests inside .l-container without doubling up on gutters.

.container as the inner wrapper — width restriction steps per breakpoint.

Containers with .l-stack-containers

100% wide until small breakpoint - padded wrapper for gutters.
100% wide until extra extra large breakpoint - no padded wrapper.

Containers with Layouts and .bg-color and .l-stack-containers

100% wide until extra extra large breakpoint
100% wide until extra extra large breakpoint
100% wide until extra extra large breakpoint
100% wide until extra extra large breakpoint - .max-w-640

Layouts

Layouts are more than likely going to sit inside a .container.

These would ideally be written in CSS so that they can be tweaked easily but they can also be built using Bootstrap which is baked in.

Cluster

The Cluster explained on Every Layout.

Split

Generally these stack on mobile but you could make a variation if you'd like.

This currently doesn't work with the Boostrap .gap- utility.

.l-split-1:1
.l-split-2:3 gap variations
.l-split-2:3 gap variations example content

Lorem Ipsum Dolar Sit Emit

Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas faucibus mollis interdum. Donec sed odio dui.

Sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas faucibus mollis interdum. Donec sed odio.

Videos

Streaming Video

Components

Three page compositions using the components built for flexible layouts. Each block uses representative static $args — not live ACF data.

Composition 1 — Home

Hero Banner · Split Text A · Bento CTA A · Split List A · Split Accordion A · Split Media A

Guidance for
shifting tides.

ORA is a financial advisory firm built for those navigating complexity, growth, and generational responsibility.

About ORA

We don't just advise.We guide.

ORA is built for discerning individuals, families, and ambitious businesses who value foresight over reaction and strategy over short-term thinking.

Learn more

Our Services

Where expertise meets intention

Private Advisory

Highly contextualised wealth management for individuals and families.

Learn more

Corporate Advisory

Strategic guidance for growing businesses navigating complex financial landscapes.

Learn more

Our capabilities

Expertise across the full advisory spectrum

What sets ORA apart

Working with ORA

Every engagement begins with listening — to your goals, constraints, and the responsibilities you carry.

From there we shape a clear advisory path: measured, personal, and built to hold under pressure.

Learn about our client approach

Composition 2 — Inner page

Page Banner · Text A · Text List A · FAQs · Boast Cards · People · Testimonial · CTA A · Split Media A

Private advisory

Private Advisory

Highly contextualised wealth management for individuals and families.

Our Story

ORA was founded on a simple belief: sophisticated capital deserves equally sophisticated counsel — delivered with discretion and care.

Today we partner with families and businesses across Australia who expect clarity, judgement, and a long view.

Portfolio Management

Actively managing your investments to maximise growth and preserve your wealth across changing market cycles.

Wealth Structuring

Designing ownership and succession structures that protect assets while remaining flexible for the next generation.

Corporate Advisory

Strategic guidance for capital raising, transactions, and growth decisions that shape long-term enterprise value.

Family Office Services

Coordinated advisory for complex family groups — investments, governance, philanthropy, and intergenerational planning.

Frequently Asked Questions

Becoming a client starts with a confidential conversation about your goals, circumstances, and the outcomes that matter most to you.

We typically partner with individuals, families, and businesses navigating meaningful complexity — whether that is wealth transition, growth capital, or multi-entity structures.

Yes. While we are based in Perth, we advise clients across Australia and maintain the relationships and networks needed to support that work.

Corporate Success stories

Who we've helped

February 2026

Island Pharmaceuticals Limited

ASX: ILA


$9,000,000

Equity Capital Raising


ORA acted as:

Lead Manager & Bookrunner

ORA

November 2025

Northshore Resources

ASX: NSR


$14,500,000

Placement


ORA acted as:

Joint Lead Manager

ORA

Our Team

Joel Ridley
Joel Ridley

Managing Director

Sarah Chen
Sarah Chen

Director, Private Advisory

James Okonkwo
James Okonkwo

Director, Corporate Advisory

Emma Walsh
Emma Walsh

Associate Director

Testimonial

ORA brought a level of clarity and care to our financial strategy that we hadn't experienced before.
Private Client, Perth WA

Ready to talk?

Start a confidential conversation

Get in Touch

A considered approach

We take the time to understand context before recommending action — because the right advice is rarely the fastest advice.

Our clients value a partner who can hold complexity without losing sight of what matters.

Meet the team

Composition 3 — Capability page

Page Banner · Split List A · Text List A · Testimonial · Split Media A

Corporate advisory

Corporate Advisory

Strategic guidance for growing businesses navigating complex financial landscapes.

Our capabilities

Expertise across the full advisory spectrum

Portfolio Management

Actively managing your investments to maximise growth and preserve your wealth across changing market cycles.

Wealth Structuring

Designing ownership and succession structures that protect assets while remaining flexible for the next generation.

Corporate Advisory

Strategic guidance for capital raising, transactions, and growth decisions that shape long-term enterprise value.

Family Office Services

Coordinated advisory for complex family groups — investments, governance, philanthropy, and intergenerational planning.

Testimonial

They understood the transaction and the people behind it — rare in this market.
Corporate Client, Melbourne VIC

Built for decisive moments

When capital, timing, and reputation are on the line, you need advisors who can move with precision.

ORA brings the market access and judgement required to execute with confidence.

Discuss a mandate

Bootstrap 5.2.3 Kitchen Sink

If this looks absolutely butchered GOOD! Not all modules are on and not all should be. Check the items that you're using and ignore the rest.

Contents

Typography

Documentation

Display 1

Display 2

Display 3

Display 4

Display 5

Display 6

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5

Heading 6

This is a lead paragraph. It stands out from regular paragraphs.

You can use the mark tag to highlight text.

This line of text is meant to be treated as deleted text.

This line of text is meant to be treated as no longer accurate.

This line of text is meant to be treated as an addition to the document.

This line of text will render as underlined.

This line of text is meant to be treated as fine print.

This line rendered as bold text.

This line rendered as italicized text.


A well-known quote, contained in a blockquote element.

Someone famous in Source Title
  • This is a list.
  • It appears completely unstyled.
  • Structurally, it's still a list.
  • However, this style only applies to immediate child elements.
  • Nested lists:
    • are unaffected by this style
    • will still show a bullet
    • and have appropriate left margin
  • This may still come in handy in some situations.
  • This is a list item.
  • And another one.
  • But they're displayed inline.

Images

Documentation
Placeholder Responsive image
A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera 200x200

Tables

Documentation
# First Last Handle
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
# First Last Handle
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
Class Heading Heading
Default Cell Cell
Primary Cell Cell
Secondary Cell Cell
Success Cell Cell
Danger Cell Cell
Warning Cell Cell
Info Cell Cell
Light Cell Cell
Dark Cell Cell
# First Last Handle
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter

Figures

Documentation
Placeholder 400x300
A caption for the above image.

Forms

Overview

Documentation
We'll never share your email with anyone else.
Radios buttons

Disabled forms

Documentation
Disabled radios buttons

Sizing

Documentation

Input group

Documentation
@
@example.com
https://example.com/users/
$ .00
With textarea

Validation

Documentation
Looks good!
Looks good!
@
Please choose a username.
Please provide a valid city.
Please select a valid state.
Please provide a valid zip.
You must agree before submitting.

Components

Accordion

Documentation

This is the first item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow.

This is the second item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow.

This is the third item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow.

Alerts

Documentation

Example heading New

Example heading New

Example heading New

Example heading New

Example heading New

Example heading New

Example heading New

Example heading New

Primary Secondary Success Danger Warning Info Light Dark

Buttons

Documentation
Placeholder Image cap
Card title

Some quick example text to build on the card title and make up the bulk of the card's content.

Go somewhere
Featured
Card title

Some quick example text to build on the card title and make up the bulk of the card's content.

Go somewhere
Card title

Some quick example text to build on the card title and make up the bulk of the card's content.

  • An item
  • A second item
  • A third item
Placeholder Image
Card title

This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

Last updated 3 mins ago

Popovers

Documentation

Scrollspy

Documentation

First heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Second heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Third heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Fourth heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Fifth heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Spinners

Documentation
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Toasts

Documentation

Tooltips

Documentation