Šime posts regular content for web developers on webplatform.news.
New Feature Policy API in Chrome
Pete LePage: You can use the document.featurePolicy.allowedFeatures method in Chrome to get a list of all Feature Policy-controlled features that are allowed on the current page.
This API can be useful when implementing a feature policy (and updating an existing feature policy) on your website.
Open your site in Chrome and run the API in the JavaScript console to check which Feature Policy-controlled features are allowed on your site.
Read about individual features on featurepolicy.info and decide which features should be disabled ('none' value), and which features should be disabled only in cross-origin <iframe> elements ('self' value).
Add the Feature-Policy header to your site’s HTTP responses (policies are separated by semicolons).
Repeat Step 1 to confirm that your new feature policy is in effect. You can also scan your site on securityheaders.com.
In other news…
Dave Camp: Firefox now blocks cookies from known trackers by default (when the cookie is used in a third-party context). This change is currently in effect only for new Firefox users; existing users will be automatically updated to the new policy „in the coming months.”
Pete LePage: Chrome for Android now allows websites to share images (and other file types) via the navigator.share method. See Web Platform News Issue 1014 for more information about the Web Share API. Ayooluwa Isaiah’s post from yesterday is also a good reference on how to use it.
Valerie Young: The ECMAScript Internationalization APIs for date and time formatting (Intl.DateTimeFormat constructor), and number formatting (Intl.NumberFormat constructor) are widely supported in browsers.
Alan Jeffrey: Patrick Walton from Mozilla is working on a vector graphics renderer that can render text smoothly at all angles when viewed with an Augmented Reality (AR) headset. We plan to use it in our browsers for AR headsets (Firefox Reality).
Pinterest Engineering: Our progressive web app is now available as a standalone desktop application on Windows 10. It can be installed via the Microsoft Store, which „treats the packaged PWA as a first class citizen with access to Windows 10 feature APIs.”
Jonathan Davis: The flow-root value for the CSS display property has landed in Safari Technology Preview. This value is already supported in Chrome and Firefox. See Web Platform News Issue 871 for a use case.
Ire Aderinokun writes about a new way to set a performance budget (and stick to it) with Lighthouse, Google’s suite of tools that help developers see how performant and accessible their websites are:
Until recently, I also hadn’t setup an official performance budget and enforced it. This isn’t to say that I never did performance audits. I frequently use tools like PageSpeed Insights and take the feedback to make improvements. But what I had never done was set a list of metrics that I needed the site to meet, and enforce them using some automated tool.
The reasons for this were a combination of not knowing what exact numbers I should use for budgets as well as there being a disconnect between setting a budget and testing/enforcing it. This is why I was really excited when, at Google I/O this year, the Lighthouse team announced support for performance budgets that can be integrated with Lighthouse. We can now define a simple performance budget in a JSON file, which will be tested as part of the lighthouse audit!
I completely agree with Ire, and much in the same way I’ve tended to neglect sticking to a performance budget simply because the process of testing was so manual and tedious. But no more! As Ire shows in this post, you can even set Lighthouse up to test your budget with every PR in GitHub. That tool is called lighthousebot and it’s just what I’ve been looking for – an automated and predictable way to integrate a performance budget into every change that I make to a codebase.
Today lighthousebot will comment on your PR after a test is complete and it will show you the before and after score:
How neat is that? This reminds me of Gareth Clubb’s recent post about improving web performance and building a culture around budgets in an organization. What better way to remind everyone about performance than right in GitHub after each and every change that they make?
Say you have an image you’re using in an <img> that is 800×600 pixels. Will it actually display as 800px wide on your site? It’s very likely that it will not. We tend to put images into flexible container elements, and the image inside is set to width: 100%;. So perhaps that image ends up as 721px wide, or 381px wide, or whatever. What doesn’t change is that image’s aspect ratio, lest you squish it awkwardly (ignoring the special use-case object-fit scenario).
The main point is that we don’t know how much vertical space an image is going to occupy until that image loads. This is the cause of jank! Terrible jank! It’s everywhere and it’s awful.
There are ways to create aspect-ratio sized boxes in HTML/CSS today. None of the options are particularly elegant because they rely on the „hack” of setting a zero height and pushing the boxes height with padding. Wouldn’t it be nicer to have a platform feature to help us here? The first crack at fixing this problem that I know about is an intrinsicsize attribute. Eric Portis wrote about how this works wonderfully in Jank-Free Image Loads.
We’d get this:
<img src="image.jpg" intrinsicsize="800x600" />
This is currently behind a flag in Chrome 71+, and it really does help solve this problem.
But…
The intrinsicsize property is brand new. It will only help on sites where the developers know about it and take the care to implement it. And it’s tricky! Images tend to be sized arbitrarily, and the intrinsicsize attribute will need to be custom on every image to be accurate and do its job. That is, if it makes it out of standards at all.
There is another possibility! Eric also talked about the aspect-ratio property in CSS as a potential solution. It’s also still just a draft spec. You might say, but how is this helpful? It needs to be just as bespoke as intrinsicsize does, meaning you’d have to do it as inline styles to be helpful. Maybe that’s not so bad if it solves a big problem, but inline styles are such a pain to override and it seems like the HTML attribute approach is more inline with the spirit of images. Think of how srcset is a hint to browsers for what images are available to download, allowing the browser to pick the best one for the scenario. Telling the browser about the aspect-ratio upfront is similarly useful.
I heard from Jen Simmons about an absolutely fantastic way to handle this: put a default aspect ratio into the UA stylesheet based on the elements existing width and height attributes. Like this:
img, video {
aspect-ratio: attr(width) / attr(height);
}
Let that soak in.
Now if you already have:
<img src="image.jpg" width="800" height="600" />
It automatically has the correct aspect ratio as the page loads. That’s awesome.
It’s easy to understand.
A ton of the internet already has these attributes sitting on their images.
New developers will have no trouble understanding this, and old developers will be grateful there is little (if any) work to do here.
I like the idea of the CSS feature. But I like 100 times more the idea of putting it into the UA</abbr? stylesheet so that the entire web benefits. I'm sure that changing a UA stylesheet is no small thing to consider — and I’m not qualified to understand all the implications of that — but it feels like a very awesome thing at first consideration.
This post covers how I built a typography grid for a design system using Vue render functions. Here’s the demo and the code. I used render functions because they allow you to create HTML with a greater level of control than regular Vue templates, yet surprisingly I couldn’t find very much when I web searched around for real-life, non-tutorial applications of them. I’m hoping this post will fill that void and provide a helpful and practical use case on using Vue render functions.
I’ve always found render functions to be a little out-of-character for Vue. While the rest of the framework emphasizes simplicity and separation of concerns, render functions are a strange and often difficult-to-read mix of HTML and JavaScript.
I suspect that this syntax turns some people off, since ease-of-use is a key reason to reach for Vue in the first place. This is a shame because render functions and functional components are capable of some pretty cool, powerful stuff. In the spirit of demonstrating their value, here’s how they solved an actual business problem for me.
Quick disclaimer: It will be super helpful to have the demo open in another tab to reference throughout this post.
Defining criteria for a design system
My team wanted to include a page in our VuePress-powered design system showcasing different typography options. This is part of a mockup that I got from our designer.
And here’s a sample of some of the corresponding CSS:
Headings are targeted with tag names. Other items use class names, and there are separate classes for weight and size.
Before writing any code, I created some ground rules:
Since this is really a data visualization, the data should be stored in a separate file.
Headings should use semantic heading tags (e.g. <h1>, <h2>, etc.) instead of having to rely on a class.
Body content should use paragraph (<p>) tags with the class name (e.g. <p class="body-text--lg">).
Content types that have variations should be grouped together by wrapping them in the root paragraph tag, or corresponding root element, without a styling class. Children should be wrapped with <span> and the class name.
Class names should only need to be written once for each cell that’s demonstrating styling.
Why render functions make sense
I considered a few options before starting:
Hardcoding
I like hardcoding when appropriate, but writing my HTML by hand would have meant typing out different combinations of the markup, which seemed unpleasant and repetitive. It also meant that data couldn’t be kept in a separate file, so I ruled out this approach.
Here’s what I mean:
<div class="row">
<h1>Heading 1</h1>
<p class="body-text body-text--md body-text--semibold">h1</p>
<p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>
<p class="group body-text body-text--md body-text--semibold">
<span>Product title (once on a page)</span>
<span>Illustration headline</span>
</p>
</div>
Using a traditional Vue template
This would normally be the go-to option. However, consider the following:
– An <h1>> tag rendered as-is.
– A <p> tag that groups some <span> children with text, each with a class (but no special class on the <p> tag).
– A <p> tag with a class and no children.
The result would have meant many instances of v-if and v-if-else, which I knew would get confusing fast. I also disliked all of that conditional logic inside the markup.
Because of these reasons, I chose render functions. Render functions use JavaScript to conditionally create child nodes based on all of the criteria that’s been defined, which seemed perfect for this situation.
Data model
As I mentioned earlier, I’d like to keep typography data in a separate JSON file so I can easily make changes later without touching markup. Here’s the raw data.
Each object in the file represents a different row.
{
"text": "Heading 1",
"element": "h1", // Root wrapping element.
"properties": "Balboa Light, 30px", // Third column text.
"usage": ["Product title (once on a page)", "Illustration headline"] // Fourth column text. Each item is a child node.
}
The object above renders the following HTML:
<div class="row">
<h1>Heading 1</h1>
<p class="body-text body-text--md body-text--semibold">h1</p>
<p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>
<p class="group body-text body-text--md body-text--semibold">
<span>Product title (once on a page)</span>
<span>Illustration headline</span>
</p>
</div>
Let’s look at a more involved example. Arrays represent groups of children. A classes object can store classes. The base property contains classes that are common to every node in the cell grouping. Each class in variants is applied to a different item in the grouping.
{
"text": "Body Text - Large",
"element": "p",
"classes": {
"base": "body-text body-text--lg", // Applied to every child node
"variants": ["body-text--bold", "body-text--regular"] // Looped through, one class applied to each example. Each item in the array is its own node.
},
"properties": "Proxima Nova Bold and Regular, 20px",
"usage": ["Large button title", "Form label", "Large modal text"]
}
We have a parent component, TypographyTable.vue, which contains the markup for the wrapper table element, and a child component, TypographyRow.vue, which creates a row and contains our render function.
I loop through the row component, passing the row data as props.
<template>
<section>
<!-- Headers hardcoded for simplicity -->
<div class="row">
<p class="body-text body-text--lg-bold heading">Hierarchy</p>
<p class="body-text body-text--lg-bold heading">Element/Class</p>
<p class="body-text body-text--lg-bold heading">Properties</p>
<p class="body-text body-text--lg-bold heading">Usage</p>
</div>
<!-- Loop and pass our data as props to each row -->
<typography-row
v-for="(rowData, index) in $options.typographyData"
:key="index"
:row-data="rowData"
/>
</section>
</template>
<script>
import TypographyData from "@/data/typography.json";
import TypographyRow from "./TypographyRow";
export default {
// Our data is static so we don't need to make it reactive
typographyData: TypographyData,
name: "TypographyTable",
components: {
TypographyRow
}
};
</script>
One neat thing to point out: the typography data can be a property on the Vue instance and be accessed using $options.typographyData since it doesn’t change and doesn’t need to be reactive. (Hat tip to Anton Kosykh.)
Making a functional component
The TypographyRow component that passes data is a functional component. Functional components are stateless and instanceless, which means that they have no this and don’t have access to any Vue lifecycle methods.
The empty starting component looks like this:
// No <template>
<script>
export default {
name: "TypographyRow",
functional: true, // This property makes the component functional
props: {
rowData: { // A prop with row data
type: Object
}
},
render(createElement, { props }) {
// Markup gets rendered here
}
}
</script>
The render method takes a context argument, which has a props property that’s de-structured and used as the second argument.
The first argument is createElement, which is a function that tells Vue what nodes to create. For brevity and convention, I’ll be abbreviating createElement as h. You can read about why I do that in Sarah’s post.
h takes three arguments:
An HTML tag (e.g. div)
A data object with template attributes (e.g. { class: 'something'})
Text strings (if we’re just adding text) or child nodes built using h
render(h, { props }) {
return h("div", { class: "example-class" }, "Here's my example text")
}
OK, so to recap where we are at this point, we’ve covered creating:
a file with the data that’s going to be used in my visualization;
a regular Vue component where I’m importing the full data file; and
the beginning of a functional component that will display each row.
To create each row, the data from the JSON file needs to be passed into arguments for h. This could be done all at once, but that involves a lot of conditional logic and is confusing.
Instead, I decided to do it in two parts:
Transform the data into a predictable format.
Render the transformed data.
Transforming the common data
I wanted my data in a format that would match the arguments for h, but before doing this, I wrote out how I wanted things structured:
// One cell
{
tag: "", // HTML tag of current level
cellClass: "", // Class of current level, null if no class exists for that level
text: "", // Text to be displayed
children: [] // Children each follow this data model, empty array if no child nodes
}
Each object represents one cell, with four cells making up each row (an array).
function createRow(data) { // Pass in the full row data and construct each cell
let { text, element, classes = null, properties, usage } = data;
let row = [];
row[0] = createCellData(data) // Transform our data using some shared function
row[1] = createCellData(data)
row[2] = createCellData(data)
row[3] = createCellData(data)
return row;
}
Let’s take another look at our mockup.
The first column has styling variations, but the rest seem to follow the same pattern, so let’s start with those.
This gives us a tree-like structure for each cell since some cells have groups of children. Let’s use two functions to create the cells.
createNode takes each of our desired properties as arguments.
createCell wraps around createNode so that we can check if the text that we’re passing in is an array. If it is, we build up an array of child nodes.
// Model for each cell
function createCellData(tag, text) {
let children;
// Base classes that get applied to every root cell tag
const nodeClass = "body-text body-text--md body-text--semibold";
// If the text that we're passing in as an array, create child elements that are wrapped in spans.
if (Array.isArray(text)) {
children = text.map(child => createNode("span", null, child, children));
}
return createNode(tag, nodeClass, text, children);
}
// Model for each node
function createNode(tag, nodeClass, text, children = []) {
return {
tag: tag,
cellClass: nodeClass,
text: children.length ? null : text,
children: children
};
}
Now, we can do something like:
function createRow(data) {
let { text, element, classes = null, properties, usage } = data;
let row = [];
row[0] = ""
row[1] = createCellData("p", ?????) // Need to pass in class names as text
row[2] = createCellData("p", properties) // Third column
row[3] = createCellData("p", usage) // Fourth column
return row;
}
We pass properties and usage to the third and fourth columns as text arguments. However, the second column is a little different; there, we’re displaying the class names, which are stored in the data file like:
Additionally, remember that headings don’t have classes, so we want to show the heading tag names for those rows (e.g. h1, h2, etc.).
Let’s create some helper functions to parse this data into a format that we can use for our text argument.
// Pass in the base tag and class names as arguments
function displayClasses(element, classes) {
// If there are no classes, return the base tag (appropriate for headings)
return getClasses(classes) ? getClasses(classes) : element;
}
// Return the node class as a string (if there's one class), an array (if there are multiple classes), or null (if there are none.)
// Ex. "body-text body-text--sm" or ["body-text body-text--sm body-text--bold", "body-text body-text--sm body-text--italic"]
function getClasses(classes) {
if (classes) {
const { base, variants = null } = classes;
if (variants) {
// Concatenate each variant with the base classes
return variants.map(variant => base.concat(`${variant}`));
}
return base;
}
return classes;
}
Now we can do this:
function createRow(data) {
let { text, element, classes = null, properties, usage } = data;
let row = [];
row[0] = ""
row[1] = createCellData("p", displayClasses(element, classes)) // Second column
row[2] = createCellData("p", properties) // Third column
row[3] = createCellData("p", usage) // Fourth column
return row;
}
Transforming the demo data
This leaves the first column that demonstrates the styles. This column is different because we’re applying new tags and classes to each cell instead of using the class combination used by the rest of the columns:
Rather than try to do this in createCellData or createNodeData, let’s make another function to sit on top of these base transformation functions and handle some of the new logic.
function createDemoCellData(data) {
let children;
const classes = getClasses(data.classes);
// In cases where we're showing off multiple classes, we need to create children and apply each class to each child.
if (Array.isArray(classes)) {
children = classes.map(child =>
// We can use "data.text" since each node in a cell grouping has the same text
createNode("span", child, data.text, children)
);
}
// Handle cases where we only have one class
if (typeof classes === "string") {
return createNode("p", classes, data.text, children);
}
// Handle cases where we have no classes (ie. headings)
return createNode(data.element, null, data.text, children);
}
Now we have the row data in a normalized format that we can pass to our render function:
function createRow(data) {
let { text, element, classes = null, properties, usage } = data
let row = []
row[0] = createDemoCellData(data)
row[1] = createCellData("p", displayClasses(element, classes))
row[2] = createCellData("p", properties)
row[3] = createCellData("p", usage)
return row
}
Rendering the data
Here’s how we actually render the data to display:
// Access our data in the "props" object
const rowData = props.rowData;
// Pass it into our entry transformation function
const row = createRow(rowData);
// Create a root "div" node and handle each cell
return h("div", { class: "row" }, row.map(cell => renderCells(cell)));
// Traverse cell values
function renderCells(data) {
// Handle cells with multiple child nodes
if (data.children.length) {
return renderCell(
data.tag, // Use the base cell tag
{ // Attributes in here
class: {
group: true, // Add a class of "group" since there are multiple nodes
[data.cellClass]: data.cellClass // If the cell class isn't null, apply it to the node
}
},
// The node content
data.children.map(child => {
return renderCell(
child.tag,
{ class: child.cellClass },
child.text
);
})
);
}
// If there are no children, render the base cell
return renderCell(data.tag, { class: data.cellClass }, data.text);
}
// A wrapper function around "h" to improve readability
function renderCell(tag, classArgs, text) {
return h(tag, classArgs, text);
}
It’s worth pointing out that this approach represents an experimental way of addressing a relatively trivial problem. I’m sure many people will argue that this solution is needlessly complicated and over-engineered. I’d probably agree.
Despite the up-front cost, however, the data is now fully separated from the presentation. Now, if my design team adds or removes rows, I don’t have to dig into messy HTML — I just update a couple of properties in the JSON file.
Is it worth it? Like everything else in programming, I guess it depends. I will say that this comic strip was in the back of my mind as I worked on this:
Maybe that’s an answer. I’d love to hear all of your (constructive) thoughts and suggestions, or if you’ve tried other ways of going about a similar task.
I was once tasked to create a makeshift customer service survey that would allow an employee to receive a customer call and send a survey to the custom once the call ended. The goal was to track customer satisfaction, which is a totally legit thing to want.
There are some solutions out there that do this out of the box. The problem was that my client neither had the desire or budget to use them. They did, however already use Campaign Monitor for sending emails and Wufoo for embedded forms. I figured, hey, if MacGyver can create complex gadgets out of bubble gum and paper clips, then I can cobble something together with these two robust tools. Right?
Right!
Knowing the data
The biggest challenge for a phone call is that it’s sometimes difficult to track down an email address during the call. Without that, there’s nowhere to send the survey, so that’s a big bummer. That means the employee needs to capture the address, whether it’s getting a custom ID at the start of the call or straight up asking for it.
I decided I would spin up Wufoo to create an internal form that employees can use to capture the data. Nothing fancy, only the following fields:
Employee Name: Used to associate the employee with the call
Customer Name: Used to personalize the email sent containing the survey link
Customer Email: Used to send the survey link
Wufoo makes this trivial, of course. Create a new form, add a few text fields, copy and paste the snippet onto a page. Voilà! We have a form! We could make it a little simpler (and dummy-proof) by creating a select field containing all employee names so the data remains consistent. We can also make the fields required and use Wufoo’s built-in validation to ensure the email address is properly formatted.
There’s also a lot of flexibility with the styling to boot, meaning the form can look like a native part of the site.
The employee can now fill that out during the call and submit it once the call is done. Plus, the form is embedded on a private page that can’t accidentally get out to others.
Sending the survey
Did you know Wufoo can auto-send emails anywhere once a form is completed? Well, now you do!
This is super handy for most cases and I really wanted to use it for this task, but decided to go with Campaign Monitor because there’s one little snag which is…
Each employee needs a unique survey form
Yep, that’s a thing. Otherwise, we’d have to ask the customer to identify the employee’s name in the survey, which we can’t expect them to know. Sure, we could use another select field, but that requires the customer to recall the name as well, and not having the employee name makes it difficult to associate customer satisfaction results with specific employees for tracking.
So, the email needs to contain a unique link that goes to a form that’s associated with a specific employee. Again, there are solutions for this stuff, but we’re MacGyver’ing this thing.
I’m so glad Wufoo makes building forms so simple. All I needed to do was create one form, duplicate it for each employee, and change the employee value in each one.
On that note, we don’t necessarily need to display the employee’s name in the survey form. Wufoo accepts custom CSS which means we can hide any field! Plus, it allows us to pre-populate a field, so that gives us hidden fields that already contains the values we need to associate the survey data with a specific employee and create a webpage containing the form for that employee. 💥
Hidden fields are, well…hidden!
Now that there’s a survey for each employee, all I needed to do was create webpages where I could embed each employee form and use those as the survey links.
Here’s the flow:
Auto-sending the survey link
Let’s go back to the first form for a moment. Remember, that’s the one an employee fills out and submits once the call has ended. This should send an email to the customer containing a link to the webpage that has the survey form for that employee.
Campaign Monitor can segment lists by custom variables. In other words, we can create one list that contains all of the customers who have called in and create sub-lists that are divided by employee since we have that as a value in the internal employee form. And, since Campaign Monitor and Wufoo play nice together, all of the data we capture is contained in both places.
The other thing Campaign Monitor can do is trigger emails to send based on conditions. So, if the employee submits the internal form, Campaign Monitor can be configured to send a specific email template/campaign to a new subscriber that is added to the list. If we set up a trigger to send an email to any new customer who is added to the list, we can personalize the email to contain the link for the employee who took the call.
Watch the data roll in!
Great! We have a form employees can use to send a survey, surveys set up on webpages for each employee, and an auto-triggered email that’s sent to a customer containing the survey link for the employee who submitted the internal survey.
All that’s left is to monitor the data and — wouldn’t you know it — Wufoo has reporting built right into it where all of that can be accessed and monitored 24/7. Heck, it even permits us to create custom reporting dashboards on a per-survey basis.
Wrapping up
Was this the best way to hack things together? Probably not. Is it bulletproof from incorrect data or misuse? Not entirely. But the fact that Wufoo could intervene at all and is flexible enough to power this sort of thing is awesome. It’s more than awesome; it’s amazing. It sure wasn’t intended to be used that way, but it did the trick anyway.
Šime posts regular content for web developers on webplatform.news.
Google posts guidelines for defining favicons
Jamie Leach: Google Search now displays favicons in search results on mobile. Your favicon should be a multiple of 48×48 (Google will re-scale it to 16×16 for use in search results). If a website doesn’t have a favicon or Google deems the favicon inappropriate, a generic globe icon will be displayed instead.
Your favicon should be a visual representation of your website’s brand, in order to help users quickly identify your site when they scan through search results.
Top websites are surprisingly inconsistent in the way they declare icons (via <link> elements in the page’s head). Twitter and Pinterest, two relatively modern progressive web apps, provide icons in two sizes.
<!-- example -->
<link rel="icon" href="/icon-32x32.png">
<link rel="apple-touch-icon" href="/icon-192x192.png">
The Paciello Group releases ARC Toolkit
In honor of Global Accessibility Awareness Day, TPG is releasing our professional-level accessibility testing tool to the public. Learn all about it at https://t.co/ol33pizq2v and download it from the Google Chrome store today. #GAAD
— The Paciello Group (@paciellogroup) May 16, 2019
The Paciello Group: ARC Toolkit, our professional-level accessibility testing tool, is now available as a Chrome DevTools extension. This tool detects issues related to the WCAG 2.1 guidelines. You can run the test on the entire page or just the node selected in the DevTools Elements panel.
Remember, automated accessibility tools are only able to find some accessibility issues, and manual testing is necessary to ensure full accessibility. Lighthouse (via the Audits panel) suggests manual checks after performing an accessibility audit.
Other news
Jeff Jaffe: W3C and WHATWG have reached an agreement to collaborate on the development of HTML. „W3C shall encourage the community … to contribute directly to the WHATWG HTML and DOM repositories; raising issues, proposing solutions, commenting on proposed solutions, and indicating support or otherwise for proposals.”
Paul Calvano: „There is a significant gap in the first- vs. third-party resource age of CSS and web fonts. 95% of first-party fonts are older than one week compared to 50% of third-party fonts … This makes a strong case for self-hosting web fonts!”
Rachel Andrew: The CSS subgrid value is a relatively straightforward addition to grid layout. For example, if you have nested grids, and you apply grid-template-rows: subgrid to the child grid, then this grid will use the row tracks of the parent grid instead of creating its own row tracks. That’s all there is to it. (This feature is currently only supported in Firefox Nightly.)
GitHub Blog: GitHub can now generate automated security fixes for your dependencies with known security vulnerabilities. On GitHub’s website, check your repository’s Security tab for security alerts. If you open an alert and press the „Create automated security fix” button, GitHub will create an automated pull request that fixes the security vulnerability.
Rick Viscomi: HTTP Archive plans to release the first annual Web Almanac in November, a report of the state of the web with interesting insights written by different experts. About 50 volunteers from the web community are currently working on it, and they are looking for more contributors.
Here’s a bonafide CSS/HTML trick from Brad Frost and Dave Rupert where they use the <picture> element to switch out a GIF file with an image if the user has reduced motion enabled. This is how Brad goes about implementing that:
<picture>
<!-- This image will be loaded if the media query is true -->
<source srcset="no-motion.jpg" media="(prefers-reduced-motion: reduce)"></source>
<!-- Otherwise, load this gif -->
<img srcset="animated.gif" alt="brick wall"/>
</picture>
How nifty is this? It makes me wonder if there are other ways this image-switching technique can be used besides accessibility and responsive images…
Also, it’s worth noting that Eric Bailey wrote about the reduced motion media query a while back where he digs into its history and various approaches to use it.
You can have the best open source project in the world but, if it doesn’t have good documentation, chances are it’ll never take off. In the office, good documentation could save you having to repeatedly answer the same questions. Documentation ensures that people can figure out how things work if key employees decide to leave the company or change roles. Well documented coding guidelines help bring consistency to a codebase.
If you’re writing long-form text, Markdown is clearly a great alternative to authoring HTML. Sometimes though, Markdown syntax isn’t enough. It’s always been possible to write straight HTML inside of Markdown documents. This includes custom elements so, if you’re building a design system with native web components, it’s easy to incorporate them inside your text-based documentation. If you’re working with React (or any other framework that speaks JSX, like Preact or Vue), you can do the same thing by using MDX.
This article is a broad overview of the tools available for writing documentation and for building style guides. Not all the tools listed here make use of MDX but it’s increasingly being incorporated into documentation tooling.
What is MDX?
A .mdx file has exactly the same syntax as a regular Markdown file, but lets you import interactive JSX components and embed them within your content. Support for Vue components is in alpha. It’s easy to get MDX set up with Create React App. There are MDX plugins for Next.js and Gatsby. The forthcoming version two release of Docusaurus will also come with built-in support.
Writing documentation with Docusaurus
Docusaurus is made by Facebook and used by every Facebook open source project, apart from React. It’s also used by many major open source projects outside of Facebook, including Redux, Prettier, Gulp and Babel.
Projects making use of Docusaurus.
You can use Docusaurus to document anything — it isn’t front-end specific. Docusaurus uses React under the hood, but you don’t have to know that framework to make use of it. It’ll take your Markdown files and turn them into a nicely-structured, well-formatted and readable documentation site, with a nice design right out of the box.
The Redux site shows the typical Docusaurus layout
Sites created with Docusaurus can also include a Markdown-based blog. Prism.js is included by default for zero-setup syntax highlighting. While relatively new, Docusaurus has proven popular, being voted the number one new tool of 2018 on StackShare.
Other options for written content
Docusaurus specifically caters to building documentation. Of course, there are a million and one ways to make a website — so you could roll your own solution with any back-end language, CMS, or static site generator.
The documentation sites for React, IBM’s design system, Apollo and Ghost CMS use Gatsby, for example — a generic static site generator often used for blogs. If you work with the Vue framework, VuePress is becoming a popular option. MkDocs is an open source static site generator for creating documentation, written in Python and configured with a single YAML file. GitBook is a popular paid product that’s free for open-source and non-profit teams. If you’re building internal documentation and want something easy, the reading experience on GitHub itself isn’t half bad, so you could just commit some Markdown files and leave it at that.
Documenting components: Docz, Storybook and Styleguidist
Style guides, design systems, pattern libraries — whatever you want to call them — have become a hugely popular area of concern in the last decade. What’s really made the difference in turning them from vanity projects into useful tools isn’t the pontificating of thought leaders but the emergence of component-driven frameworks, like React, and the tools mentioned here.
Storybook, Docz and Styleguidist all do much the same thing: display interactive UI components and document their API. A project may have dozens or even hundreds of components to keep track of — all with a variety to states and styles. If you want components to be reused, people have to know that they exist. We aid discoverability when we catalog components. A style guide gives an easily searchable and scannable overview of all your UI components. This helps to maintain visual consistency and avoid duplicating work.
These tools provide a convenient way to review different states. It can be difficult to reproduce every state of a component in the context of a real application. Rather than needing to click through an actual app, developing a component in isolation can be helpful. Hard-to-reach states (like a loading state, for example) can be mocked.
Dan Green wrote a nice synopsis of the benefits of using Storybook, but it applies equally to Docz and Styleguidist:
„Storybook has made it really easy for designers who code to collaborate with engineers. By working in storybook they don’t need to get a whole environment running (docker container, etc). For Wave, we have many important components that are only visible in the middle of a process that is short lived and time consuming to reproduce (i.e. a loading screen that only shows while a user is having their payment account set up). Before Storybook, we didn’t have a good way to work on these components and were forced to temporary hacks in order to make them visible. Now, with Storybook we have an isolated place to easily work on them, which has the bonus feature of being easily accessible for designers and PMs. It also makes it really easy for us to show off these states in sprint demos.”
– Dan Green, Wave Financial
As well as visualizing different states side-by-side and listing props, its often helpful to have written content about a component — whether its explaining the design rationale, use-cases, or describing the results of user-testing. Markdown is easy enough for *anybody* to learn — ideally a style guide should be a joint resource for designers and developers that both disciplines contribute to. Docz, Styleguidist and Storybook all offer a way to seamlessly intermingle Markdown with the components themselves.
Docz
Currently, Docz is a React-only project, but is working on support for Preact, Vue and web components. Docz is the newest of the three tools, but has already amounted over 14,000+ stars on GitHub. It is, to my mind, the easiest solution to work with. Docz provides two components — <Playground> and <Props>. These are imported and used directly in .mdx files.
import { Playground, Props } from "docz";
import Button from "../src/Button";
## You can _write_ **markdown**
### You can import and use components
<Button>click</Button>
You can wrap your own React components with <Playground> to create the equivalent of an embedded CodePen or CodeSandbox — a view of your component alongside editable code.
<Playground>
<Button>click</Button>
</Playground>
<Props> will show all the available props for a given React component, default values, and whether the prop is required.
<Props of={Button} />
I personally find this MDX-based approach the simplest to understand and the easiest to work with.
Just like with Docz, examples are written using Markdown syntax. Styleguidist uses Markdown code blocks (triple backticks) in regular .md files rather than MDX:
Code blocks in Markdown usually just show the code. With Styleguidist, any code block with a language tag of js, jsx or javascript will be rendered as a React component along with the code. Just like with Docz, the code is editable — you can change props and instantly see the result.
Styleguidist will automatically create a table of props from either PropTypes, Flow or Typescript declarations.
Styleguidist currently supports React and Vue.
Storybook
Storybook markets itself as „a development environment for UI components.” Rather than writing examples of components inside Markdown or MDX files, you write *stories* inside Javascript files. A *story* documents a particular state of a component. A component might have stories for a loading state and a disabled state, for example.
Storybook is less straightforward to use than Styleguidist and Docz. At over 36,000 GitHub stars though, it’s the most popular option. It’s an open source project with 657 contributors and a full-time maintainer. It is used by, among others, Airbnb, Algolia, Atlassian, Lyft, and Salesforce. Storybook supports more frameworks than any other offering — React, React Native, Vue, Angular, Mithril, Ember, Riot, Svelte and plain HTML are all supported.
Writing documentation about components currently requires addons. In a future release, Storybook is taking inspiration from Docz and adopting MDX.
# Button
Some _notes_ about your button written with **markdown syntax**.
<Story name="disabled">
<Button disabled>lorem ipsum</Button>
</Story>
Storybook’s new Docs feature is being rolled out incrementally over the next couple of months and looks set to be a big step forward.
Do you use @storybookjs for component docs or design systems? You're gonna love DocBlocks: 📦 Drop into MDX 🏗 Modular and composable 🤝 Compatible w/ @gatsbyjs, #nextjs, etc
The benefits of pattern libraries have been extolled at nauseating length in a million Medium articles. When done well, they aid visual consistency and facilitate the creation of cohesive products. Of course, none of these tools can magic up a design system. That takes careful thought about both design and CSS. But when it comes time to communicate that system to the rest of an organization, Docz, Storybook and Styleguidist are all great options.
I work for Supercool, a fast-moving design agency that makes custom built sites for arts clients, powered by the off-the-shelf system, Craft CMS; it’s high-spec graphic design with relatively demanding typography and art direction. Over the past few months we’ve been moving to CSS grid. We’re transitioning slowly, allowing ourselves to discover new paradigms and design methods, instead of simply porting old habits to a new syntax.
So far, we’ve developed a number of really useful strategies for keeping track of the layout. I’ve written a couple of surprisingly nifty mixins, using named areas and templates, and we’ve hit upon some basic conventions to create highly readable code. I thought it would be valuable to walk through a fully-developed production implementation of a single major component using grid, digging in to some of the design questions it throws up and steering you away from some pitfalls we’ve encountered. CSS grid is a large spec, with lots of possible approaches and lots of right ways to do things, but at some point you have to lock down your method and get it live.
I’m expecting some basic familiarity with CSS, Sass, BEM, and some interest in the task of prototyping fully-realized, accessible, custom frameworks with 50+ components from Sketch or Photoshop-type documents on a tight timeline (say, a week).
First, let’s identify and separate out the design into distinct coding tasks and plan how we’ll approach them:
Type: The designer has already defined a type system.
Colors: First, we build a theme model and then include that in the partial.
Content: What elements are in this block? What are its variations? This is where our BEM mixin comes in.
Layout: This is how the content is placed in the block. You might want to skip directly to this.
Conventions: This is exactly how we choose to write all the above. There are many right answers in CSS, so what is important is that we all just agree to a convention, the rules of the road. This really comes first, but for the sake of this article, we’ll conclude here.
Type system
We use utility classes (e.g. h-text--h1, h-text--badge) for type styles. There may be a hundred type styles in a project. We export those styles from Sketch right into our Patternlab using Typex. That’s a whole other article on its own, so let’s just stipulate type as handled. We won’t bring type into our component partial.
Theming is a few tiny mixins dropped in, so we ideally won’t see a ton of color rules in our partial. We store them all together in a _themer.scss partial in our „Mixins and Models” library, so we can be sure to follow the design system of the site. This way, when someone comes back to the build later on, they have a key reference partial describing the design and branding rules. When building and maintaining numerous sites in broadly the same market — but each all with different brand spec — you’ve gotta make sure you don’t mix up one brand with another! So, much like type, we abstract the color rules away from the partial. In essence, we’re really only looking at layout (as much as possible) in our _header.scss file.
Given that we agree the convention to always theme using our mixin, this is how it would be included on an element:
@include var($property, $value);
Then we’ll set a theme model, of how colors work on this particular site and apply that theme to a component with:
@include theme;
Here’s the sample theme model we’re going to use with this page header. It’s super simple.
We’re pairing a color with black or white. We depend on a contrast rule and flip them for emphasis, maybe on events, like hover, or a highlighted call to action. This is all we need to do to make that happen and now we have a document of how color should really work on this site. We can go to and check against if we need to debug or expand the UI.
We also want to prep inheritance to help us, so let’s identify some helpful conventions:
Using this theme model, we might generate any number of themes, perhaps storing them as utility classes, or looping over a list of modifiers inside a component, or just allowing the user to set variables right on the block in the CMS. When IE 11 drops below 1% in our stats, we can do much more with variables, but this is enough for our current purposes.
Let’s not get side-tracked. What about grid?!
Content components
Grid lets us describe exactly what content we have in each partial in a new way. It’s really a game changer for design agencies building new UI for every project and we’re discovering new (and fun) applications for it as we explore.
To give context: we customize each interface for our clients, with custom fields made to suit their specific needs and their content model, using Craft CMS. We have internal tools that pull in events from ticketing APIs and create entries from that data, which may then be edited and expanded (or created entirely) in the CMS. The client can fill in or edit named fields in permanent page regions, and also add in whole designed, branded content blocks into the layout of each page as they build them.
There’s a lot of UI. The clients have a lot of control over content and we have a lot of control over the HTML, so we can ensure a high standard of accessible, semantic code on the page. We develop the content model together during discovery and then turn ’em loose on content creation. They add what they want and we ensure that it works and always looks right. Better than right! Super. (Sorry! :P)
Accessible, logical HTML is my jam. At minimum, I require a green accessibility score on Lighthouse score for my projects. (Who am I kidding, I want that delicious 100!) Core paths and pages are tested with a couple of screen readers, the keyboard tab, keyboard navigation), low vision simulators, dasher, voice access and binary switch. (I also work for Robots and Cake so this is a big part of my development.) I add giant clickable phone numbers and email addresses to pages over and over. I just want to get people where they are going.
I’ve been concerned about the way content can be re-ordered with grid (and flexbox, for that matter). Having gone through a few builds now, I actually think grid can help us with this problem. With CSS Grid, there’s no reason to move around HTML in service to the layout. We can go back to thinking about the whole document as a logical, linear sequence as our first concern.
Branding vs. Performance vs. Maintenance
Arts venues require high-spec graphic design, unified across print and web, and have constantly changing materials (e.g. programs, brochures, tickets, posters, microsites, etc.) they need to get out to their audiences, including contractual marketing obligations that must be met. As you can imagine, we have a lot of high quality large images we have to prioritize and typically come with strong print-led branding. That means we may be serving around fifteen custom fonts (including weight variations, display faces, etc.) and complex CSS to the page as well. We have to keep ourselves as lean as we can. We are shipping CSS that’s around 20 KB nano Gzipped at the moment but I’m working on reducing it further.
However, we do keep the grid area names full length by setting reduce identifiers to false in our PostCSS task. It’s vastly more useful to have the layout maps available in DevTools than it is to save those few bytes. For maintenance, self-documentation, and the sake of your future self who is debugging this site without repo access on a delayed train in Sowerby Bridge: keep the maps.
Code health
The way to balance all these competing needs is to articulate and agree on conventions so that there’s less to fix in testing and so that solved problems stay solved. We examine all the components we build and make sure they always start with a heading, that links go places, and buttons trigger actions, that countable objects are delivered as a list and preceded by a landmark heading, that navs are <nav> and times are <time> and div soup is eaten for breakfast— the basics.
With CSS Grid, there’s no excuse to move aroundHTMLin service to the layout. Your content can always flow logically while changes in layout happen in CSS. And, as there’s no need for margins or padding to create gutters, you can simply declare:
.o-grid .o-grid { width:100%; }
…to be sure any number of nested groups all visually occupy the same page grid. The HTML can be a clearer guide to what things really are: a closer document.
There’s a lot to manage between the heading and the action, and it’s my challenge to keep track of all these fields in all those components and make it traversable, scannable, linearizable, and easily read in some kind of logical, understandable manner, while making sure I’m faithfully executing the design spec.
Let’s bring in my first, surprisingly useful, grid mixin.
Each component partial now starts out with a list of all its possible elements, which is a very handy piece of documentation, especially when Twigging the actual front-end component.
The mixin takes care of assigning the grid areas.
Element and component names stay consistent across Sketch, CSS, and HTML and any inconsistencies will be very obvious, as the layout will fail. I’m firm, but fair.
BEM naming is enforced automatically but isn’t muddling things up in the partial.
Now, in the partial, we will just declare grid-template-areas, using normal English words, giving us a series of maps of the layouts that also match the database fields. Super readable!
We decided to stick to named areas for internal grids because I read a great article on this very site explaining how Autoprefixer can handle grid for IE 11 if you stick to the listed supported properties — and it does for the most part. If you view this test case with Autoprefixer applied in the super useful Debug Mode in a browser test, you’ll see it working.
So far, so good.
But there are pitfalls! You must set inline elements to block to make sure they always operate as grid cells in IE 11. Comment out the marked line in the example to see what happens otherwise:
Debug has caught an issue.
Ouch! Be careful with those blocks. You may find some versions of IE 11 don’t even pick up this fix, in which case you might try just using plain ol’ <p> tags… sigh.
I don’t include display:grid in this mixin because there are scenarios where the actual grid is set on an inner container, for example, but we’d still want the grid-areas to match on the correct BEM class.
Let’s identify a few more rules of the road to ensure this component slides right into a page layout without hassle. At time of writing, there’s no subgrid) available (but there will be!), so this component knows nothing of the parent grid it’s living in. This happens to match the BEM component approach well — as each component is written flat, and orphaned, to limit inheritance. I’m not advocating for BEM (or BEM-ish as we obviously use) here — I’m just saying that if you’re already using it, this is a bonus.
In this example, the designer has set a page layout of 12 column grid with 20px (1.25rem) gutters, site-wide, with no offset pieces. Our component is a page region and will occupy all 12 grid columns. In this transitional period, we’re still using this kind of set grid as we have a ton of systems still based on this idea that we have to integrate with. So, here’s our convention for this condition: for a full width region drop the grip gap and write the grid template columns as fractional units (fr) of 12.
Doing things this way means:
the sight lines of this internal grid broadly follow the grid it sits within;
it’s easy to see the underlying design rules in the code; and
it’s easy to line things up exactly, if required.
A quick note on „lining up”
Wait… what do I mean 'to have things line up exactly’? Doesn’t it already line up exactly?
Two equal columns split mid-gutter of the parent 12-column grid.
Well, no. The fractional units approach divides across the space perfectly, so you end up in the gutter. Two even columns lands you halfway across the gutter. Two columns where one is 2/3 and the other is 1/3 will split 1/3 of the way across that gutter, and so on.
Two unequal columns (set to 2fr and 1fr, respectively) split a third of the way into a gutter of the 12-column parent grid.
It’s not exactly hard to fix the alignment, as we know the width of our page grid gutter. For example, on an even split, we could include the grid gap.
However, we can’t do that with any other division. What we can do is add that gap as a margin — the margin is added inside no matter what box sizing you have set. In this example, we have three columns (two named areas and one empty space), splitting our gutter into thirds:
This is how to calculate those margins: Make sure the total fr units sum results in 12. Divide the grid gap by the number of columns in the parent grid, then multiply that like so:
The right margin multiplier of n is equal to the sum of the fr units to the right of n. The left margin of n is equal to the sum of the fr units to the left of n.
So, for grid-template-columns with a value of 2fr 3fr 2fr 4fr 1fr:
I’m sure you can think of other solutions, like switching to named lines, or adding in extra fixed-width columns or even writing all maps with 12 named areas per row. There are so many ways you can deal with this, but I think a lot of them remove the advantage of named areas. Areas give us a readable layout map that contains what our future selves need to know. It is code as documentation.
To be clear, the design problem I’m walking us through is not one of alignment. Alignment is easy with grid. The question is not of solving the immediate, trivial, layout problem, but of solving it in a way that supports our goal of being able to come back in six months and grasp:
What elements are in the component.
How they are laid out.
Why the code is written in this way.
The grid specification is huge and it’s easy to get lost in the options. Perhaps it’s a better plan to reset to a 12-column grid and use the number spec (i.e. explicitly link to our page grid, which uses the number spec) when absolute alignment is required — but I do feel there’s a smarter, simpler solution waiting to be found. For this site, we ended up writing a page grid object and added nested internal grid cells to it with classes: .o-page-grid__sidebar.
What do you all think? I definitely foresee differing perspectives on this. 🤦♀️
What next? A themed event header with a full width info bar that sticks and an internal button that lines up with the sidebar on the parent grid? You bet. I’ll include a parent grid so it’s easier to see:
Here’s a demo of all these variations as one partial. Yes, it’s a map! And it’s a wrap!
Conventions
Phew, we covered a lot! But you can see how flexible and self-documenting a system like this can be, right?
Type is handled with a separate type system.
Colors are handled by a theme partial that describes the underlying color rules of the design, rather than simply coloring elements ad hoc.
Elements are called what they are, in English, and included as a list at the top of the partial with the template mixin. This list can be taken into Twig or a template as a reference.
Correct HTML is always used and nesting doesn’t break grid. That means you can apply any number of nested grids to the same layout space by setting a convention.
Precise alignment is done in a number spec, and not a name spec (but note that alignment is possible with name spec).
IE 11 is supported.
I do have one more quick note and example of another component built with named areas. In this example, cards are not regions, but components placed in a grid, so there’s no reason to use the fr of 12 convention. You can expect a media object partial to look like this:
.c-card {
&--news {
align-content: start;
grid-template-areas:
"image"
"datetime"
"title";
}
&--search {
justify-content: start;
grid-template-columns: 1fr 3fr;
grid-template-areas:
"image page"
"image title"
"image summary";
}
&--merchandise {
grid-gap: 0;
grid-template-columns: $b 1fr 1fr $b;
grid-template-areas:
"image image image image"
". title title ."
". summary summary ."
". price action .";
}
&--donations {
// donations thanks button is too long and must take up more space than input
grid-gap: 0;
grid-template-columns: $b 1fr 2fr $b;
grid-template-areas:
"image image image image"
". title title ."
". summary summary ."
". input action .";
}
}
// ...
I work for Supercool, a fast-moving design agency that makes custom built sites for arts clients, powered by the off-the-shelf system, Craft CMS; it’s high-spec graphic design with relatively demanding typography and art direction. Over the past few months we’ve been moving to CSS grid. We’re transitioning slowly, allowing ourselves to discover new paradigms and design methods, instead of simply porting old habits to a new syntax.
So far, we’ve developed a number of really useful strategies for keeping track of the layout. I’ve written a couple of surprisingly nifty mixins, using named areas and templates, and we’ve hit upon some basic conventions to create highly readable code. I thought it would be valuable to walk through a fully-developed production implementation of a single major component using grid, digging in to some of the design questions it throws up and steering you away from some pitfalls we’ve encountered. CSS grid is a large spec, with lots of possible approaches and lots of right ways to do things, but at some point you have to lock down your method and get it live.
I’m expecting some basic familiarity with CSS, Sass, BEM, and some interest in the task of prototyping fully-realized, accessible, custom frameworks with 50+ components from Sketch or Photoshop-type documents on a tight timeline (say, a week).
First, let’s identify and separate out the design into distinct coding tasks and plan how we’ll approach them:
Type: The designer has already defined a type system.
Colors: First, we build a theme model and then include that in the partial.
Content: What elements are in this block? What are its variations? This is where our BEM mixin comes in.
Layout: This is how the content is placed in the block. You might want to skip directly to this.
Conventions: This is exactly how we choose to write all the above. There are many right answers in CSS, so what is important is that we all just agree to a convention, the rules of the road. This really comes first, but for the sake of this article, we’ll conclude here.
Type system
We use utility classes (e.g. h-text--h1, h-text--badge) for type styles. There may be a hundred type styles in a project. We export those styles from Sketch right into our Patternlab using Typex. That’s a whole other article on its own, so let’s just stipulate type as handled. We won’t bring type into our component partial.
Theming is a few tiny mixins dropped in, so we ideally won’t see a ton of color rules in our partial. We store them all together in a _themer.scss partial in our „Mixins and Models” library, so we can be sure to follow the design system of the site. This way, when someone comes back to the build later on, they have a key reference partial describing the design and branding rules. When building and maintaining numerous sites in broadly the same market — but each all with different brand spec — you’ve gotta make sure you don’t mix up one brand with another! So, much like type, we abstract the color rules away from the partial. In essence, we’re really only looking at layout (as much as possible) in our _header.scss file.
Given that we agree the convention to always theme using our mixin, this is how it would be included on an element:
@include var($property, $value);
Then we’ll set a theme model, of how colors work on this particular site and apply that theme to a component with:
@include theme;
Here’s the sample theme model we’re going to use with this page header. It’s super simple.
We’re pairing a color with black or white. We depend on a contrast rule and flip them for emphasis, maybe on events, like hover, or a highlighted call to action. This is all we need to do to make that happen and now we have a document of how color should really work on this site. We can go to and check against if we need to debug or expand the UI.
We also want to prep inheritance to help us, so let’s identify some helpful conventions:
Using this theme model, we might generate any number of themes, perhaps storing them as utility classes, or looping over a list of modifiers inside a component, or just allowing the user to set variables right on the block in the CMS. When IE 11 drops below 1% in our stats, we can do much more with variables, but this is enough for our current purposes.
Let’s not get side-tracked. What about grid?!
Content components
Grid lets us describe exactly what content we have in each partial in a new way. It’s really a game changer for design agencies building new UI for every project and we’re discovering new (and fun) applications for it as we explore.
To give context: we customize each interface for our clients, with custom fields made to suit their specific needs and their content model, using Craft CMS. We have internal tools that pull in events from ticketing APIs and create entries from that data, which may then be edited and expanded (or created entirely) in the CMS. The client can fill in or edit named fields in permanent page regions, and also add in whole designed, branded content blocks into the layout of each page as they build them.
There’s a lot of UI. The clients have a lot of control over content and we have a lot of control over the HTML, so we can ensure a high standard of accessible, semantic code on the page. We develop the content model together during discovery and then turn ’em loose on content creation. They add what they want and we ensure that it works and always looks right. Better than right! Super. (Sorry! :P)
Accessible, logical HTML is my jam. At minimum, I require a green accessibility score on Lighthouse score for my projects. (Who am I kidding, I want that delicious 100!) Core paths and pages are tested with a couple of screen readers, the keyboard tab, keyboard navigation), low vision simulators, dasher, voice access and binary switch. (I also work for Robots and Cake so this is a big part of my development.) I add giant clickable phone numbers and email addresses to pages over and over. I just want to get people where they are going.
I’ve been concerned about the way content can be re-ordered with grid (and flexbox, for that matter). Having gone through a few builds now, I actually think grid can help us with this problem. With CSS Grid, there’s no reason to move around HTML in service to the layout. We can go back to thinking about the whole document as a logical, linear sequence as our first concern.
Branding vs. Performance vs. Maintenance
Arts venues require high-spec graphic design, unified across print and web, and have constantly changing materials (e.g. programs, brochures, tickets, posters, microsites, etc.) they need to get out to their audiences, including contractual marketing obligations that must be met. As you can imagine, we have a lot of high quality large images we have to prioritize and typically come with strong print-led branding. That means we may be serving around fifteen custom fonts (including weight variations, display faces, etc.) and complex CSS to the page as well. We have to keep ourselves as lean as we can. We are shipping CSS that’s around 20 KB nano Gzipped at the moment but I’m working on reducing it further.
However, we do keep the grid area names full length by setting reduce identifiers to false in our PostCSS task. It’s vastly more useful to have the layout maps available in DevTools than it is to save those few bytes. For maintenance, self-documentation, and the sake of your future self who is debugging this site without repo access on a delayed train in Sowerby Bridge: keep the maps.
Code health
The way to balance all these competing needs is to articulate and agree on conventions so that there’s less to fix in testing and so that solved problems stay solved. We examine all the components we build and make sure they always start with a heading, that links go places, and buttons trigger actions, that countable objects are delivered as a list and preceded by a landmark heading, that navs are <nav> and times are <time> and div soup is eaten for breakfast— the basics.
With CSS Grid, there’s no excuse to move aroundHTMLin service to the layout. Your content can always flow logically while changes in layout happen in CSS. And, as there’s no need for margins or padding to create gutters, you can simply declare:
.o-grid .o-grid { width:100%; }
…to be sure any number of nested groups all visually occupy the same page grid. The HTML can be a clearer guide to what things really are: a closer document.
There’s a lot to manage between the heading and the action, and it’s my challenge to keep track of all these fields in all those components and make it traversable, scannable, linearizable, and easily read in some kind of logical, understandable manner, while making sure I’m faithfully executing the design spec.
Let’s bring in my first, surprisingly useful, grid mixin.
Each component partial now starts out with a list of all its possible elements, which is a very handy piece of documentation, especially when Twigging the actual front-end component.
The mixin takes care of assigning the grid areas.
Element and component names stay consistent across Sketch, CSS, and HTML and any inconsistencies will be very obvious, as the layout will fail. I’m firm, but fair.
BEM naming is enforced automatically but isn’t muddling things up in the partial.
Now, in the partial, we will just declare grid-template-areas, using normal English words, giving us a series of maps of the layouts that also match the database fields. Super readable!
We decided to stick to named areas for internal grids because I read a great article on this very site explaining how Autoprefixer can handle grid for IE 11 if you stick to the listed supported properties — and it does for the most part. If you view this test case with Autoprefixer applied in the super useful Debug Mode in a browser test, you’ll see it working.
So far, so good.
But there are pitfalls! You must set inline elements to block to make sure they always operate as grid cells in IE 11. Comment out the marked line in the example to see what happens otherwise:
Debug has caught an issue.
Ouch! Be careful with those blocks. You may find some versions of IE 11 don’t even pick up this fix, in which case you might try just using plain ol’ <p> tags… sigh.
I don’t include display:grid in this mixin because there are scenarios where the actual grid is set on an inner container, for example, but we’d still want the grid-areas to match on the correct BEM class.
Let’s identify a few more rules of the road to ensure this component slides right into a page layout without hassle. At time of writing, there’s no subgrid) available (but there will be!), so this component knows nothing of the parent grid it’s living in. This happens to match the BEM component approach well — as each component is written flat, and orphaned, to limit inheritance. I’m not advocating for BEM (or BEM-ish as we obviously use) here — I’m just saying that if you’re already using it, this is a bonus.
In this example, the designer has set a page layout of 12 column grid with 20px (1.25rem) gutters, site-wide, with no offset pieces. Our component is a page region and will occupy all 12 grid columns. In this transitional period, we’re still using this kind of set grid as we have a ton of systems still based on this idea that we have to integrate with. So, here’s our convention for this condition: for a full width region drop the grip gap and write the grid template columns as fractional units (fr) of 12.
Doing things this way means:
the sight lines of this internal grid broadly follow the grid it sits within;
it’s easy to see the underlying design rules in the code; and
it’s easy to line things up exactly, if required.
A quick note on „lining up”
Wait… what do I mean 'to have things line up exactly’? Doesn’t it already line up exactly?
Two equal columns split mid-gutter of the parent 12-column grid.
Well, no. The fractional units approach divides across the space perfectly, so you end up in the gutter. Two even columns lands you halfway across the gutter. Two columns where one is 2/3 and the other is 1/3 will split 1/3 of the way across that gutter, and so on.
Two unequal columns (set to 2fr and 1fr, respectively) split a third of the way into a gutter of the 12-column parent grid.
It’s not exactly hard to fix the alignment, as we know the width of our page grid gutter. For example, on an even split, we could include the grid gap.
However, we can’t do that with any other division. What we can do is add that gap as a margin — the margin is added inside no matter what box sizing you have set. In this example, we have three columns (two named areas and one empty space), splitting our gutter into thirds:
This is how to calculate those margins: Make sure the total fr units sum results in 12. Divide the grid gap by the number of columns in the parent grid, then multiply that like so:
The right margin multiplier of n is equal to the sum of the fr units to the right of n. The left margin of n is equal to the sum of the fr units to the left of n.
So, for grid-template-columns with a value of 2fr 3fr 2fr 4fr 1fr:
I’m sure you can think of other solutions, like switching to named lines, or adding in extra fixed-width columns or even writing all maps with 12 named areas per row. There are so many ways you can deal with this, but I think a lot of them remove the advantage of named areas. Areas give us a readable layout map that contains what our future selves need to know. It is code as documentation.
To be clear, the design problem I’m walking us through is not one of alignment. Alignment is easy with grid. The question is not of solving the immediate, trivial, layout problem, but of solving it in a way that supports our goal of being able to come back in six months and grasp:
What elements are in the component.
How they are laid out.
Why the code is written in this way.
The grid specification is huge and it’s easy to get lost in the options. Perhaps it’s a better plan to reset to a 12-column grid and use the number spec (i.e. explicitly link to our page grid, which uses the number spec) when absolute alignment is required — but I do feel there’s a smarter, simpler solution waiting to be found. For this site, we ended up writing a page grid object and added nested internal grid cells to it with classes: .o-page-grid__sidebar.
What do you all think? I definitely foresee differing perspectives on this. 🤦♀️
What next? A themed event header with a full width info bar that sticks and an internal button that lines up with the sidebar on the parent grid? You bet. I’ll include a parent grid so it’s easier to see:
Here’s a demo of all these variations as one partial. Yes, it’s a map! And it’s a wrap!
Conventions
Phew, we covered a lot! But you can see how flexible and self-documenting a system like this can be, right?
Type is handled with a separate type system.
Colors are handled by a theme partial that describes the underlying color rules of the design, rather than simply coloring elements ad hoc.
Elements are called what they are, in English, and included as a list at the top of the partial with the template mixin. This list can be taken into Twig or a template as a reference.
Correct HTML is always used and nesting doesn’t break grid. That means you can apply any number of nested grids to the same layout space by setting a convention.
Precise alignment is done in a number spec, and not a name spec (but note that alignment is possible with name spec).
IE 11 is supported.
I do have one more quick note and example of another component built with named areas. In this example, cards are not regions, but components placed in a grid, so there’s no reason to use the fr of 12 convention. You can expect a media object partial to look like this:
.c-card {
&--news {
align-content: start;
grid-template-areas:
"image"
"datetime"
"title";
}
&--search {
justify-content: start;
grid-template-columns: 1fr 3fr;
grid-template-areas:
"image page"
"image title"
"image summary";
}
&--merchandise {
grid-gap: 0;
grid-template-columns: $b 1fr 1fr $b;
grid-template-areas:
"image image image image"
". title title ."
". summary summary ."
". price action .";
}
&--donations {
// donations thanks button is too long and must take up more space than input
grid-gap: 0;
grid-template-columns: $b 1fr 2fr $b;
grid-template-areas:
"image image image image"
". title title ."
". summary summary ."
". input action .";
}
}
// ...