Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save philwolstenholme/78169f05fed6478cdb809879fd49abf2 to your computer and use it in GitHub Desktop.
Save philwolstenholme/78169f05fed6478cdb809879fd49abf2 to your computer and use it in GitHub Desktop.
Phil Wolstenholme - DrupalCon Europe 2020 - Utility-first CSS

Hello DrupalCon! 👋

Here's a big dump of code examples from my slides, plus some examples that didn't make it to the final cut.

The snippets should be organised by slide number, e.g. #46--_search.scss is the snippet of search-related SCSS shown on slide 46.

My slides

Here's a PDF of my slides (via Google Drive) for your reference, or if Zoom goes wrong!

Something I forgot to say

It's okay to not like the look of classes like u-bg-grey-25 that contain an explicit colour name. We define these colours in our Tailwind file, but we also add aliases like these and use these in the components for non-grey colours:

colors.primary = colors['purple-700'];
colors.secondary = colors['purple-900'];
colors.light = colors.white;
colors.dark = colors.black;

That way our components use classes like bg-primary or text-light and this makes sharing component code between projects super easy! (We share a lot of code between projects).

The recording of my talk

https://www.youtube.com/watch?v=MYY1teFZ_Fk

A still showing a slide from my talk with the quote 'Adding new CSS should be the exception, not the rule'

Tailwind docs and learning resources

Case studies of other people's utility-first success

More reading about utility-first

Me elsewhere on the internet

/* Taken from https://tailwindcss.com */
.chat-notification {
display: flex;
max-width: 24rem;
margin: 0 auto;
padding: 1.5rem;
border-radius: 0.5rem;
background-color: #fff;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.chat-notification-logo-wrapper {
flex-shrink: 0;
}
.chat-notification-logo {
height: 3rem;
width: 3rem;
}
.chat-notification-content {
margin-left: 1.5rem;
padding-top: 0.25rem;
}
.chat-notification-title {
color: #1a202c;
font-size: 1.25rem;
line-height: 1.25;
}
.chat-notification-message {
color: #718096;
font-size: 1rem;
line-height: 1.5;
}
<!-- Taken from https://tailwindcss.com -->
<div class="chat-notification">
<div class="chat-notification-logo-wrapper">
<img class="chat-notification-logo" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div class="chat-notification-content">
<h4 class="chat-notification-title">ChitChat</h4>
<p class="chat-notification-message">You have a new message!</p>
</div>
</div>
<!-- Taken from https://tailwindcss.com -->
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4">
<div class="flex-shrink-0">
<img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div>
<div class="text-xl font-medium text-black">ChitChat</div>
<p class="text-gray-500">You have a new message!</p>
</div>
</div>
<!-- Taken from https://tailwindcss.com -->
<div class="grid grid-cols-1 sm:grid-cols-2 sm:px-8 sm:py-12 sm:gap-x-8 md:py-16">
<div class="relative z-10 col-start-1 row-start-1 px-4 pt-40 pb-3 bg-gradient-to-t from-black sm:bg-none">
<p class="text-sm font-medium text-white sm:mb-1 sm:text-gray-500">Entire house</p>
<h2 class="text-xl font-semibold text-white sm:text-2xl sm:leading-7 sm:text-black md:text-3xl">Beach House in Collingwood</h2>
</div>
<div class="col-start-1 row-start-2 px-4 sm:pb-16">
<div class="flex items-center text-sm font-medium my-5 sm:mt-2 sm:mb-4">
<svg width="20" height="20" fill="currentColor" class="text-violet-600">
<path d="M9.05 3.691c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.372 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.539 1.118l-2.8-2.034a1 1 0 00-1.176 0l-2.8 2.034c-.783.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.363-1.118l-2.8-2.034c-.784-.57-.381-1.81.587-1.81H7.03a1 1 0 00.95-.69L9.05 3.69z" />
</svg>
<div class="ml-1">
<span class="text-black">4.94</span>
<span class="sm:hidden md:inline">(128)</span>
</div>
<div class="text-base font-normal mx-2">·</div>
<div>Collingwood, Ontario</div>
</div>
<hr class="w-16 border-gray-300 hidden sm:block">
</div>
<div class="col-start-1 row-start-3 space-y-3 px-4">
<p class="flex items-center text-black text-sm font-medium">
<img src="/kevin-francis.jpg" alt="" class="w-6 h-6 rounded-full mr-2 bg-gray-100">
Hosted by Kevin Francis
</p>
<button type="button" class="bg-violet-100 text-violet-700 text-base font-semibold px-6 py-2 rounded-lg">Check availability</button>
</div>
<div class="col-start-1 row-start-1 flex sm:col-start-2 sm:row-span-3">
<div class="w-full grid grid-cols-3 grid-rows-2 gap-2">
<div class="relative col-span-3 row-span-2 md:col-span-2">
<img src="/beach-house.jpg" alt="" class="absolute inset-0 w-full h-full object-cover bg-gray-100 sm:rounded-lg" />
</div>
<div class="relative hidden md:block">
<img src="/beach-house-interior.jpg" alt="" class="absolute inset-0 w-full h-full object-cover rounded-lg bg-gray-100" />
</div>
<div class="relative hidden md:block">
<img src="/beach-house-view.jpg" alt="" class="absolute inset-0 w-full h-full object-cover rounded-lg bg-gray-100" />
</div>
</div>
</div>
</div>
{% embed "@molecules/accordion-item/accordion-item.twig" with {
accordion_item_id: 'ai-' ~ paragraph.id() ~ '-' ~ paragraph.index,
accordion_title: content.field_p_ai_heading,
heading_level: 3,
is_first: paragraph.is_first,
} %}
{% block content %}
{{ content.field_p_ai_components }}
{% endblock %}
{% endembed %}
{% set attributes = attributes
.addClass([
'accordion-item',
'u-block',
'u-bg-grey-25',
is_first ? 'is-active',
])
%}
{% set link_attributes = create_attribute()
.addClass([
'group',
'accordion-title',
'u-flex',
'u-bg-grey-25',
'u-text-large',
'u-py-4',
'u-mx-6',
'u-no-underline',
'u-transition',
'u-select-none',
get_audience() == 'residential' ? 'u-text-black' : 'u-text-primary',
get_audience() == 'residential' ? 'hocus:u-text-grey-400' : 'hocus:u-text-secondary',
])
.setAttribute('href', '#' ~ accordion_item_id)
%}
<li data-accordion-item {{ attributes }}>
<a {{ link_attributes }}>
{{ title_prefix }}
{% embed "@atoms/02-text/00-headings/_heading.twig" with {
"heading_level": heading_level|default(3),
omit_base_class: true,
attributes: create_attribute().addClass(['u-flex', 'u-font-interstate', 'u-font-light', 'u-text-mid', 'u-mb-0', 'u-gap-x-2 xl:u-gap-x-3']),
} %}
{% block heading_content %}
<span class="accordion-title--rotate">
{% include "@atoms/04-images/icons/_icon.twig" with {
icon_name: expand_icon|default('feather--chevron-down'),
icon_attributes: create_attribute().addClass(['u-scale-140', 'u-font-light']),
} %}
</span>
{{ accordion_title }}
{% endblock %}
{% endembed %}
{{ title_suffix }}
</a>
<div class="accordion-content u-items-start u-p-0" data-tab-content id="{{ accordion_item_id }}">
<div class="md:u-flex u-py-3 u-mx-6 u-bg-grey-25 u-border-t-1 u-border-grey-50--dark">
<div class="md:u-flex-1 u-o-3 u-text-grey-400">
{% block content %}
{{ accordion_content }}
{% endblock %}
</div>
</div>
</div>
</li>
$--spacing-1: 8px;
$--spacing-2: 16px;
$--spacing-3: 24px;
.field--text-long {
* + * {
margin-top: $--spacing-3;
}
.footer-legal-text & {
* + * {
margin-top: $--spacing-1;
}
}
.accordion-item &,
.tab-item & {
* + * {
margin-top: $--spacing-2;
}
}
}
.field--text-long * + * {
margin-top: 24px;
}
.footer-legal-text .field--text-long * + * {
margin-top: 8px;
}
.accordion-item .field--text-long * + *,
.tab-item .field--text-long * + * {
margin-top: 16px;
}
{# This could be done in a preprocess rather than in a Twig file, but that wouldn't be as easy to show on one slide! #}
{% set parent_entity = element['#object'] %}
{% set host_of_rich_text_paragraph = parent_entity.getParentEntity.getParagraphType.id %}
{% if parent_entity.bundle() == 'footer_legal_text' %}
{% set spacing_level = 1 %}
{% elseif host_of_rich_text_paragraph == 'accordion_item' %}
{% set spacing_level = 2 %}
{% elseif host_of_rich_text_paragraph == 'tab_item' %}
{% set spacing_level = 2 %}
{% endif %}
{# The spacing_level variable will be available in the file we include below #}
{% include "@atoms/_field-wysiwyg.twig" %}
{% set attributes = attributes|default(create_attribute()) %}
{% set spacing_levels = {
1: 'u-o-1',
2: 'u-o-2',
3: 'u-o-3',
} %}
{% set spacing_level_utility = attribute(spacing_levels, spacing_level|default(3)) %}
{% set attributes = attributes.addClass([
'text-long',
'u-max-w-measure-lg',
'u-leading-normal',
spacing_level_utility
]) %}
.site-search {
mark {
color: inherit;
background-color: inherit;
font-weight: bold;
}
}
{# Don't use concatenation… #}
{% set spacing_level_utility = 'u-o-' ~ spacing_level %}
{# …use a lookup table instead! #}
{% set spacing_levels = {
1: 'u-o-1',
2: 'u-o-2',
3: 'u-o-3',
} %}
{% set spacing_level_utility = attribute(spacing_levels, spacing_level|default(3)) %}
{% include "@molecules/information-block/information-block.twig" with {
is_reversed: paragraph.index is odd,
heading: content.field_p_ct_heading|field_value,
content: content.field_p_ct_summary_rich_text,
button_url: content.field_p_ct_cta|field_value|first['#url']|render,
button_content: content.field_p_ct_cta|field_value|first['#title'],
} %}
<?php
/**
* Implements hook_preprocess_field().
*/
function theme_name_preprocess_field(&$variables) {
if (
$variables['field_type'] == 'entity_reference_revisions'
&&
$variables['element']['#items']->getItemDefinition()->getSetting('target_type') == 'paragraph'
) {
foreach ($variables['items'] as $index => $item) {
$variables['items'][$index]['content']['#paragraph']->index = $index;
}
}
}
@opdavies
Copy link

opdavies commented Dec 9, 2020

For Drupal, there's a starter kit theme that I maintain that people can fork in their projects and use as a starting point for their custom themes - https://www.drupal.org/project/tailwindcss.

There are currently releases for D7, D8 and D9.

@philwolstenholme
Copy link
Author

philwolstenholme commented Dec 9, 2020

Something I forgot to say, it's okay to not like the look of classes like u-bg-grey-25. We define these colours in our Tailwind file, but we also add aliases like these and use these in the components for non-grey colours:

colors.primary = colors['purple-700'];
colors.secondary = colors['purple-900'];
colors.light = colors.white;
colors.dark = colors.black;

That way our components use classes like bg-primary or text-light and this makes sharing component code between projects super easy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment