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/andviews/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
| Directory | Purpose | Examples |
|---|---|---|
views/components/ | Larger UI components | Header, footer, navigation, pagination, post items |
views/objects/ | Atomic UI elements | Icons, badges, buttons |
views/partials/ | Template fragments | Reusable 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(): arrayPHPReturns all registered Chisel menus (prefixed with chisel_).
Custom logo:
Components::get_logo(): stringPHPReturns responsive HTML for the site logo from Customizer.
Sidebars:
Components::get_sidebar( string $sidebar_id = '' ): ?arrayPHPReturns sidebar data with id, name, 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(): arrayPHPReturns 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(): arrayPHPReturns current page/archive title with text and class keys. Respects ACF page_title_display field (show, hide, hide-visually)
Icons:
Components::get_icon( array $args ): stringPHPRenders 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' %}TwigStructure:
- Includes logo component via block
header_logo - Includes main navigation via block
header_nav - Uses BEM classes:
c-header,c-header__inner - Wrapped in
o-wrappercontainer
Footer
File: views/components/footer.twig
Usage:
{% include 'components/footer.twig' %}TwigContext:
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' %}TwigContext:
Main navigation
File: views/components/main-nav.twig
Includes: components/main-nav-item.twig for each menu item
Usage:
{% include 'components/main-nav.twig' %}TwigContext:
Pagination
File: views/components/pagination.twig
Usage:
{% include 'components/pagination.twig' with { posts: posts } %}TwigParameters:
posts→ Timber post collection with pagination datatype→'load-more'for AJAX load more button, omit for standard paginationload_more→ Object withpost_typeandper_pagefor load more functionality
Standard pagination:
{% include 'components/pagination.twig' with { posts: posts } %}TwigLoad more:
{% include 'components/pagination.twig' with {
posts: posts,
type: 'load-more',
load_more: {
post_type: 'post',
per_page: 10
}
} %}Twig- 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 %}TwigParameters:
post→ Timber post object
Blocks available for extension:
item_content→ Entire content wrapperitem_title→ Post title and linkitem_category→ Category badgeitem_meta→ Author and dateitem_excerpt→ Post excerptitem_tags→ Tag linksitem_image→ Featured image
Search form
File: views/components/search-form.twig
Usage:
{% include 'components/search-form.twig' %}TwigParameters:
label→ Search field label (default:'Search for:')placeholder→ Input placeholder (default:'Search...')post_type→ Limit search to specific post typesearch_query→ Pre-fill search query (fromsite.search_query)
Example with post type:
{% include 'components/search-form.twig' with {
placeholder: __('Search products...', 'chisel'),
post_type: 'product'
} %}TwigPage title
File: views/components/the-title.twig
Usage:
{% include 'components/the-title.twig' %}TwigContext:
the_title→ Title data from global context- Retrieved via
Components::get_the_title() - Respects ACF
page_title_displayfield
No results
File: views/components/no-results.twig
Usage:
{% if posts|length == 0 %}
{% include 'components/no-results.twig' %}
{% endif %}TwigDisplays 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'
}
} %}Twigslides_html→ HTML string containing slides (each must haveswiper-slideclass)params→ Configuration object processed byslider_prepare_params()functionslider_container_class_names→ Optional classes for container wrapperslider_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' }) }}TwigUsage via direct include:
{% include 'objects/icon.twig' with { name: 'search' } %}Twig| Parameter | Type | Description |
|---|---|---|
name | string | Icon name (required) |
inline | boolean | Display as inline element (default: false) |
rectangle | boolean | Icon is rectangular, not square |
force_mono | boolean | Force monochrome for icons starting with color- |
alt | string | Accessible label for screen readers |
is_css | boolean | Load icon via CSS background (requires CSS config) |
color | string | CSS 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.svgsprite - Format:
#icon-{name}-view
Color modes
Monochrome icons:
- Use
mask-imageCSS property - Color controlled via
colorparameter - Class:
o-icon--mono
Color icons:
- Detected when name starts with
color- - Use
background-imageCSS property - Preserve original SVG colors
- Class:
o-icon--color
Examples
Basic icon:
{{ get_icon({ name: 'search' }) }}TwigInline icon with color:
{{ get_icon({
name: 'arrow-right',
inline: true,
color: '#ff6600'
}) }}TwigAccessible icon:
{{ get_icon({
name: 'close',
alt: __('Close menu', 'chisel')
}) }}TwigCSS 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: ();SCSSIcon 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)
| Parameter | Type | Default | Description |
|---|---|---|---|
$name | string | (required) | Icon name from settings list |
$multicolor | boolean | false | If the icon is multicolor |
$animated | boolean | false | If 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
);SCSSUsing has-icon classes
Basic syntax:
<a href="/contact" class="has-icon has-icon-arrow-right">
Contact Us
</a>HTMLPattern:
- 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>HTMLLink with icon
<a href="/search" class="has-icon has-icon-search">
Search
</a>HTMLList 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>HTMLIcon only (no text)
<button class="has-icon has-icon-close has-icon-only" aria-label="Close">
<span class="u-visually-hidden">Close</span>
</button>HTMLhas-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
- 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 opensfirstOpen(boolean, default:false) → Open first item on loadtitleTag(string, default:'h3') → Heading tag for item titles
Inner block (chisel/accordion-item):
title(string) → Item title texttitleTag(string) → Auto-injected from parent via context
Frontend behavior
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- Uses native
<details>element for accessibility - Smooth height animation (200ms linear)
- State class
is-openapplied during animation - Overflow control during transitions
.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
- Block:
b-accordion - Elements:
b-accordion__item,b-accordion__item-header,b-accordion__item-title,b-accordion__item-content - State:
is-open(applied by JS during animation) - Modifiers:
has-first-open,has-close-others(wrapper flags)
- Editor:
src/blocks/accordion/editor.scss - Frontend:
src/blocks/accordion/style.scss
Editor usage
- Insert “Accordion” block in editor
- Configure sidebar settings:
- Title tag (
h2,h3,h4, etc.) - First item open (boolean)
- Close other items (boolean)
- Title tag (
- Add “Accordion Item” inner blocks
- Each item has:
- Title field (text input)
- Content area (accepts any blocks)
Slider block (ACF)
- 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):
| Field | Type | Description |
|---|---|---|
slider_settings | Checkbox | Multi-select: arrows, dots, loop, autoplay, thumbnails |
slider_settings_dynamic_dots | True/False | Enable dynamic dots (requires dots enabled) |
slider_settings_autoplay_timeout | Number | Autoplay delay in milliseconds (requires autoplay) |
slider_settings_thumbnails_no | Number | Number of visible thumbnails (requires thumbnails) |
Rendering flow
slider.twigloops throughacf_block_slider_slides- Each slide rendered as
<div class="swiper-slide b-slider__slide">with responsive image - If thumbnails enabled, adds
data-thumbnail-urlattribute (medium size) - Composes
slides_htmlstring - Includes
views/components/slider.twigwith params
The slider_prepare_params() Twig function converts ACF settings to data attributes:
| ACF setting | Data attribute |
|---|---|
arrows checked | data-arrows="yes" |
dots checked | data-dots="yes" |
dots + dynamic_dots | data-dots-dynamic="1" |
loop checked | data-loop="yes" |
autoplay checked | data-autoplay="yes" + data-autoplay-timeout="{ms}" |
thumbnails checked | data-thumbnails="{count}" |
| Custom params | Passed through as data-{param-name} |
Styling hooks
- Container:
b-slider__inner - Component:
c-slider - JS hooks:
js-slider,js-slider-container - Slides:
b-slider__slide swiper-slide - Type modifier:
c-slider--{type}(from params)
Editor usage
- Insert “Slider” (ACF) block
- Add slides (upload images in repeater)
- 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();JavaScriptHow 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);
});
});JavaScriptLazy 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'),
]);JavaScriptConfiguration via data attributes
Supported attributes:
| Attribute | Type | Default | Description |
|---|---|---|---|
data-type | string | 'default' | Slider type (triggers type-specific params) |
data-arrows | 'yes'|'no' | 'no' | Enable navigation arrows |
data-autoplay | 'yes'|'no' | 'no' | Enable autoplay |
data-autoplay-timeout | number | 5000 | Autoplay 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-direction | string | 'horizontal' | Slide direction (horizontal or vertical) |
data-dots | 'yes'|'no' | 'no' | Enable pagination dots |
data-dots-dynamic | number | 0 | Dynamic bullets count (requires dots) |
data-effect | string | 'slide' | Transition effect (slide, fade, cube, coverflow, flip, creative, cards) |
data-free-mode | 'yes'|'no' | 'no' | Enable free mode (momentum scrolling) |
data-initial-slide | number | 0 | Start slide index |
data-loop | 'yes'|'no' | 'no' | Enable infinite loop |
data-parallax | 'yes'|'no' | 'no' | Enable parallax effect |
data-slides-per-view | number | 1 | Number of slides visible |
data-space-between | number | 10 | Gap between slides (pixels) |
data-speed | number | 1000 | Transition speed (milliseconds) |
data-scrollbar | 'yes'|'no' | 'no' | Enable scrollbar navigation |
data-thumbnails | number | 0 | Enable thumbnails with specified count |
Advanced configuration:
data-breakpoints→ JSON string with responsive breakpointsdata-args→ JSON string to override any Swiper paramsdata-thumbs-slider-params→ JSON config for thumbnail sliderdata-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'
}
} %}TwigMulti-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'
} %}TwigSlider 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'
}
} %}TwigPure 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>TwigThe 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
};
};JavaScriptThen 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
}
} %}TwigAccessibility 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);
});
}JavaScriptNavigation 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')
});JavaScriptCreating 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>TwigNew 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>TwigUsage:
{% 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')
} %}TwigExtend 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 %}TwigBest practices
- Use parameters: Make components flexible with
withparameter 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()orpost.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