# Components & Modules
# Class Patterns
You must follow a few coding guidelines depending on whether your component is a Top-Level component or a reusable/nestable one.
# Top-Level Components
For modules and top-level components that won't be nested inside other components:
- ✅ Pick a unique top-level class name for the component for e.g.
hero,collage,complexlist,timeline, etc. - ❌ Don't use hyphens
-in the top level classes, if you need a separator use underscores_instead - ✅ Inside the component's markup, use "free classes" like
title,copywrap, etc - ✅ When selecting elements for styling, keep the nesting level to maximum of (3) nested classes, preferably only 2. Media queries, pseudo-classes and pseudo-elements doesn't count towards this limit.
- ❌ Don't try to mimic the HTML structure in SCSS, its rigid and only makes the nesting situation worse
- ✅ Use
-mycustom-variantat the component root for component-wide variant styles that affect multiple elements
Example:
// a top level unique name
.hero {
// free...
.wrap {
}
// classes...
.slider {
}
// everywhere.
.copy-content {
}
// override styles for nested components, in this case a CTA
.cta {
&:hover {
}
}
// max nesting of 3 classes
.super .deep {
&::after {
@media (min-width: 600px) {
// this is fine
}
}
}
&-light-variant {
.overlay {
// overlay overrides for light variant
}
.copy {
// copy overrides for light variant
}
}
}
# Nestable Components (A.K.A. Widgets or Shared components)
Components that are meant to be reused in many areas, such as:
- CTAs
- Text styles
- Booking/Reservation Widget
- Forms
- Form inputs
- UI Widgets: Comboboxes, Sliders, Accordions, Tabs
Must use an adapted BEM (opens new window)-like naming scheme and rules:
- ✅ Pick a unique top-level class name for the component for e.g.
cta,tabs,booking_widget, etc. - ❌ Don't use hyphens
-in the top level classes, if you need a separator use underscores_instead - ✅ All classes used in the markup must be unique
- ✅ Use hyphens
-for sub elements and variants - ✅ Use SCSS
&-sub-elementsyntax for easier coding
Example:
<button class="cta cta-style-a"></button>
<div class="tabs">
<div class="tabs-header"></div>
<div class="tabs-body">
<div class="tabs-panel"></div>
<div class="tabs-panel"></div>
</div>
</div>
.cta {
&-style-a {
}
&-style-b {
}
}
.tabs {
&-body {
}
&-panel {
}
&-header {
// even though it looks nested, the CSS output of this is: .tabs-header.is-selected
&.is-selected {
}
}
}
# State classes
Whenever you have different component states that depend on user ACF settings or that are activated at runtime via JavaScript, use state classes to facilitate communication and avoid CSS clashes:
.custom_select {
.custom_select-item {
&.is-selected { // is-selected, is-empty, is-open, is-closed etc
}
}
}
.mycomponent {
&.has-items { // .has-thing, .has-mobile-version, .has-extended-description etc
}
}
<div class="mycomponent {{ !empty($items) ? 'has-items' : '' }}">
@foreach($items as $item) ...
</div>
# Reusable Components
# CTAs & Actions
Call-to-Actions are implemented in a way that allows the developer to focus on the design and placement of the CTA's and let the user define which action she wants to trigger when the CTA is clicked/pressed.
- Use the
Component: CTAsACF Field Group and thepartials.ctasBlade Template in places where you need one or two CTAs, or theComponent: Single CTA/partials.ctafor exactly one CTA - Use
Component: Actionsto add or remove actions, you will need to update thepartials.ctablade template and add logic to support this new action
Add you CTA styles in theme/assets/scss/partials/ctas.scss.
# Image and Video
There are two included components for handling media:
Component: Image- used for displaying images only, it allows CMS authors to do art direction by selecting a different image for mobile devicesComponent: Media- extendsComponent: Imageto support video formats (HLS, Vimeo, YouTUbe)
Both are rendered using the partials.media blade template, so you can interchangeably use both fields for the most
part.
Choosing between the two:
For scenarios where media is not going to be rendered as part of an interactive widget (e.g. carousels, tabs, accordions, etc), and where there is enough space for a Video to make sense (e.g. not a logo) use "Component: Media".
Otherwise, default to use "Component: Image" unless the requirements say otherwise.
In some scenarios you might want to use a vanilla Image field from ACF, these are only intended for cases where you are certain a mobile image will never be provided, for example, Instagram galleries.
# Media Inside Interactive Widgets
Sometimes the requirements will ask to support videos on interactive areas, more commonly in carousels or the Hero. When this is the case you must ensure that Videos are well-supported in the context of the interactive widget, for example, videos must
- fully play before changing slides on an autorotating carousel
- not start unless their slide is activated
- pause if the user manually changes the slide. Test both autoplay and non-autoplay videos as they behave differently
# Galleries
Media Galleries such as those used for Collage modules should also use Component: Media or Component: Image as described above. The same rules apply: if the gallery has any interactivity that might conflict with vide content, use Component: Image in a Repeater field by default, unless someone asked for video support.
# Styling & CSS API
See theme/views/partials/media.blade.php and theme/assets/scss/partials/media.scss for information of the classes
that are applied by default. You should create styles in such a way that allows for both Videos and Images without too
much hassle. Follow these guidelines:
- To apply styles to all media regardless of type, use
.swmedia - To target only images in both desktop and mobile use
.swmedia-img - To target only the user-defined mobile images use
.swmedia-sm. Note that this should be rarely used if ever, since its possible for the desktop version to render on mobile if the user didn't select a mobile image.- It is best to just use media queries and target
.swmedia-img, for e.g.:
.swmedia-img { // mobile styles @include media-breakpoint-up($swmedia-lg-brekpoint) { // desktop styles } } - It is best to just use media queries and target
- To target only videos, use
.swmedia-video. Note that this won't target a video tag, but adivcontaining a video tag, or an iframe (if using YouTube/Vimeo embeds). Use.swmedia-video-nativeto only select the container div of videos using the native tag. - To apply
object-fitand/orobject-positionuse.swmedia-object. This targets the underlyingimgorvideotag (if present), for .e.g:The outer container is "needed" for specificity if you want to target a single media in a scenario where you have many of them, DON'T do.some-outer-container .swmedia-object { object-fit: contain; }.mycustommedia.swmedia-objectsince depending on if a Video, or an Image is rendered theswmedia-objectclass will be at the root or nested inside supporting marking. - You can use pass a custom class to
partials.media, which allows you to apply different styles to multiple images easily:.mycustommedia { // applies to mycustommedia on any media type } .mycustommedia.swmedia-img { // applies to mycustommedia only when its an image } .my_other_custommedia.swmedia-img { // applies to my_other_custommedia only when its an image } .mycustommedia.swmedia-video { // applies to mycustommedia only when it a video } .mycustommedia-container .swmedia-object { // applies to the media object (video or img tag) }
# Forms & Validation
There are a few considerations when you are creating a form:
- ✅ Wrap the form components inside
<form class="needs-validation" novalidate></form>tags. Make sure to add aneeds-validationclass and anovalidateattribute. The novalidate attribute help to avoid the validation on submission disabling the browser default feedback tooltips. - ✅ Associate label (opens new window) correctly with the input via
forandidto reap multiple benefits.
<label for="first-name" class="form-label">First name</label>
<input required
type="text"
value="Mark"
class="form-control"
id="first-name">
- ✅ Use concatenation of the module id with the input/label id to make it uniq. e.g:
<label for="{{ $id }}-name" class="form-label">First name</label>
<input required
type="text"
value="Mark"
class="form-control"
id="{{ $id }}-name">
- ✅ Add correct type depending on the data of the field
text,password,email, etc. - ✅ Use
autocompleteattribute taking in count the requirements, sometime it will necessary to useofffor example in a "confirm new password" but other you will want to autofill inputs like address. More info (opens new window) - ✅ Add needed HTML5 validation attributes such as
required,maxlength, etc. - ✅ For input validation add
data-validatio-validordata-validation-invalidto show feedback to the user when is valid or not, for e.g:
<label for="{{ $id }}-validation-first-name" class="form-label">First name</label>
<input required
maxlength=""
type="text"
value="Mark"
class="form-control"
id="{{ $id }}-validation-first-name"
data-validation-valid="Valid. Looks good!"
data-validation-invalid="Please choose a valid first name.">
- ❌ Avoid adding a click event handler to the submit button.
<button class="btn btn-primary" onClick="submit()">Submit form</button>
- ✅ Use a type submit button and init form validator e.g:
<form class="needs-validation" novalidate>
... inputs
<button class="btn btn-primary" type="submit">Submit form</button>
</form>
import formValidator from './shared/formValidator'
const formEl = document.querySelector('.needs-validation')
formValidator(formEl, () => { console.log('Make something when valid') }, () => { console.log('Make something when invalid') })
The formValidator comes from a script that helps to validate the form is an easy way. It is using part of the bootstrap
logic(how it works (opens new window)) combined with a custom handler to
avoid a11y issues. Where the goal is checking if the field is invalid, it appends an element and we associate this new element
with the invalid field, letting the user know the reason of the invalidation(helped by the data-validation-invalid attribute and
aria-describedby of the field).
You can take a look on file theme/assets/js/shared/formValidator.js which contains the init function and handler of individual
form inputs.
# Modals
Use Bootstrap modals (opens new window) for all Modals.
# Carousel
Use swiper (opens new window) for all Carousels.
← Accessibility SCSS →