Skip to main content

Code Culture for WordPress templating

General Principles

  1. Maximum readability and clarity.

  2. Integrity and generally accepted rules of writing.

  3. Avoiding duplicate code.

  4. Minimize SCSS.
    Make the most of styles with Tailwind. If none of the styles correspond to what is in the figma, use the most similar one.

    Allowable inaccuracy is: 10%.

  5. Isolation of elements.
    This is necessary to avoid conflicts between components.

    Example:
    the grid must be wrapped in a container. Grid settings are written to the grid, and everything else ( margin, padding, background, etc. ) is written in the wrapper of this grid




What will it give us?

  1. Easily maintainable code
  2. Good understanding of other developers' code
  3. Easy onboarding of new developers
  4. Avoiding unexpected bugs by avoiding conflicts between components



Code Principles

Grids

It is very important to choose the most suitable method of creating grids.

List of options and situations where we use them:

  1. Grid - For grids with similar, proportional elements such as cards

Grid


  1. Flex - For grids with different content, but having no more than one row and changing orientation to column at the nearest required breakpoint. A good example of application is the Media + Text component

Flex


  1. Flex Offset - For the most complex grids, with columns of different proportions with different content and several rows of these columns.

Flex Offset


Includes

  1. Each include should to have own wrapper
  2. When you pass more than one parameter, write params in a column
  3. The value of the parameter and the colon must be on the same level as level of colon & value in param with the longest param name.
  4. The colon and the value must be separated by a spaces
  5. Add a line break before and after the parameter column

✅ Correct

<div class="{{ component_class }}__image-wrapper">

{% include '01-atoms/images/image-a/image-a-1/image-a-1.twig' with {

parent_class : component_class,
img : img,
lazyload : true,
extra_class : image_class

} only %}

</div>

❌ Wrong

{% include '01-atoms/images/image-a/image-a-1/image-a-1.twig' with {
parent_class : component_class,
img : img,
lazyload : true,
extra_class : image_class
} only %}

❌ Wrong

{% include '01-atoms/images/image-a/image-a-1/image-a-1.twig' with { parent_class : component_class, img : img, lazyload : true, extra_class : image_class } only %}

Images Includes

We need to use aspect ratio for images everywhere where is possible in order to avoid situation when source images with different size proportion breaks our layout

  1. Add Tailwind Aspect Ratio to each image wrapper.
  2. Pass to include the next classes w-full h-full object-cover

✅ Correct

<div class="{{ component_class }}__img-wrapper aspect-4/3"

data-aos="fade"
data-aos-easing="ease-in"
data-aos-delay="250"
>

{# Accessing WordPress image object and making them available to Twig templates #}
{% set img = TimberImage( section.image ) %}

{# Passing this to the actual image tag to make ratio work #}
{% set image_class = 'w-full h-full object-cover' %}

{% include '01-atoms/images/image-a/image-a-1/image-a-1.twig' with {

parent_class : component_class,
img : img,
lazyload : true,
extra_class : image_class

} only %}

</div>

❌ Wrong

<div class="{{ component_class }}__img-wrapper"

data-aos="fade"
data-aos-easing="ease-in"
data-aos-delay="250"
>

{# Accessing WordPress image object and making them available to Twig templates #}
{% set img = TimberImage( section.image ) %}

{# Passing this to the actual image tag to make ratio work #}
{% set image_class = 'w-full' %}

{% include '01-atoms/images/image-a/image-a-1/image-a-1.twig' with {

parent_class : component_class,
img : img,
lazyload : true,
extra_class : image_class

} only %}

</div>

Repeaters

Required elements of each repeater must be repeater, repeater-inner, repeater-item


✅ Correct

<div class="{{ component_class }}__repeater">

<div class="{{ component_class }}__repeater-inner grid gap-4 grid-cols-1 lg:grid-cols-2">

{% for item in items %}

<div class="{{ component_class }}__repeater-item">

{% include '02-molecules/cards/card-post-a/card-post-a-1.twig' with { item : item } only %}

</div>

{% endfor %}

</div>

</div>

❌ Wrong

<div class="{{ component_class }}__repeater">

{% for item in items %}

{% include '02-molecules/cards/card-post-a/card-post-a-1.twig' with { item : item } only %}

{% endfor %}

</div>

ACF

When Organism has repeater don't remove any output options




Folder structure

|── src - Main Frontend
|
| |── global - Global files
| |
| | |── js - Global JS
| | |── styles - Global SCSS
| | |
| | | |── base - SCSS Variables, Config of Fonts, Controls, Spaces, Animations, etc
| | | |── layout - Styles for the main layout of website
| | | |── vendors - Styles of libraries
| | |
| | |── twig - Global TWIG components
| | |
| | | |── base-banner - Banner which is used on each page
| | | |── base-cta - CTA which is used on each page
| | | |── base-html-head - Start of HTML with HEAD content
| |
| |── patterns - Components
| |
| | The components are built according to the atomic structure. Templates are built from organisms, organisms from molecules, molecules from atoms.
| |
| | The rule for the names of components (the rule was not followed everywhere, but for new components it must be done).
| |
| | [component-name]-[type( a, b, c, etc.)] - Component name with type
| |
| | [component-name]-[type]-[version( 1, 2, 3, etc. )] - Component version
| |
| | --------------------------------------------------------------------------
| | |
| | |── 01-atoms - Atoms ( Typography, Links, Buttons, Icons, etc. )
| | |── 02-molecules - Molecules ( Cards, Navs, Accordions, Backgrounds, Galleries, etc. )
| | |── 03-organisms - Organisms ( Blocks )
| | | |
| | | |── _base - Helpers
| | | | |
| | | | |── base-block - Base wrapper of each block
| | | | |── base-block-repeater - Connection controller for all custom blocks on the current page
| | | | |── repeatable-base.hcl.json - Internal scripts files ( skip it )
| | | |
| | | |── repeatable-custom - Custom Blocks
| | | | |
| | | | |── [block-name]-a - Block ( with type -a )
| | | | | |
| | | | | |── [block-name]-a-1 - Block Version
| | | | | | |
| | | | | | |── [block-name]-a-1-custom.twig - Custom content of the block. ( Repeaters, embeds and any custom content )
| | | | | | |── [block-name]-a-1-inner.twig - Main content of the block ( Pretitle, title, content, button, custom include )
| | | | | | |── [block-name]-a-1.twig - Filling the base wrapper of the block. Setting vertical offsets, background, text color and alignment
| | | | |
| | | | | |── index.twig - Main file of the block. Here are connected: the base wrapper of the block "base-block", the version of the block according to the data from ACF
| | | | |
| | | | |── repeatable-custom.acf.json - Adding the ACF of each block to the main ACF "Repeatable Custom". In the admin panel, when adding a block, show up pop-up with a selection of blocks. A list of these blocks is in this file.
| | | |
| | | |── repeatable-default - Default blocks ( Coming soon )
| | | |
| | | |── single - Single Blocks ( Header, Hero, Footer, Posts overview, etc. )
| | |
| | |── 04-templates - Templates ( Base, Singles, Overviews, etc. )
| | |
| | |── all.css - Imports the styles of all components
| |
| |── index.js - The root JS in which all script imports are added
| |── style.scss - The root SCSS in which the main style imports are added

General structure of TWIG files

  1. All code must be inside a tag {% spaceless %}...{% endspaceless %}
  2. After opening {% spaceless %}, there should be a comment with a description of the variables that are imported to this component through the include of the parent component ( applies only to Molecules and Atoms ). You can see examples in finished components.



Elements naming

  1. Name the elements according to the BEM methodology.

  2. The name of each class ( selector ) should start with {{ component_class }}__.
    Exceptions: classes from Tailwind and other libraries.

  3. Includes wrap in a div with a class
    {{ component_class }}__[include-name]-wrapper

  4. Column container in the Main content of the block
    {{ component_class }}__columns

  5. Column in the Main content of the block
    {{ component_class }}__column

  6. Repeater container
    {{ component_class }}__repeater

  7. Container of repeater elements
    {{ component_class }}__repeater-item

  8. Inner Container
    {{ component_class }}__[component-name]-inner,
    {{ component_class }}__[component-name]-inner-[index( 1, 2, 3, etc. )]
    depending on the level of nesting.


✅ Correct

<div class='{{ component_class }}__title-wrapper'>

{% include '01-atoms/typography/title-a/title-a-1/title-a-1.twig' with { title : 'Title' } only %}

</div>

❌ Wrong

<div class='title-wrapper'>

{% include '01-atoms/typography/title-a/title-a-1/title-a-1.twig' with { title : 'Title' } only %}

</div>


<div class='title'>

{% include '01-atoms/typography/title-a/title-a-1/title-a-1.twig' with { title : 'Title' } only %}

</div>


<div class='{{ component_class }}-title-wrapper'>

{% include '01-atoms/typography/title-a/title-a-1/title-a-1.twig' with { title : 'Title' } only %}

</div>


✅ Correct

<div class="{{ component_class }}__repeater">

<div class="{{ component_class }}__repeater-inner grid gap-4 grid-cols-1 lg:grid-cols-2">

{% for item in items %}

<div class="{{ component_class }}__repeater-item">

{% include '02-molecules/cards/card-post-a/card-post-a-1.twig' with { item : item } only %}

</div>

{% endfor %}

</div>

</div>

❌ Wrong

<div class="{{ component_class }}__posts">

<div class="{{ component_class }}__posts-items grid gap-4 grid-cols-1 lg:grid-cols-2">

{% for item in items %}

<div class="{{ component_class }}__post">

{% include '02-molecules/cards/card-post-a/card-post-a-1.twig' with { item : item } only %}

</div>

{% endfor %}

</div>

</div>

❌ Wrong

<div class="{{ component_class }}__repeater">

<div class="{{ component_class }}__repeater__inner grid gap-4 grid-cols-1 lg:grid-cols-2">

{% for item in items %}

<div class="{{ component_class }}__repeater__item">

{% include '02-molecules/cards/card-post-a/card-post-a-1.twig' with { item : item } only %}

</div>

{% endfor %}

</div>

</div>


✅ Correct

<div class="{{ component_class }}__columns">

<div class="{{ component_class }}__column {{ component_class }}__column--1">Content</div>
<div class="{{ component_class }}__column {{ component_class }}__column--2">Content</div>
<div class="{{ component_class }}__column {{ component_class }}__column--3">Content</div>

</div>

❌ Wrong

<div class="{{ component_class }}__row">

<div class="{{ component_class }}__cell">Content</div>
<div class="{{ component_class }}__cell">Content</div>
<div class="{{ component_class }}__cell">Content</div>

</div>



Main content of the block structure

[block-name]-a-1-inner.twig - Correct structure for Main content of the block ( Pretitle, Title, Content, Image, Custom Include, Button )

  1. Content should wrapped into 2 containers: Container and Container Inner.
    Scheme:
    Container -> Container Inner -> Content

  2. When you need to put columns to container:
    Scheme:
    Container -> Container Inner -> Columns -> Column -> Content

  3. Container should to have container (l-container for Helium < 1.1.2) class which set global max-width for content. Also you should to add content type modificator to this element ( about it below ).

  4. Inner Container with text-align and custom max-width from ACF ( if available )

  5. Each Block by default should to contain 3 Containers type:

  • Container Text - Pretitle, Title, Text, Image
  • Container Custom - Custom include if it's not empty
  • Container Button - Button include if it exists
  1. Few containers with the same. When you need to add it ( example: you need to make different max-width ) just add name it Container Text 2
{{ component_class }}__container {{ component_class }}__container--text

{{ component_class }}__container-inner

[pretitle]

[title]

[content]

[image]

{{ component_class }}__columns - If the content should be in columns

{{ component_class }}__column {{ component_class }}__column--[index( 1, 2, 3, etc. )]

[content]



{{ component_class }}__container {{ component_class }}__container--custom

{{ component_class }}__container-inner

[custom_include]



{{ component_class }}__container {{ component_class }}__container--button

{{ component_class }}__container-inner

[button]

✅ Correct

<div class="{{ component_class }}__container {{ component_class }}__container--text container container--small"> <!-- l-container for Helium < 1.1.2 -->

<div class="{{ component_class }}__container-inner mx-auto {{ alignment_classes }}" {% if section.add_max_width and section.max_width %} style="max-width: {{ section.max_width }}px"{% endif %}>

Pretitle Include

Title Include

Text Include

Image Include

<div class="{{ component_class }}__columns grid gap-4 grid-cols-1 lg:grid-cols-2">

<div class="{{ component_class }}__column {{ component_class }}__column--1">Column Content</div>
<div class="{{ component_class }}__column {{ component_class }}__column--2">Column Content</div>

</div>

</div>

</div>



{% set block_custom %}

Custom Include

{% endset %}


{% if block_custom|trim is not empty %}

<div class="{{ component_class }}__container {{ component_class }}__container--custom container"> <!-- l-container for Helium < 1.1.2 -->

<div class="{{ component_class }}__container-inner mx-auto {{ alignment_classes }}" {% if section.add_max_width and section.max_width %} style="max-width: {{ section.max_width }}px"{% endif %}>

{{ block_custom }}

</div>

</div>

{% endif %}



{% if section.add_button and section.button %}

<div class="{{ component_class }}__container {{ component_class }}__container--button container"> <!-- l-container for Helium < 1.1.2 -->

<div class="{{ component_class }}__container-inner mx-auto {{ alignment_classes }}" {% if section.add_max_width and section.max_width %} style="max-width: {{ section.max_width }}px"{% endif %}>

Button Include

</div>

</div>

{% endif %}

❌ Wrong

<div class="{{ component_class }}__wrapper container container--small"> <!-- l-container for Helium < 1.1.2 -->

<div class="{{ component_class }}__container-inner">

Pretitle Include

Title Include

Text Include

Image Include

</div>


<div class="{{ component_class }}__row grid gap-4 grid-cols-1 lg:grid-cols-2">

<div class="{{ component_class }}__cell">Column Content</div>
<div class="{{ component_class }}__cell">Column Content</div>

</div>

</div>



<div class="{{ component_class }}__wrapper-2 container"> <!-- l-container for Helium < 1.1.2 -->

<div class="{{ component_class }}__container-inner mx-auto"}>

Custom Include

</div>

</div>



<div class="{{ component_class }}__wrapper-3 container"> <!-- l-container for Helium < 1.1.2 -->

<div class="{{ component_class }}__container-inner mx-auto">

{% if section.add_button and section.button %}

Button Include

{% endif %}

</div>

</div>



Classes

If more than 4 classes must be added to the element, or the element must have different classes depending on if, use the following scheme

  1. Create a variable [element_name]_classes with an array containing only the main class of the element
{% set [element_name]_classes = [ component_class ~ '__[element-name]' ] %}

  1. If you need to add a modifier depending on the "if" condition
{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([ component_class ~ '__[element-name]--[modificator]' ]) %}

{% endif %}

  1. Add classes from Tailwind ( write in a column )
{% set [element_name]_classes = [element_name]_classes|merge([

'grid',
'grid-cols-1',
'gap-6',

'mx-auto',

]) %}

  1. If you want to add Tailwind classes depending on the "if" condition
{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([

'xs:grid-cols-2',
'xl:grid-cols-3',

]) %}

{% endif %}

  1. Assign classes to element
<div class="{{ [element_name]_classes|join(' ') }}"></div>

✅ Correct

{% set [element_name]_classes = [ component_class ~ '__element-name' ] %}

{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([ component_class ~ '__element-name--modificator' ]) %}

{% endif %}


{% set [element_name]_classes = [element_name]_classes|merge([

'grid',
'grid-cols-1',
'gap-6',

'mx-auto',

]) %}


{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([

'xs:grid-cols-2',
'xl:grid-cols-3',

]) %}

{% endif %}

<div class="{{ [element_name]_classes|join(' ') }}"></div>

❌ Wrong

{% set [element_name]_classes = [

component_class ~ '__element-name' ],

'grid',
'grid-cols-1',
'gap-6',

'mx-auto',

]) %}

{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([

'xs:grid-cols-2',
'xl:grid-cols-3',

]) %}

{% endif %}

{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([ component_class ~ '__element-name--modificator' ]) %}

{% endif %}

<div class="{{ [element_name]_classes|join(' ') }}"></div>

❌ Wrong

{% set [element_name]_classes = [ component_class ~ '__element-name' ] %}

{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([ component_class ~ '__element-name--modificator' ]) %}

{% endif %}


{% set [element_name]_classes = [element_name]_classes|merge([ 'grid', 'grid-cols-1', 'gap-6', 'mx-auto' ]) %}


{% if data %}

{% set [element_name]_classes = [element_name]_classes|merge([ 'xs:grid-cols-2', 'xl:grid-cols-3' ]) %}

{% endif %}

<div class="{{ [element_name]_classes|join(' ') }}"></div>

Class Grouping

  1. Write styles in the same order as in the Tailwind documentation.
  2. Add line breaks to separate style categories
  3. For side-related properties, use the following order X / Y, Top / Right / Bottom / Left,
  4. Group classes by breakpoints. From smallest to largest. px-2, py-4, md:px4, md:py-8



Spaces

With the help of indentation, we can visually break the code into blocks. Before reading the text, our eye first visually divides it into blocks: Headings, paragraphs, lists, etc. Authors of books and articles specially divide the text into such blocks so that it is easier for you to orient yourself in the text and read it. We can do the same with code

Use the following indents:


0 line breaks
for components of the same level that are 1 line in size.

<li>Text</li>
<li>Text</li>
<li>Text</li>

1 line break
between parent and child components or between components of the same level, one of which has 1 line in size and the other more than 2

<ul>
<!--1-->
<li>Text</li>
<li>Text</li>
<li>Text</li>
<!--1-->
</ul>
<h3>Title</h3>
<!--1-->
<ul>
<!--1-->
<li>Text</li>
<li>Text</li>
<li>Text</li>
<!--1-->
</ul>

2 line breaks
for components of the same level with the size of 2-5 lines

<ul>
<!--1-->
<li>Text</li>
<li>Text</li>
<li>Text</li>
<!--1-->
</ul>
<!--1-->
<!--2-->
<ul>
<!--1-->
<li>Text</li>
<li>Text</li>
<li>Text</li>
<!--1-->
</ul>

3 line breaks
for components of the same level with the size of 5+ lines

<div>
<!--1-->
<h3>Title</h3>
<!--1-->
<ul>
<!--1-->
<li>Text</li>
<li>Text</li>
<li>Text</li>
<!--1-->
</ul>
<!--1-->
</div>
<!--1-->
<!--2-->
<!--3-->
<div>
<!--1-->
<h3>Title</h3>
<!--1-->
<ul>
<!--1-->
<li>Text</li>
<li>Text</li>
<li>Text</li>
<!--1-->
</ul>
<!--1-->
</div>

✅ Correct

<div>

<h3>Title</h3>

<ul>

<li>Text</li>
<li>Text</li>
<li>Text</li>

</ul>

</div>



<div>

<h3>Title</h3>

<ul>

<li>Text</li>
<li>Text</li>
<li>Text</li>

</ul>

</div>

❌ Wrong

<div>
<h3>Title</h3>
<ul>
<li>Text</li>
<li>Text</li>
<li>Text</li>
</ul>
</div>

<div>
<h3>Title</h3>
<ul>
<li>Text</li>
<li>Text</li>
<li>Text</li>
</ul>
</div>

❌ Wrong

<div>

<h3>Title</h3>

<ul>

<li>Text</li>

<li>Text</li>

<li>Text</li>

</ul>

</div>

<div>

<h3>Title</h3>

<ul>

<li>Text</li>

<li>Text</li>

<li>Text</li>

</ul>

</div>



Comments

  1. Try to use comments as it has been done in other components.
  2. In all atoms and molecules, write down the variables that are imported to the component and what information they contain. The syntax can also be seen in ready-made components
  3. When the components are pretty large, comment out the closing tags as follows
<div class="container">

<div class="container-inner">

...

{# Container inner end. #}
</div>

{# Container end. #}
</div>



Terms

Inner Container - a container that is inserted inside the wrapper. Grid styles ( grid or flex ) are usually configured for this element. The inner container is needed to isolate the grid settings from the main wrapper. This is necessary in order to observe the principle of element isolation

Flex Offset - a swiss army knife for creating grids / columns, which was used even by our grandpas in 2012. Allows you to create responsive grids, the columns of which contain very different content and also change their width in different ways depending on the breakpoint. Use to create columns with different (like cards) content.

How to code:

<div class="items flex flex-wrap -ml-4 gap-y-4">

<div class="item w-1/2 pl-4">Item</div>
<div class="item w-1/2 pl-4">Item</div>
<div class="item w-1/2 pl-4">Item</div>

</div>