Skip to content

HTML / Twig

Use semantic elements to give meaning to the document structure. Avoid <div> and <span> when a more descriptive element fits.

<!-- Good -->
<header class="site-header">
<nav class="nav">...</nav>
</header>
<main class="main">
<article class="card">...</article>
<aside class="sidebar">...</aside>
</main>
<footer class="site-footer">...</footer>
<!-- Bad -->
<div class="site-header">
<div class="nav">...</div>
</div>

Common semantic elements: <header>, <nav>, <main>, <section>, <article>, <aside>, <footer>, <figure>, <time>.

Use <button> for actions and <a> for navigation. Never use a <div> or <span> as a clickable element.

<!-- Good -->
<button class="button" type="button">Open menu</button>
<a class="card__link" href="/about">Read more</a>
<!-- Bad -->
<div class="button" onclick="openMenu()">Open menu</div>

Always provide a text label for interactive elements. Use aria-label when visible text is absent.

<!-- Good — visible text -->
<button class="button">Close</button>
<!-- Good — icon-only button with aria-label -->
<button class="button button--icon" aria-label="Close menu">
<svg>...</svg>
</button>
<!-- Bad — no label -->
<button class="button button--icon">
<svg>...</svg>
</button>

Add alt text to all images. Use an empty alt="" for purely decorative images.

<!-- Good — descriptive alt -->
<img src="team.jpg" alt="The Instance Studio team at their Melbourne office" />
<!-- Good — decorative image -->
<img src="divider.svg" alt="" />
<!-- Bad — no alt -->
<img src="team.jpg" />

Use 4 spaces.

Lowercase and double quotes [Prettier enforced]

Section titled “Lowercase and double quotes [Prettier enforced]”

Use lowercase for all tags and attributes. Use double quotes for all attribute values.

<!-- Good -->
<section class="hero">
<h1 class="hero__title">Heading</h1>
</section>
<!-- Bad -->
<SECTION CLASS='hero'>
<H1 CLASS='hero__title'>Heading</H1>
</SECTION>

Close all HTML tags. Use the self-closing syntax for void elements.

<!-- Good -->
<img src="..." alt="..." />
<input type="text" />
<!-- Bad -->
<img src="..." alt="...">
<input type="text">

Use CSS classes instead. Inline styles bypass the design system and are hard to override.

<!-- Good -->
<section class="hero hero--dark">...</section>
<!-- Bad -->
<section style="background: #000; color: #fff;">...</section>

Keep spacing clean and consistent inside Twig tags.

{# Good #}
{{ post.title }}
{% if post.title %}
{# Bad #}
{{post.title}}
{%if post.title%}

Twig templates should only render data — no queries, no processing. All data must come from $context.

{# Good — data passed from PHP #}
{% for member in team_members %}
<p>{{ member.name }}</p>
{% endfor %}
{# Bad — fetching data in Twig #}
{% for member in function('get_posts', {post_type: 'team'}) %}
<p>{{ member.post_title }}</p>
{% endfor %}

Pass only the variables a partial needs using with {}. This makes the data flow explicit.

{% include 'partials/card.twig' with {
title: post.title,
image: post.thumbnail,
link: post.link
} %}

Use {% set %} to avoid repeating the same expression.

{# Good #}
{% set imageUrl = post.thumbnail.src %}
<img src="{{ imageUrl }}" srcset="{{ imageUrl }}?w=400 400w, {{ imageUrl }}?w=800 800w" />
{# Bad — expression repeated #}
<img src="{{ post.thumbnail.src }}" srcset="{{ post.thumbnail.src }}?w=400 400w" />

Use the clsx Twig function to build class strings conditionally.

<article class="{{ clsx('card', isFeatured and 'card--featured', isLarge and 'card--large') }}">