Jump to Navigation Jump to Main Content Jump to Footer
Home » Docs » Features » Ready‑made components

Ready‑made components

Chisel includes a library of pre-built Twig components that handle common UI patterns—headers, footers, navigation, pagination, search forms, icons, and post listings. These components are located in views/components/ and views/objects/, ready to include or extend in your templates.

Components are designed with flexibility in mind: they accept parameters for customization, use Twig blocks for extensibility, and follow BEM naming conventions for consistent styling. The Components helper class provides cached access to site-wide elements like menus, logos, sidebars, and page titles—automatically added to the global Timber context.

All components support translation, responsive images, and accessibility features out of the box.

Component directories

DirectoryPurposeExamples
views/components/Larger UI components​Header, footer, navigation, pagination, post items
views/objects/Atomic UI elements​Icons, badges, buttons
views/partials/Template fragmentsReusable template pieces

If you want full seperation between core and custom you can create custom component inside custom/views/components/ directory


Components helper class

File: core/Timber/Components.php

Provides static methods for retrieving cached site components. Results are cached internally to avoid redundant processing.

Available methods

Navigation menus:

Components::get_menus(): array
PHP

Returns all registered Chisel menus (prefixed with chisel_).

Custom logo:

Components::get_logo(): string
PHP

Returns responsive HTML for the site logo from Customizer.

Sidebars:

Components::get_sidebar( string $sidebar_id = '' ): ?array
PHP

Returns sidebar data with idname, and content keys. Automatically detects context if no ID provided:

  • Blog posts → chisel-sidebar-blog
  • WooCommerce → chisel-sidebar-woocommerce

Footer sidebars:

Components::get_footer_sidebars(): array
PHP

Returns footer widget columns with automatic grid classes:

  • 4 columns → o-layout__item--3-large
  • 3 columns → o-layout__item--4-large
  • 2 columns → o-layout__item--6-large
  • 1 column → o-layout__item--12

Page title:

Components::get_the_title(): array
PHP

Returns current page/archive title with text and class keys. Respects ACF page_title_display field (showhidehide-visually)

Icons:

Components::get_icon( array $args ): string
PHP

Renders icon from views/objects/icon.twig template. Cached by argument combination.

Global context integration

All methods are automatically available in Twig templates via the global Site::add_to_context() method. Components are accessible directly without prefixing Components::


Available components

Header

File: views/components/header.twig

Usage:

{% include 'components/header.twig' %}
Twig

Structure:

  • Includes logo component via block header_logo
  • Includes main navigation via block header_nav
  • Uses BEM classes: c-headerc-header__inner
  • Wrapped in o-wrapper container

Footer

File: views/components/footer.twig

Usage:

{% include 'components/footer.twig' %}
Twig

Context:

  • footer_sidebars → Footer widget columns from global context
  • Automatically applies responsive grid classes based on column count

Logo

File: views/components/logo.twig

Usage:

{% include 'components/logo.twig' %}
Twig

Context:

  • logo → Responsive logo HTML from global context
  • Retrieved via Components::get_logo()

Main navigation

File: views/components/main-nav.twig

Includes: components/main-nav-item.twig for each menu item

Usage:

{% include 'components/main-nav.twig' %}
Twig

Context:

  • menus.primary → Primary menu from global context
  • Retrieved via Components::get_menus()

Pagination

File: views/components/pagination.twig

Usage:

{% include 'components/pagination.twig' with { posts: posts } %}
Twig

Parameters:

  • posts → Timber post collection with pagination data
  • type → 'load-more' for AJAX load more button, omit for standard pagination
  • load_more → Object with post_type and per_page for load more functionality

Standard pagination:

{% include 'components/pagination.twig' with { posts: posts } %}
Twig

Load more:

{% include 'components/pagination.twig' with {
    posts: posts,
    type: 'load-more',
    load_more: {
        post_type: 'post',
        per_page: 10
    }
} %}
Twig

Features:

  • First/Last page links
  • Previous/Next navigation
  • Numbered page links
  • Disabled state for unavailable actions
  • Load more button with AJAX integration
  • Translatable labels

Post item

File: views/components/post-item.twig

Usage:

{% for post in posts %}
    {% include 'components/post-item.twig' with { post: post } %}
{% endfor %}
Twig

Parameters:

  • post → Timber post object

Blocks available for extension:

  • item_content → Entire content wrapper
  • item_title → Post title and link
  • item_category → Category badge
  • item_meta → Author and date
  • item_excerpt → Post excerpt
  • item_tags → Tag links
  • item_image → Featured image

Search form

File: views/components/search-form.twig

Usage:

{% include 'components/search-form.twig' %}
Twig

Parameters:

  • label → Search field label (default: 'Search for:')
  • placeholder → Input placeholder (default: 'Search...')
  • post_type → Limit search to specific post type
  • search_query → Pre-fill search query (from site.search_query)

Example with post type:

{% include 'components/search-form.twig' with {
    placeholder: __('Search products...', 'chisel'),
    post_type: 'product'
} %}
Twig

Page title

File: views/components/the-title.twig

Usage:

{% include 'components/the-title.twig' %}
Twig

Context:

  • the_title → Title data from global context
  • Retrieved via Components::get_the_title()
  • Respects ACF page_title_display field

No results

File: views/components/no-results.twig

Usage:

{% if posts|length == 0 %}
    {% include 'components/no-results.twig' %}
{% endif %}
Twig

Displays message when no posts/results are found

Slider component

File: views/components/slider.twig

Usage:

{% set slides_html %}
    <div class="swiper-slide">Slide 1</div>
    <div class="swiper-slide">Slide 2</div>
    <div class="swiper-slide">Slide 3</div>
{% endset %}

{% include 'components/slider.twig' with {
    slides_html: slides_html,
    params: {
        'slides-per-view': 1,
        arrows: 'yes',
        dots: 'yes',
        loop: 'yes'
    }
} %}
Twig

Parameters:

  • slides_html → HTML string containing slides (each must have swiper-slide class)
  • params → Configuration object processed by slider_prepare_params() function
  • slider_container_class_names → Optional classes for container wrapper
  • slider_class_names → Optional classes for slider element

Used with slider_prepare_params() Twig function for data attributes

Slider component uses Global slider module, which can be used to create custom sliders and carousels

Icon component

File: views/objects/icon.twig

Usage via Twig function:

{{ get_icon({ name: 'arrow-right' }) }}
Twig

Usage via direct include:

{% include 'objects/icon.twig' with { name: 'search' } %}
Twig
ParameterTypeDescription
namestringIcon name (required)​
inlinebooleanDisplay as inline element (default: false)​
rectanglebooleanIcon is rectangular, not square​
force_monobooleanForce monochrome for icons starting with color-
altstringAccessible label for screen readers​
is_cssbooleanLoad icon via CSS background (requires CSS config)​
colorstringCSS color value​

Icon systems

Individual SVG files:

  • Icons loaded from assets/icons-source/{name}.svg

SVG sprite (disabled by default):

When should_use_icons_module() returns true:

  • Icons loaded from assets/icons/icons.svg sprite
  • Format: #icon-{name}-view

Color modes

Monochrome icons:

  • Use mask-image CSS property
  • Color controlled via color parameter
  • Class: o-icon--mono

Color icons:

  • Detected when name starts with color-
  • Use background-image CSS property
  • Preserve original SVG colors
  • Class: o-icon--color

Examples

Basic icon:

{{ get_icon({ name: 'search' }) }}
Twig

Inline icon with color:

{{ get_icon({ 
    name: 'arrow-right', 
    inline: true, 
    color: '#ff6600' 
}) }}
Twig

Accessible icon:

{{ get_icon({ 
    name: 'close', 
    alt: __('Close menu', 'chisel') 
}) }}
Twig

CSS icon classes

Chisel provides a powerful mixin-based system for adding icons via CSS. Icons are defined in settings and applied using mixins from the tools layer. This approach is useful for adding icons to buttons (integrated also with the wp core buttons block) or links, but also as a solo icon using a span element.

Configuration files

Icon settings: src/design/settings/_icon-settings.scss

// Static monochrome icons
$static-icons: (
  'minus',
  'plus',
  'arrow-right',
  'arrow-left',
  'search'
);

// Animated icons (e.g., loaders)
$animated-icons: (
  'loader'
);

// Multi-color icons that should preserve original colors
$multicolor-icons: ();
SCSS

Icon mixin: src/design/tools/_icon.scss

The @mixin icon-svg() generates a complete icon with ::after or ::before pseudo-element:

Mixin parameters

@mixin icon($name, $multicolor: false, $animated: false)

ParameterTypeDefaultDescription
$namestring(required)Icon name from settings list
$multicolorbooleanfalseIf the icon is multicolor
$animatedbooleanfalseIf the icon is animated

Adding new icons

Step 1: Add SVG file

Place your icon in assets/icons-source/{name}.svg (e.g., download.svg)

Step 2: Add to settings list

File: src/design/settings/_icon-settings.scss:

$static-icons: (
  'minus',
  'plus',
  'arrow-right',
  'arrow-left',
  'search',
  'download',  // New icon added
  'upload',    // New icon added
);
SCSS

Using has-icon classes

Basic syntax:

<a href="/contact" class="has-icon has-icon-arrow-right">
    Contact Us
</a>
HTML

Pattern:

  • Base class: has-icon
  • Icon class: has-icon-{icon-name}

Common use cases

Button with icon:

<button class="c-btn has-icon has-icon-download">
    Download PDF
</button>
HTML

Link with icon

<a href="/search" class="has-icon has-icon-search">
    Search
</a>
HTML

List items with icons:

<ul class="c-list">
    <li><span class="has-icon has-icon-check">Feature one</span></li>
    <li><span class="has-icon has-icon-check">Feature two</span></li>
    <li><span class="has-icon has-icon-check">Feature three</span></li>
</ul>
HTML

Icon only (no text)

<button class="has-icon has-icon-close has-icon-only" aria-label="Close">
    <span class="u-visually-hidden">Close</span>
</button>
HTML

has-icon-only class removes the spacing that is added between the icon and the text.

Icon positioning

By default, CSS icons appear after the text content using ::after pseudo-element. You can change the positioning to display the icon before the text by using a class: has-icon-left


Blocks as components

Chisel includes pre-built blocks that function as interactive components with built-in JavaScript behavior and BEM-styled markup.

Accordion block

Path: src/blocks/accordion/

Files:

  • Definition: src/blocks/accordion/block.json (chisel/accordion)
  • Editor UI: src/blocks/accordion/edit.js
  • Save output: src/blocks/accordion/save.js
  • Frontend JS: src/blocks/accordion/view.js
  • Inner block: src/blocks/accordion/accordion-item.js (chisel/accordion-item)

Block attributes

Parent block (chisel/accordion):

  • closeOthers (boolean, default: false) → Close other items when one opens
  • firstOpen (boolean, default: false) → Open first item on load
  • titleTag (string, default: 'h3') → Heading tag for item titles

Inner block (chisel/accordion-item):

  • title (string) → Item title text
  • titleTag (string) → Auto-injected from parent via context

Frontend behavior

Initialization:

The view.js script automatically initializes on DOMContentLoaded for all .js-accordion elements.

class Accordion {
    constructor(accordion) {
        this.accordion = accordion;
        // Reads settings from CSS classes
        this.settings = {
            closeOthers: accordion.classList.contains('has-close-others'),
            firstOpen: accordion.classList.contains('has-first-open')
        };
        // Opens first item if setting enabled
        this.firstOpenhandler();
        // Attaches click handlers to items
        this.toggleItems();
    }
}
JavaScript

Animation:

  • Uses native <details> element for accessibility
  • Smooth height animation (200ms linear)
  • State class is-open applied during animation
  • Overflow control during transitions

Selectors:

  • .js-accordion → Main accordion wrapper
  • .js-accordion-item → Individual accordion item
  • .js-accordion-header → Item header/trigger
  • .js-accordion-content → Item content area

Styling hooks

BEM classes:

  • Block: b-accordion
  • Elements: b-accordion__itemb-accordion__item-headerb-accordion__item-titleb-accordion__item-content
  • State: is-open (applied by JS during animation)
  • Modifiers: has-first-openhas-close-others (wrapper flags)

Style files:

  • Editor: src/blocks/accordion/editor.scss
  • Frontend: src/blocks/accordion/style.scss

Editor usage

  1. Insert “Accordion” block in editor
  2. Configure sidebar settings:
    • Title tag (h2h3h4, etc.)
    • First item open (boolean)
    • Close other items (boolean)
  3. Add “Accordion Item” inner blocks
  4. Each item has:
    • Title field (text input)
    • Content area (accepts any blocks)

Slider block (ACF)

Path: src/blocks-acf/slider/

Files:

  • Template: src/blocks-acf/slider/slider.twig
  • ACF schema: src/blocks-acf/slider/acf-json/group_66462c70b851f.json
  • Styles: src/blocks-acf/slider/style.scss
  • Script: src/blocks-acf/slider/script.js (imports styles only)

ACF fields

Slides repeater (acf_block_slider_slides):

  • acf_bs_image → Image field (ID)

Settings group (cloned from group_66d5a4ebb41cd, exposed as fields.slider_options):

FieldTypeDescription
slider_settingsCheckboxMulti-select: arrows, dots, loop, autoplay, thumbnails
slider_settings_dynamic_dotsTrue/FalseEnable dynamic dots (requires dots enabled)
slider_settings_autoplay_timeoutNumberAutoplay delay in milliseconds (requires autoplay)
slider_settings_thumbnails_noNumberNumber of visible thumbnails (requires thumbnails)

Rendering flow

Twig template processing:

  1. slider.twig loops through acf_block_slider_slides
  2. Each slide rendered as <div class="swiper-slide b-slider__slide"> with responsive image
  3. If thumbnails enabled, adds data-thumbnail-url attribute (medium size)
  4. Composes slides_html string
  5. Includes views/components/slider.twig with params

Data attribute mapping:

The slider_prepare_params() Twig function converts ACF settings to data attributes:

ACF settingData attribute
arrows checkeddata-arrows="yes"
dots checkeddata-dots="yes"
dots + dynamic_dotsdata-dots-dynamic="1"
loop checkeddata-loop="yes"
autoplay checkeddata-autoplay="yes" + data-autoplay-timeout="{ms}"
thumbnails checkeddata-thumbnails="{count}"
Custom paramsPassed through as data-{param-name}

Styling hooks

BEM classes:

  • Container: b-slider__inner
  • Component: c-slider
  • JS hooks: js-sliderjs-slider-container
  • Slides: b-slider__slide swiper-slide
  • Type modifier: c-slider--{type} (from params)

Editor usage

  1. Insert “Slider” (ACF) block
  2. Add slides (upload images in repeater)
  3. Configure Settings:
    • Enable arrows, dots, loop, autoplay, thumbnails
    • If dots: toggle dynamic dots
    • If autoplay: set timeout (milliseconds)
    • If thumbnails: set number of visible thumbs

Global slider module

File: src/scripts/modules/slider.js

Auto-initialization:

Imported and executed in src/scripts/app.js

import Slider from './modules/slider';

Slider();
JavaScript

How it works

Discovery:

The module finds all .js-slider elements on page load and initializes a Swiper instance for each:

const sliders = document.querySelectorAll('.js-slider');

loadModules().then(({ swiper, swiperModules }) => {
    sliders.forEach((slider) => {
        new Slider(slider, swiper, swiperModules);
    });
});
JavaScript

Lazy loading:

Swiper.js and its modules are dynamically imported only when sliders exist on the page:

const [swiper, swiperModules, style] = await Promise.all([
    import('swiper'),
    import('swiper/modules'),
    import('swiper/swiper-bundle.css'),
]);
JavaScript

Configuration via data attributes

Supported attributes:

AttributeTypeDefaultDescription
data-typestring'default'Slider type (triggers type-specific params)
data-arrows'yes'|'no''no'Enable navigation arrows
data-autoplay'yes'|'no''no'Enable autoplay
data-autoplay-timeoutnumber5000Autoplay delay in milliseconds (min: 1000)
data-auto-height'yes'|'no''no'Auto-adjust height per slide
data-center'yes'|'no''no'Center active slide
data-directionstring'horizontal'Slide direction (horizontal or vertical)
data-dots'yes'|'no''no'Enable pagination dots
data-dots-dynamicnumber0Dynamic bullets count (requires dots)
data-effectstring'slide'Transition effect (slide, fade, cube, coverflow, flip, creative, cards)
data-free-mode'yes'|'no''no'Enable free mode (momentum scrolling)
data-initial-slidenumber0Start slide index
data-loop'yes'|'no''no'Enable infinite loop
data-parallax'yes'|'no''no'Enable parallax effect
data-slides-per-viewnumber1Number of slides visible
data-space-betweennumber10Gap between slides (pixels)
data-speednumber1000Transition speed (milliseconds)
data-scrollbar'yes'|'no''no'Enable scrollbar navigation
data-thumbnailsnumber0Enable thumbnails with specified count

Advanced configuration:

  • data-breakpoints → JSON string with responsive breakpoints
  • data-args → JSON string to override any Swiper params
  • data-thumbs-slider-params → JSON config for thumbnail slider
  • data-thumbs-module-params → JSON config for Thumbs module

Creating custom sliders

Basic slider in Twig

{% set slides_html %}
    <div class="swiper-slide">
        <img src="/path/to/image1.jpg" alt="Slide 1">
    </div>
    <div class="swiper-slide">
        <img src="/path/to/image2.jpg" alt="Slide 2">
    </div>
    <div class="swiper-slide">
        <img src="/path/to/image3.jpg" alt="Slide 3">
    </div>
{% endset %}

{% include 'components/slider.twig' with {
    slides_html: slides_html,
    params: {
        type: 'default',
        arrows: 'yes',
        dots: 'yes',
        loop: 'yes',
        autoplay: 'yes',
        'autoplay-timeout': '3000'
    }
} %}
Twig

Multi-slide carousel

{% include 'components/slider.twig' with {
    slides_html: slides_html,
    params: {
        'slides-per-view': '3',
        'space-between': '20',
        arrows: 'yes',
        dots: 'yes',
        breakpoints: {
            '320': { 'slidesPerView': 1 },
            '768': { 'slidesPerView': 2 },
            '1024': { 'slidesPerView': 3 }
        }|json_encode
    },
    slider_class_names: 'c-product-carousel'
} %}
Twig

Slider with thumbnails

{% set slides_html %}
    {% for image in images %}
        <div class="swiper-slide" data-thumbnail-url="{{ image.src('medium') }}">
            {{ image.responsive() }}
        </div>
    {% endfor %}
{% endset %}

{% include 'components/slider.twig' with {
    slides_html: slides_html,
    params: {
        arrows: 'yes',
        thumbnails: '5',
        loop: 'yes'
    }
} %}
Twig

Pure JavaScript slider

<div class="swiper-container js-slider-container">
    <div class="swiper js-slider c-slider"
         data-arrows="yes"
         data-dots="yes"
         data-loop="yes"
         data-autoplay="yes"
         data-autoplay-timeout="4000">
        <div class="swiper-wrapper">
            <div class="swiper-slide">Slide 1</div>
            <div class="swiper-slide">Slide 2</div>
            <div class="swiper-slide">Slide 3</div>
        </div>
    </div>
</div>
Twig

The global slider module will automatically initialize this markup.

Custom slider type

Create type-specific params:

The Slider class checks for type-specific methods using the pattern {type}SliderParams()

// In custom/scripts/modules/custom-slider.js
gallerySliderParams() {
    this.params = {
        ...this.params,
        slidesPerView: 1,
        spaceBetween: 0,
        effect: 'fade',
        speed: 600
    };
};
JavaScript

Then use data-type="gallery" to trigger these params.

Advanced Swiper configuration

Override with JSON:

{% set custom_args = {
    effect: 'coverflow',
    coverflowEffect: {
        rotate: 50,
        stretch: 0,
        depth: 100,
        modifier: 1,
        slideShadows: true
    }
}|json_encode %}

{% include 'components/slider.twig' with {
    slides_html: slides_html,
    params: {
        args: custom_args
    }
} %}
Twig

Accessibility features

Automatic ARIA announcements:

The module creates an aria-live region that announces slide changes to screen readers:

initAccessibility() {
    this.elements.liveRegion = this.createLiveRegion();
    
    mainSwiper.on('slideChange', () => {
        const currentSlide = mainSwiper.realIndex + 1;
        const totalSlides = mainSwiper.slides.length;
        this.elements.liveRegion.textContent = __('Slide %1$d of %2$d', 'chisel')
            .replace('%1$d', currentSlide)
            .replace('%2$d', totalSlides);
    });
}
JavaScript

Navigation buttons:

Arrow buttons include proper ARIA labels:

const navigationButtonPrev = Object.assign(document.createElement('button'), {
    type: 'button',
    className: 'swiper-button swiper-button-prev',
    ariaLabel: __('Previous slide', 'chisel')
});
JavaScript

Creating custom components

When creating custom components or overriding the core ones, you can modify or add components directly in (core) views/ folder, but if you want full sepeeration between core and custom you can use custom/view/ folder for that

Override core component (via custom folder)

File: custom/views/components/header.twig

<header class="c-header c-header--custom">
    <div class="c-header__inner">
        {{ logo }}
        {% include 'components/main-nav.twig' %}
        {# Custom header elements #}
    </div>
</header>
Twig

New component

File: views/components/cta-banner.twig or custom/views/components/cta-banner.twig

<div class="c-cta-banner">
    <h2 class="c-cta-banner__title">{{ title }}</h2>
    <p class="c-cta-banner__text">{{ text }}</p>
    <a href="{{ link }}" class="{{ bem('c-btn', 'primary') }}">{{ button_text }}</a>
</div>
Twig

Usage:

{% include 'components/cta-banner.twig' with {
    title: __('Get Started Today', 'chisel'),
    text: __('Join thousands of satisfied customers.', 'chisel'),
    link: '/contact',
    button_text: __('Contact Us', 'chisel')
} %}
Twig

Extend existing component

File: views/components/post-item-featured.twig or custom/views/components/post-item-featured.twig

{% extends 'components/post-item.twig' %}
{# or if via custom #}
{% extends 'views/components/post-item.twig' %}

{% block item_image %}
    {% if post.get_thumbnail() %}
        <div class="c-post-item__image c-post-item__image--featured">
            <a href="{{ post.link }}" tabindex="-1">
                {{ post.get_thumbnail('large') }}
            </a>
            <span class="c-badge c-badge--featured">{{ __('Featured', 'chisel') }}</span>
        </div>
    {% endif %}
{% endblock %}
Twig

Best practices

  • Use parameters: Make components flexible with with parameter passing
  • Use blocks: Enable customization via {% block %} tags
  • Follow BEM: Use bem() function for consistent class naming
  • Translate strings: Wrap all user-facing text in __() function
  • Cache results: Helper methods automatically cache; avoid redundant calls
  • Responsive images: Use get_responsive_image() or post.get_thumbnail() for proper srcset
  • Accessibility: Include ARIA labels, roles, and semantic HTML
  • Full separetion between core and custom: Create custom versions in custom/views/ instead of editing core files
  • Document parameters: Add comment blocks describing required/optional parameters at the top of custom components
  • Lazy load sliders: The global module handles dynamic imports automatically
  • Use data attributes: Configure sliders via HTML data attributes instead of manual JS initialization

Do you like Chisel?

Give it a star on GitHub!