“This isn’t a website. Or even a blog. It’s a conversation.”
That’s the idea! Jay Hoffman and I’ve been chatting a long time now, back since he began writing a series on web history. It’s easy-going talking with someone with all that knowledge about the web’s interesting nooks and crannies.
Anyway, I always look forward to those chats. Sometimes, though, one of can’t make it for whatever reason. At the time, we’d been talking about different approaches to blogging and were particularly keen on the whole “digital garden” concept. We weren’t going to plant one or anything, but that inspired us to move our long-running conversation to a blog — you know, one of the best mediums ever in web history.
We didn’t want something linear in the sense that the blog is an archive of posts in chronological order. No, instead we wanted it to start as a seed that grows into a vine that twists and turns at different bends.
That’s where the “Dialogues” idea came in. It’s all Jay’s, really. He starts by asking me a question that I answer in the form of a post with a follow-up question that he, in turn, answers in a post with a follow-up question, and on and on.
The first question is already up there, and it’s a nice little ice breaker: What was the moment the web clicked for you? I had to take a few moments to let that one sink in and reflect on myself as a web user from 30 years ago. What a great little mind exercise and thing to talk about!
I wouldn’t adopt Alvaro’s suggestion without further ado, as I would waste so much space on a smartphone, for example, and many users would probably be irritated by the large font.
I set a font size of 1.2rem from a certain viewport size. But this also has to be done carefully, because then grey areas arise in which media queries suddenly fall back into another area…
I personally agree with Alvaro that the default 16px size is too small. That’s just how I feel as someone who is uncomfortably close to wearing the bottoms of actual Coke bottles to see anything clearly on a screen.
On the flip side, I professionally agree with Sebastian, not that many users would probably be irritated by the large font, but to openly question an approach rather than adopting someone else’s approach wholesale based on a single blog post. It may very well be that a font-size bump is the right approach. Everything is relative, after all, and we ought to be listening to the people who use the thing we’re making for decisions like this.
The much bigger question is the one Sebastian poses right at the end there:
Should browsers perhaps use a larger font size on large screens from the outset if the user does not specify otherwise? Or do we need an information campaign to make them aware that they should check their system settings or set a different default font size in the browser?
Fantastic, right?! I’m honestly unsure where I’d draw the viewport breakpoint for 16px being either too large or small and where to start making adjustments. Is 16px the right default at any viewport size? Or perhaps user agents ought to consider a fluid type implementation that defines a default font scale and range of sizes instead? It’s great food for thought.
But that’s exactly the sort of discussions that Schalk Venter and Schalk Neethling host on their Mental Health in Tech podcast. They invited me on the show and we got deep into what it’s like to do your best work when you’re not feeling your best. We got so deep into it that we didn’t realize two hours blew right by, and the interview was split into two parts, the second of which published today.
Vulnerability and Breaking the Facade as a Balancing Act – Geoff – Part 1 by Schalk Neethling
Ever search for CSS info and run into some article — perhaps even one or a dozen on this site — that looks promising until you realize it was published when dinosaurs roamed the planet? The information is good, but maybe isn’t the best reflection of modern best practices?
I’ve wondered that a bunch. And so has Chris. But so has Brecht De Ruyte and he’s actually doing something about it, along with other folks who make up the W3C CSS-Next community group. I worked with him on this article for Smashing Magazine and was stoked to see how much discussion, thought, and intention have gone into “versioning” CSS.
The idea? We’d “skip” CSS4, so to speak, slapping a CSS4 label on a lot of what’s released since CSS3:
CSS3 (~2009-2012): Level 3 CSS specs as defined by the CSSWG. (immutable)
CSS4 (~2013-2018): Essential features that were not part of CSS3 but are already a fundamental part of CSS..
From there?
CSS5 (~2019-2024): Newer features whose adoption is steadily growing.
CSS6 (~2025+): Early-stage features that are planned for future CSS
The most important part of the article, though, is that you (yes, you) can help the CSS-Next community group.
We also want you to participate. Anyone is welcome to join the CSS-Next group and we could certainly use help brainstorming ideas. There’s even an incubation group that conducts a biweekly hour-long session that takes place on Mondays at 8:00 a.m. Pacific Time (2:00 p.m. GMT).
Christian Heilmann gave this talk at Typo3 Developer Days. I’m linking it up because it strikes an already stricken nerve in me. The increasing complexity of web development has an inverse relationship with the decreasing number of entry points for those getting into web development.
I love how Christian compares two hypothetical development stacks.
Then
index.html
Now
Get the right editor with all the right extensions
Set up your terminal with the right font and all the cool dotfiles
Install framework flügelhorn.js with bundler wolperdinger.io
Go to the terminal and run packagestuff –g install
Look at all the fun warning messages and update dependencies
Doesn’t work? Go SUDO, all the cool kids are …
Don’t bother with the size of the modules folder
Learn the abstraction windfarm.css – it does make you so much more effective
Use the templating language funsocks – it is much smaller than HTML
Check out the amazing hello world example an hour later…
He’s definitely a bit glib, but the point is solid. Things are more complex today than they were, say, ten years ago. I remember struggling with Grunt back then and thinking I’d never get it right. I did eventually, and my IDE was never the same after that.
This world is unfortunately becoming lost or, at least, degraded — not because it is no longer possible to view the source of a webpage, but because that source is often inscrutable, even on simple webpages.
The thing is: none of this is gone. Nothing about the web has changed that prevents us from going back. If anything, it’s become a lot easier. We can return. Better, yet: we can restore the things we loved about the old web while incorporating the wonderful things that have emerged since, developing even better things as we go forward, and leaving behind some things from the early web days we all too often forget when we put on our rose-colored glasses.
As we’ve been hearing constantly for the last couple of years, AI will soon replace every creative job and we’ll all have to retrain as mechanics or tree surgeons or something. This prediction, apart from being unrealistic, also sounds totally dystopian.
Welcome to the August toolbox. We’ve found goodies for designers, developers, project managers, domain admins, and those of you who wear all of these hats.
I have to thank Jeremy Keith and his wonderfully insightful article from late last year that introduced me to the concept of HTML Web Components. This was the “a-ha!” moment for me:
When you wrap some existing markup in a custom element and then apply some new behaviour with JavaScript, technically you’re not doing anything you couldn’t have done before with some DOM traversal and event handling. But it’s less fragile to do it with a web component. It’s portable. It obeys the single responsibility principle. It only does one thing but it does it well.
Until then, I’d been under the false assumption that all web components rely solely on the presence of JavaScript in conjunction with the rather scary-sounding Shadow DOM. While it is indeed possible to author web components this way, there is yet another way. A better way, perhaps? Especially if you, like me, advocate for progressive enhancement. HTML Web Components are, after all, just HTML.
Let’s look at three specific examples that show off what I think are the key features of HTML Web Components — CSS style encapsulation and opportunities for progressive enhancement — without being forced to depend on JavaScript to work out of the box. We will most definitely use JavaScript, but the components ought to work without it.
Let’s start with the first-class citizen, HTML. Web components allow us to establish custom elements with our own naming, which is the case in this example with a tag we’re using to hold a designed to show/hide a block of text and a
that holds the of text we want to show and hide.
<webui-disclosure
data-bind-escape-key
data-bind-click-outside
>
<button
type="button"
class="button button--text"
data-trigger
hidden
>
Show / Hide
</button>
<div data-content>
<p>Content to be shown/hidden.</p>
</div>
</webui-disclosure>
If JavaScript is disabled or doesn’t execute (for any number of possible reasons), the button is hidden by default — thanks to the hidden attribute on it— and the content inside of the div is simply displayed by default.
Nice. That’s a really simple example of progressive enhancement at work. A visitor can view the content with or without the .
I mentioned that this example extends Chris Ferdinandi’s initial demo. The key difference is that you can close the element either by clicking the keyboard’s ESC key or clicking anywhere outside the element. That’s what the two [data-attribute]s on the <webui-disclosure tag are for.
Custom elements must be named with a dashed-ident, such as or whatever, but as Jim Neilsen notes, by way of Scott Jehl, that doesn’t exactly mean that the dash has to go between two words.
I typically prefer using TypeScript for writing JavaScript to help eliminate stupid errors and enforce some degree of “defensive” programming. But for the sake of simplicity, the structure of the web component’s ES Module looks like this in plain JavaScript:
default class WebUIDisclosure extends HTMLElement {
constructor() {
super();
this.trigger = this.querySelector('[data-trigger]');
this.content = this.querySelector('[data-content]');
this.bindEscapeKey = this.hasAttribute('data-bind-escape-key');
this.bindClickOutside = this.hasAttribute('data-bind-click-outside');
if (!this.trigger || !this.content) return;
this.setupA11y();
this.trigger?.addEventListener('click', this);
}
setupA11y() {
// Add ARIA props/state to button.
}
// Handle constructor() event listeners.
handleEvent(e) {
// 1. Toggle visibility of content.
// 2. Toggle ARIA expanded state on button.
}
// Handle event listeners which are not part of this Web Component.
connectedCallback() {
document.addEventListener('keyup', (e) => {
// Handle ESC key.
});
document.addEventListener('click', (e) => {
// Handle clicking outside.
});
}
disconnectedCallback() {
// Remove event listeners.
}
}
Are you wondering about those event listeners? The first one is defined in the constructor() function, while the rest are in the connectedCallback() function. Hawk Ticehurst explains the rationale much more eloquently than I can.
This JavaScript isn’t required for the web component to “work” but it does sprinkle in some nice functionality, not to mention accessibility considerations, to help with the progressive enhancement that allows the to show and hide the content. For example, JavaScript injects the appropriate aria-expanded and aria-controls attributes enabling those who rely on screen readers to understand the button’s purpose.
That’s the progressive enhancement piece to this example.
For simplicity, I have not written any additional CSS for this component. The styling you see is simply inherited from existing global scope or component styles (e.g., typography and button).
However, the next example does have some extra scoped CSS.
Example 2:
That first example lays out the progressive enhancement benefits of HTML Web Components. Another benefit we get is that CSS styles are encapsulated, which is a fancy way of saying the CSS doesn’t leak out of the component. The styles are scoped purely to the web component and those styles will not conflict with other styles applied to the current page.
Let’s turn to a second example, this time demonstrating the style encapsulating powers of web components and how they support progressive enhancement in user experiences. We’ll be using a tabbed component for organizing content in “panels” that are revealed when a panel’s corresponding tab is clicked — the same sort of thing you’ll find in many component libraries.
Starting with the HTML structure:
<webui-tabs>
<div data-tablist>
<a href="#tab1" data-tab>Tab 1</a>
<a href="#tab2" data-tab>Tab 2</a>
<a href="#tab3" data-tab>Tab 3</a>
</div>
<div id="tab1" data-tabpanel>
<p>1 - Lorem ipsum dolor sit amet consectetur.</p>
</div>
<div id="tab2" data-tabpanel>
<p>2 - Lorem ipsum dolor sit amet consectetur.</p>
</div>
<div id="tab3" data-tabpanel>
<p>3 - Lorem ipsum dolor sit amet consectetur.</p>
</div>
</webui-tabs>
You get the idea: three links styled as tabs that, when clicked, open a tab panel holding content. Note that each [data-tab] in the tab list targets an anchor link matching a tab panel ID, e.g., #tab1, #tab2, etc.
We’ll look at the style encapsulation stuff first since we didn’t go there in the last example. Let’s say the CSS is organized like this:
webui-tabs {
[data-tablist] {
/* Default styles without JavaScript */
}
[data-tab] {
/* Default styles without JavaScript */
}
[role='tablist'] {
/* Style role added by JavaScript */
}
[role='tab'] {
/* Style role added by JavaScript */
}
[role='tabpanel'] {
/* Style role added by JavaScript */
}
}
See what’s happening here? We have two style rules — [data-tablist] and [data-tab] — that contain the web component’s default styles. In other words, these styles are there regardless of whether JavaScript loads or not. Meanwhile, the other three style rules are selectors that are injected into the component as long as JavaScript is enabled and supported. This way, the last three style rules are only applied if JavaScript plops the**role**attribute on those elements in the HTML. Right there, we’re already supplying a touch of progressive enhancement by setting styles only when JavasScript is needed.
All these styles are fully encapsulated, or scoped, to the web component. There is no “leakage” so to speak that would bleed into the styles of other web components, or even to anything else on the page within the global scope. We can even choose to forego classnames, complex selectors, and methodologies like BEM in favour of simple descendent selectors for the component’s children, allowing us to write styles more declaratively on semantic elements.
Quickly: “Light” DOM versus Shadow DOM
For most web projects, I generally prefer to bundle CSS (including the web component Sass partials) into a single CSS file so that the component’s default styles are available in the global scope, even if the JavaScript doesn’t execute.
However, it is possible to import a stylesheet via JavaScript that is only consumed by this web component if JavaScript is available:
import styles from './styles.css';
class WebUITabs extends HTMLElement {
constructor() {
super();
this.adoptedStyleSheets = [styles];
}
}
customElements.define('webui-tabs', WebUITabs);
Alternatively, we could inject a tag containing the component’s styles instead:
class WebUITabs extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' }); // Required for JavaScript access
this.shadowRoot.innerHTML = `
<style> <!-- styles go here --> </style>
// etc.
`;
}
}
customElements.define('webui-tabs', WebUITabs);
Whichever method you choose, these styles are scoped directly to the web component, preventing component styles from leaking out, but allowing global styles to be inherited.
Now consider this simple example. Everything we write in between the component’s opening and closing tags is considered part of the “Light” DOM.
<my-web-component>
<!-- This is Light DOM -->
<div>
<p>Some content... styles are inherited from the global scope</p>
</div>
----------- Shadow DOM Boundary -------------
| <!-- Anything injected by JavaScript --> |
---------------------------------------------
</my-web-component>
Dave Rupert has an excellent write-up that makes it really easy to see how external styles are able to “pierce” the Shadow DOM and select an element in the Light DOM. Notice how the element that is written in between the custom element’s tags receives the button selector’s styles in the global CSS, while the injected via JavaScript is left untouched.
If we want to style the Shadow DOM we’d have to do that with internal styles like the examples above for importing a stylesheet or injecting an inline block.
Progressively enhance the tabbed component with JavaScript
Even though this second example is more about style encapsulation, it’s still a good opportunity to see the progressive enhancement we get practically for free from web components. Let’s step into the JavaScript now so we can see how we can support progressive enhancement. The full code is quite lengthy, so I’ve abbreviated things a bit to help make the points a little clearer.
The JavaScript injects ARIA roles, states, and props to the tabs and content blocks for screen reader users, as well as extra keyboard bindings so we can navigate between tabs with the keyboard; for example, the TAB key is reserved for accessing the component’s active tab and any focusable content inside the active tabpanel, and the tabs can be traversed with the ARROW keys. So, if JavaScript fails to load, the default experience is still an accessible one where the tabs still anchor link to their respective panels, and those panels naturally stack vertically, one on top of the other.
And if JavaScript is enabled and supported? We get an enhanced experience, complete with updated accessibility considerations.
Example 3:
This final example differs from the previous two in that it is entirely generated by JavaScript, and uses the Shadow DOM. This is because it is only used to indicate a “loading” state for Ajax requests, and is therefore only needed when JavaScript is enabled.
The HTML markup is just the opening and closing component tags:
Notice right out of the gate that everything in between the tags is injected with JavaScript, meaning it’s all in the Shadow DOM, encapsulated from other scripts and styles not directly bundled with the component.
But also notice the part attribute that’s set on the element. Here’s where we’ll zoom in:
<svg role="img" part="svg">
<!-- etc. -->
</svg>
That’s yet another way we have to style the custom element: named parts. Now we can style that SVG from outside of the template literal we used to establish the element. There’s a ::part pseudo-selector to make that happen:
webui-ajax-loader::part(svg) {
// Shadow DOM styles for the SVG...
}
And here’s something cool: that selector can access CSS custom properties, whether they are scoped globally or locally to the element.
As far as progressive enhancement goes, JavaScript supplies all of the HTML. That means the loader is only rendered if JavaScript is enabled and supported. And when it is, the SVG is added, complete with an accessible title and all.
Wrapping up
That’s it for the examples! What I hope is that you now have the same sort of epiphany that I had when reading Jeremy Keith’s post: HTML Web Components are an HTML-first feature.
Of course, JavaScript does play a big role, but only as big as needed. Need more encapsulation? Want to sprinkle in some UX goodness when a visitor’s browser supports it? That’s what JavaScript is for and that’s what makes HTML Web Components such a great addition to the web platform — they rely on vanilla web languages to do what they were designed to do all along, and without leaning too heavily on one or the other.