Standout Designs for the Web is available to pre-order
My new book, Standout Designs for the Web, is available to pre-order for £14.99. Discover what’s inside, why I wrote it, and how to get my Hardboiled Client Interview Guide with your purchase.
My new book, Standout Designs for the Web, is available to pre-order for £14.99. Discover what’s inside, why I wrote it, and how to get my Hardboiled Client Interview Guide with your purchase.
When accessibility questions come up, not every team has someone who can help answer them. Without that resource, teams can miss how their decisions affect people with disabilities—and leave barriers in place until someone encounters them.
AI-assisted development makes that gap more pressing. If the tools helping us build software don’t account for how people with disabilities use technology, they can repeat those mistakes across more interfaces, faster. We need to bring that understanding into the work while decisions are still being made.
Today, my team is open sourcing the Accessibility Considerations Toolkit (ACT) to help close that gap.
I haven’t driven a car in decades (New Yorker). But from my pre-New York life, I remember how sun glare could turn a pleasant road trip into a blinding gauntlet. Enter “Sunward,” a uniquely specific demo app my Automattic colleague Peter Westwood just whipped up in Spacefast. Spacefast – for your next idea Sunward – […]
The post Better Day, Sunshine appeared first on Jeffrey Zeldman Presents.
Pete Millspaugh has a great post over on the Val Town blog where he documents his attempt at making generating the same website across a variety of AI tools.
I love the post. Here are a couple of thoughts that come to mind.
Pete decided to make a website for his conference talk, and he added a comments section to make it a less static / more dynamic demo.
This is interesting because LLMs are really good at generating one-off HTML pages you open at file://, but as soon as you start to introduce technological dependencies — like a database, or an analytics tool, or a service for sending email, or payment processing, etc. — the question becomes: How does the LLM choose which service to rely on?
It’s one thing to have the LLM choose React over Angular or Vue over Svelte. But how (and why) does it choose something like an auth provider? Does it build you a site with Auth0 or WorkOS or Clerk? How would a non-technical user even know how to weigh the trade-offs of making such a decision?
Pete said he was deliberately vague about these details, which is why I find his experience so intriguing. For example, here’s what he found after generating a site in Lovable:
My Lovable app is fullstack React, I learn, clicking around to find the code. It seems a bit heavy for a single-page site what with dozens of files […] The comments database seems to be using Supabase, which required no intervention on my part to set up. There’s a whole lot more—analytics, e-commerce and AI integrations, etc.
I’d be really curious to know what that “a whole lot more” was. Which tools/services did it choose to depend on?
How many people creating these sites understand that, even for a website that’s mostly static content, they’re being given an output that ships a JavaScript application framework, router, hydration runtime, analytics, component bundles, etc.? You asked for a sailboat, but you got an aircraft carrier. Do you know? Or more importantly, do you care?
But it’s more than just a choice of languages and frameworks. It’s also a choice of third-party services. Why Supabase? Why not Firebase? Or Neon? Speaking of Neon, here’s Pete’s experience with Vercel’s v0:
I copy-paste my prompt into the chatbox. After 11 seconds, v0 asks if I’d like to install Neon, because my comment wall needs a real database. Sure, why not. (Does that mean I need a separate Neon account? Vercel creates one for me, and later I’ll find a welcome email from Neon in my inbox.)
Frankly, this all feels like a space absolutely ripe for those kinds of business referral transactions you find in other industries.
Take real estate, for example. For lots of folks, when you buy a home you need an agent. (Not that kind of agent; a real estate agent.) Your real estate agent is there to help you answer all those common questions that come along with buying a home:
While you’re free to make all these decisions yourself, your agent is also there to help you pick. Presumably they’ll choose a company that reflects well on them, but there’s also undoubtedly good money to be made in referrals. After all, who else is dealing with people with these kinds of needs all day long?
The “make a website with AI” space has a parallel here which makes you wonder about the incentives behind the LLM’s decision-making. Why Supabase over Firebase? Because the LLM reasoned about it? Because, statistically speaking, it was the next most probable word? Because Supabase paid Lovable (and hopefully disclosed such preferential integrations)? If LLMs are already unpredictable and non-deterministic, nothing will make their actions more predictable and deterministic than some financial incentives.
Who is the customer here? The person generating the website? Or the company willing to pay for its services to be embedded in generated code? (That’s a lucrative business waiting to happen…)
As a customer, the credit systems behind these LLM systems are annoying as hell. Here’s Pete:
I’m using up “credits” as the machine works—0.70 so far, now 2.30, now 3.10, oh boy—and I have no clue what that means. There are no units. I’d prefer dollars. Are credits just dollars? As it turns out, no.
With another tool:
I do two more rounds of click “Continue generation” and wait until my website is ready: 5 minutes 36 seconds for 0.070 credits, and 1 minute 29 seconds for 0.024 credits.
It’s probably just me, but c’mon, who do these “credit” systems really benefit?
When my prompt is running, don’t show me usage like this:
Credits: 0.05 -> 0.0672 -> 0.0871
Just show me the cost:
Cost: $0.02 -> $0.21 -> $0.31
Don’t make me do the conversion of credits -> dollars on the fly. Computers are good at math.
[soap-box]
I know it might hurt your bottom line if I’m not thinking about how much money credits I’m spending, but c’mon, can we stop with the financial abstraction on top of financial abstraction?
I hate how so many hosting companies are doing this “credits” for compute primitives thing. It’s pitched as if spending credits is easier, because you can apply them across services, e.g. buy image transformations or serverless function compute, all with credits!
Yeah well guess what other financial abstraction I can apply to all those different services? Dollars and cents.
[/soap-box]
Pete’s conclusion:
I am both optimistic and pessimistic about this moment for making websites. I’m optimistic because end-user programming, the idea that everyone should be able to customize their own software, seems more possible than ever. I’m pessimistic because a slopsite, an AI-generated website with little to no human thinking involved, is often worse than no website at all.
That resonates — and feels generalizable. You fill in the blank:
An AI-generated ___ with little to no human thinking involved, is often worse than no ___ at all.
My conclusion?
Making Generating a website may be easier than ever. Understanding what you’ve generated, who you now depend on, and why? Not so much.
Shaw and Chris talk CSS Modules, a rather old bit of technology in the CSS-in-JS category. CodePen now supports them, which took a little work because the way they process code is a smidge unusual. We use CodePen Modules (with Sass!) here at CodePen so we knew we wanted to support them, but we got […]
While testing out a recent web component, I discovered the demo I’d built made active buttons totally inaccessible, but only on Android in dark mode. I was baffled because I intentionally use system colors in all my web components and their demos. In the browsers I’d been testing, the pair looked great in both dark and light themes, but over on my phone it was a different story.
In Chrome’s dark mode on Android, HighlightText is a mid-tone grey. Highlight is a mid-tone blue. The contrast was abysmal—a mere 1.98:1, failing all five contrast thresholds in the Web Content Accessibility Guidelines (WCAG). I wish I could say it was the first time I’d stumbled on this kind of issue. A week or so earlier, a user reported similar trouble in my tabbed interface demo with Windows in forced-color mode. I’d mistakenly thought forced-color mode was the primary issue. It wasn’t.
In both cases, I’d expected colors named for foreground and background roles to work together. Apparently, that was asking too much.
These experiences prompted me to build the System Color Study: a resource for comparing browser-provided colors and their intended pairings across browsers, operating systems, and preferences. I’m using it to inform my component work; I hope it’ll help you too.
The subhed on that Politico story above gets the election right in crediting “safety hawks, populists, and child-safety advocatesâ€� who united to oppose AI companies and their data centers in the midterms. But Politico got the main headline terribly wrong. These are not “AI doomers.â€� AI doomers are the AI cultists who, paradoxically and illogically, […]
The post A glossary for AI cultists vs. us appeared first on BuzzMachine.
Asset management is big business within the web world. The larger that our web apps grow, the larger the number of assets and their file size naturally increases. With that increase, you need to worry about increasing costs and vendor complexity! Luckily I’ve discovered a service which fixes all of those problems! Welcome to STRG: […]
The post STRG: S3-Compatible, AI-Native Cloud Storage (Sponsored) appeared first on David Walsh Blog.
The cover artwork for Standout Designs for the Web has a story stretching back to 2010, inspired by pulp detective magazines, a woman in a red dress, and a continuing detective story.
A quiet week in the studio gave me time to start writing a new video course, plan the months ahead, and enjoy a monstrously good trip to the cinema.
I still think saying no is important. In that vein, here’s something I shipped by removing functionality (and I think my site is better for it).
For the longest time on my icon gallery sites, you could view a list of icons in four pixel dimensions:
Each of these sizes could be accessed via a control on the website. This has been true for years on my site, whether it’s the current site:
Or an older version of the site:
By default, I loaded the 128-pixel version of the icons. But if you wanted to see other sizes, you could.
However, the more I used the site myself the more I thought, “Four options is too many”.
When it comes to choice, what I really wanted was:
(Plus three is always a good number, unless you have a reason otherwise).
So I removed the big 512 option. If I want to study an icon at that size and detail, I click through to the icon’s detail page.
Now there are three options for list views: 56, 128, and 256. Small (great on mobile), 128 (great by default), and big (let me see more detail).
It’s simple to think about:
You don’t need to think about it anymore than that.
I mean you can, but I’ve found I don’t need to. So I’ve taken that off your plate. Less cognitive overhead.
Maybe one day I’ll add a slider and make it all dynamic. But for now, I like this small addition by subtraction.
Studying The Times’ stats on the midterms, one conclusion stands out clearly: It was uneducated, white, older, often Southern men who did not ride the blue wave, who continued to back the Trumpists. Out of that, I see two lessons: the need to support a pluralistic society and education. It is time to end government […]
The post It’s education, stupid appeared first on BuzzMachine.
Chris & Shaw are chatting this week about the newfangled web technology, WebMCP. It involves new browser APIs like document.modelContext, meaning it needs to be a web standard, and that process is chugging along, but certainly not a done deal. That said, CodePen already has it conditionally implemented for browsers that support it, so you […]
I had my little Smashburgers in Bend website on Astro on Cloudflare Pages. While I 💜 both of those things, CodePen hosting is more than capable, and that’s obviously extremely fun and satisfying for me. I had the Smashburgers site on Astro way back when because they supported multi-page view transitions in a really nice […]
Have you ever wanted to display all variants of a Button next to each other in your Storybook like your designer did in Figma? But then you come to a screeching halt when you realize :hover, :active, :focus don’t work without user-interaction? Blergh. Whatever can we do?
I got nerd-sniped by this problem in the ShopTalk Discord when Nils said he was trying to build a simple state machine demo for his components. A handful of immediate options came to my head:
.is-hover utility classes (à la SMACSS)[data-state="hover"] attributes and copy over stylesEach of those approaches has trade-offs for me. I don’t want to ship extra component styles or manage duplicate global styles. And if I’m building a Playwright contraption for a CSS demo… yeesh, I should probably back away from the keyboard. When Nils mentioned he was using web components, I realized I might have an ace up my sleeve for a simple’ish solution to this problem.
See the Pen Pseudo-pseudo-states Demo! by Dave Rupert (@davatron5000) on CodePen.
There’s a handful of Advanced Web Component Tips & Tactics happening in this demo like adoptedStyleSheets, ElementInternals, and custom state sets but the approach is pretty lightweight. I scan the custom element’s styles, clone the interactive bits, rewrite styles like :hover state to :state(hover) and add the new stylesheet inside the component.
What I like about this approach compared to the other ideas I had is:
:hover and .is-hover styles to customers.Want an end-to-end walkthrough of how it all works? Please, follow me…
In web components, we’d typically author our styles in a CSS tagged template literal:
const MyButtonStyles = css`
/* Change the host-element itself */
:host(:hover) {
background: pink;
scale: 1.1;
}
/* Update a child element based on the host-element interaction */
:host(:hover) button {
background: pink;
scale: 1.1;
}
/* Or put the interaction on the interactive child-element */
button:hover {
background: pink;
scale: 1.1;
}
`
Then we create a new “constructed stylesheet” and populate the stylesheet with the styles we wrote. That stylesheet then attaches to our component by adding it to the Shadow Root’s array of adoptedStyleSheets .
/* Create a new constructed stylesheet */
const MyButtonStyleSheet = new CSSStyleSheet()
MyButtonStyleSheet.replaceSync(MyButtonStyles)
class MyButton extends HTMLElement {
constructor() {
/* Setup and create shadowroot */
super();
this.attachShadow({ mode: "open" })
/* Adopt the constructed stylesheet */
this.shadowRoot.adoptedStyleSheets = [MyButtonStyleSheet]
/* ... etc */
}
}
The component will now render its styles without a <link> tag or inline <style> tag in the template. If we put two MyButton components on the page, they’ll share the same stylesheet in memory which is a pretty neat efficiency gain for style application. adoptedStyleSheets aren’t limited to web components, but they’re still pretty niche. But the web component ecosystem’s pattern of using adoptedStyleSheets for style application will come in handy later when we want to apply our new custom states.
Before we jump to the grand finale, there’s one other unique-to-web-components feature we need to talk about first: ElementInternals. In a web component class you can call attachInternals() which allows you to dip into the element’s guts and mutate the innards of your spicy div. An example of this might be setting an ARIA role:
class MyButton extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals()
this._internals.role = "button"
}
}
customElements.define('my-button', MyButton)
The accessibility inspector will report a role of button for your <my-button> custom element. Setting ARIA role is a weak example because both <my-button role="button"> and Element.role = "button" both work pretty seamlessly. But ElementInternals brings other ✨superpowers✨ like adding form validation behaviors that you can’t do on a normal div. One newer capability of ElementInternals is that it allows you to create your own custom pseudo-classes and apply styles using :state().
class MyScrollingElement extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals()
}
connectedCallback() {
this.addEventListener('scroll', this.handleScroll)
this.addEventListener('scrollend', this.handleScrollEnd)
}
/* Use arrow functions to avoid `this` complications */
handleScroll = (_event) => {
this._internals.states.add('scrolling')
}
handleScrollEnd = (_event) => {
this._internals.states.delete('scrolling')
}
}
Now you can style your element when it’s scrolling using :state(scrolling) and you’re manipulating the element’s internal state, not modifying HTML in the DOM.
:host {
overflow-y: scroll;
transition: background 0.2s;
}
:host(:state(scrolling)) {
background: pink;
}
Isn’t that cool? With great power comes great spider-mans, of course, but having your own custom pseudos is a thing you can do now with web components.
Now that we have a loose grasp on all the pieces we need to make our demo work, the next steps are:
:hover with :state(hover)Here’s a simplified version of what’s happening in the CodePen version.
/* A bit of pseudo-code here... */
const MyButtons = document.querySelectorAll('my-button');
const sheet = new CSSStyleSheet();
MyButtons.forEach(async (button, index) => {
const isDefined = await customElements.whenDefined(button.localName);
// Grab the stylesheet from the first
if(isDefined && index === 0) {
// Get current CSS rules
const cssRules = Array.from(
button.shadowRoot.adoptedStyleSheets[0].cssRules
)
.filter(rule => rule.cssText.includes(':hover'))
.map(rule => rule.cssText.replaceAll(':hover', ':state(hover)'));
// Update the stylesheet contents
sheet.replaceSync(cssRules.join(''));
}
// Adopt the stylesheet
button.shadowRoot.adoptedStyleSheets = [...button.shadowRoot.adoptedStyleSheets, sheet]
// Attach internals and set the state
if(button.dataset.state == 'hover') {
const _internals = button._internals ?? button.attachInternals();
_internals.states.add('hover');
}
})
And that’s the gist of the trick: clone the pseudo-class states and set imposter custom states. I’m hand-waving some of the more complex bits like button:hover {} needs to be rewritten to :host(:state(hover)) button {}, but again, feel free to read the code.
And my demo makes a couple assumptions I know won’t scale:
<my-button> elements use the same static stylesheet (e.g. it doesn’t change per component).<my-button> only has one adoptedStyleSheet.HTMLButtonElement-specific interactions.ElementInternals already setup or already externally expose them and you know that @public property name.Next steps might be to make this a bit more robust and support all the other platform pseudo-classes like :popover-open, :user-invalid, or :playing. An agent could probably do this, not me though. I’m tired.
Shadow DOM often gets a bad rap even in web component circles. I fully understand the friction there. Everyone’s first test drive of Shadow DOM styling almost always ends up in flames, leading to a poor unboxing experience that’s hard to shake off.
That said, I feel like this demo captures a glimpse at some of the unique ✨superpowers✨ you get when styling with Shadow DOM. When coming from a global styles world, that loss of control can feel frustrating. But in my experience, with Shadow DOM encapsulation you’re gaining predictability and precise control by keeping everything local.
You could probably figure out a way to do this with global styles. It’d be more difficult with roboclass-based scoping from something like CSS Modules, but I think it’s possible. But I imagine you’d have to parse through all the global document.styleSheets and that seems like a lot of work to add a :hover state demo to a <button>. Style encapsulation here saves a lot of work.
If you got all the way here and still hate Shadow DOM, I’d love to read a post of yours about where it broke down for you. But if you use Shadow DOM on the regular, I’d love to hear why it’s great. This has been my mantra for awhile now, but I think the web component community can do more about promoting the benefits. For example, there’s only two articles about adoptedStyleSheets on CSS-Tricks which seems low.
In the meantime, while I try to convince my bosses to let me blog for a living, I’ve got my free course guidebook online and you can check it out.
If you’ve used the theme switcher on this site, you may notice it works a little differently now.
There’s been a lot of discussion lately about whether theme controls should offer two states or three. Bramus has made the case for three states, while Lea has argued that two are enough.
That discussion prompted me to revisit the way I’ve been managing themes on this site. I’d supported two themes—dark and light—and neglected the system setting entirely. I wanted to rectify that and felt the urge to build a web component to make it reusable: <theme-switcher>.
Stakeholder AI prototypes can clarify expectations and reduce rework, if designers help shape how they’re used.
Back in 2022, I wrote “There can be only one: Options for building ‘choose one’ fields” for HTMHell. The interaction sounds simple—pick one option from a list—but choosing the right HTML control involves a few tradeoffs.
A <select> is compact, but you generally need to open it to see the available choices. Radio buttons put all the options in front of you, making them easier to scan and compare, but the group typically requires more screen real estate.
What if we could maintain visibility while presenting the choices in a more compact way?
That’s where <form-multi-switch> comes in. It presents a radio group as connected segments: all the options remain visible, but a short set of labels can fit neatly into a single row. Under the hood, they’re still native radio buttons, retaining their accessibility and UI affordances.
There’s that famous video of Steve Jobs explaining why they don’t didn’t make touchscreen MacBooks:
We thought about [a touch screen MacBook] years ago. We’ve done tons of user testing on this and it turns out it doesn’t work! Touch surfaces don’t want to be vertical. It gives great demo. But after a short period of time, you start to fatigue. And after an extended period of time, your arm wants to fall off. It doesn’t work. It’s ergonomically terrible. Touch surfaces want to be horizontal […] that’s why we’ve perfected our multitouch trackpads over the years.
I’m not going to weigh in on the new MacBooks. But what I find specifically intriguing in this video — and broadly relevant — is that phrase: “It gives great demo.”
Damn, that is a great line for so much of technology.
“It gives great demo.”
Something that looks amazing when it’s presented to you, but in practical day-to-day use you discover, “Actually this is kinda terrible.”
Demo is everything nowadays. Whatever can garner attention. Who cares if it works in practice, does it give great demo? Because demo time is sale time.
The purpose of a demo is to get your attention. To make you think about the product and that it exists. But a great product often makes you forget it exists at all.
It seems like a paradox: we’re attracted to what stands out, but we become enamored with what disappears.
Of course, this doesn’t just apply to products. People can give great demo, too. You can make a whole career out of just giving great demo.
I decided not to do #NaNoWriMo this year. Part of that is because I’ve never done it. Never even attempted it. Don’t even want to. Writing a novel is a life goal of mine, but cramming it all into one month isn’t for me. November is impossibly busy. But I do think it would be cool to have a big excuse for avoiding all those obligations.
“Dave, come to this holiday party!”
“Sorry, I’m writing a novel.”
“Dave, come work at the school’s fall carnival!”
“Sorry, I’m writing a novel.”
“Dave, pay attention to your kids!”
“Sorry, I’m writing a novel.”
See, that level of self-preservation and pre-emptive boxing out of other responsibilities would be so cool. Like cigarettes cool.
Despite not having a novel excuse, I am feeling compelled to wri’ mo’ this month. Whether it’s autumnal ghosts or pumpkin spices, there’s a draw pulling me back to my text editor. A vapor of creativity stolen by corporate dementors and their infernal machines, seeping back into my lifeless shell.
Last week I powered through a handful of short stories by Arthur C. Clarke. Vintage sci-fi inspires me. Partly because the experimental short form factor containing a single “What If?” thought experiment seems achievable and because most of the stories are complete shit. In a modern world of blockbuster movie adaptations and eight volume octologies, vintage sci-fi sets the bar real low.
One story I came across was The Nine Billion Names of God. The audiobook has a sick-ass cover of a skeleton in a cracked space helmet on a dying planet with a giant sun rising in the distance. You can pause this and get through it in ten minutes.
Nothing in the story relates to that cover. Rather, the story is about two Western men that work for a fancy computer company that makes the Mark V. They’re hired by a group of Tibetan monks to help them write a program that will procedurally generate the nine billion names of God which they believe will bring about the end of the world. The Mark V will shorten the task of writing down all the possible names of God from 15,000 years of manual writing down to 100 days! Incredible productivity gains!
AAABBBAAA AAABBBABA AAABBBACA AAABBBADA AAABBBAEA AAABBBAFA AAABBBAGA AAABBBAHA AAABBBAIA AAABBBAJA
AAABBBAAB AAABBBABB AAABBBACB AAABBBADB AAABBBAEB AAABBBAFB AAABBBAGB AAABBBAHB AAABBBAIB AAABBBAJB
AAABBBAAC AAABBBABC AAABBBACC AAABBBADC AAABBBAEC AAABBBAFC AAABBBAGC AAABBBAHC AAABBBAIC AAABBBAJC
AAABBBAAD AAABBBABD AAABBBACD AAABBBADD AAABBBAED AAABBBAFD AAABBBAGD AAABBBAHD AAABBBAID AAABBBAJD
AAABBBAAE AAABBBABE AAABBBACE AAABBBADE AAABBBAEE AAABBBAFE AAABBBAGE AAABBBAHE AAABBBAIE AAABBBAJE
AAABBBAAF AAABBBABF AAABBBACF AAABBBADF AAABBBAEF AAABBBAFF AAABBBAGF AAABBBAHF AAABBBAIF AAABBBAJF
AAABBBAAG AAABBBABG AAABBBACG AAABBBADG AAABBBAEG AAABBBAFG AAABBBAGG AAABBBAHG AAABBBAIG AAABBBAJG
... ... ... ... ... ... ... ... ... ...
THIS STORY HAS NO MODERN DAY PARALLELS. An altruistic sex cult from a specific locale using a mountain of money to hasten the end of the world with their enormous computers that endlessly generate combinations of nonsense words that they believe will summon God… well, that would be a work of cheesy science fiction, not real life.
The Nine Billion Names of God flipped a switch in me and has renewed my courage to write more. As someone with ideas about ideas burning holes in my head, it’d be worthwhile to offload some of my cognitive surplus. I don’t have any solid plans, words-per-day goals, charts, graphs, storyboards, outlines, or some Benjamin Franklin life hack schedule to make this happen. Instead I have an Obsidian vault with a hundred unsorted drafts.
I don’t want to commit myself to publishing every day, but my goal is to get up and spend thirty minutes to an hour each day writing something. I want to carve out time before work, before doing my puzzles, before doom-scrolling the news. I want to use those premium dopamine reserves –the flavorful floating creme of consciousness that comes from a good night’s sleep, not the metallic bitter dregs distilled from caffeine and stimulants– I want to savor and use that precious natural dopamine on writing.
My promise to you, dear reader, is that I will write more this month. I will write no less than nine billion stories and/or blog posts by the end of November. And upon completing my works, I can shed this mortal coil and leave this forsaken world.
The float property in CSS is old school. CSS developers used to have to think about it all the time, because it was one of the mechanisms we had for building actual layouts like side-by-side columns and stuff. Those days are gone, but it doesn’t mean float as a property is entirely dead. It remains […]
CSS has two new positional alignment keywords as part of CSS Box Alignment Level 3: flow-start and flow-end. These are defined as aliases for the existing flex-start and flex-end keywords. Therefore, in a supporting browser, the following two lines should align flex items to the end of the flex container (the right assuming you are ... Making features generic in CSS
I really liked Ray Nayler’s The Mountain In The Sea. I also liked The Tusks Of Extinction. But reading those back-to-back gave me pause:
At times it seems to relish in describing the worst that humanity is capable of. It starts to veer into misanthropy.
I said:
I hope that the next Ray Nayler book I read won’t tip over into wallowing in the worst of our species.
Well, when I cracked open Palaces Of The Crow, my mind was immediately put at ease. And I mean immediately.
Before the story even begins, the book opens with two quotes. One is the closing passage of Darwin’s On The Origin Of Species describing “the war of nature”, but the other quote is from Kropotkin:
Mutual aid is as much a law of nature as mutual struggle.
That sums up the themes of Palaces Of The Crow. On the one hand, we do indeed get the worst that humans are capable of. But on the other hand, we also get not only the best that humans are capable of, but non-humans too.
As with The Mountain In The Sea, this is a terrestrial first-contact story. We’ve gone from cephalopods to corvids. And we’ve gone from the near future back to Eastern Europe during the Second World War.
I’m always nervous of any piece of popular entertainment that attempts to feature the Holocaust, but I feel that here it’s done with respect. It never comes across as gratuitous.
There are four main human characters; a Polish boy in the Soviet army, a Lithuanian Jewish girl, a young boy who never speaks, and a Roma girl. The narrative is set almost entirely within a forest.
At times this book reminded me of The Vaster Wilds by Lauren Groff. Both stories feature a young woman trying to survive in the wilderness. But whereas The Vaster Wilds is ultimately a tale of the individual, Palaces Of The Crow is very much a story of the group. And whether that group comprises humans, crows, or both, the message is clear:
Social animals are built to survive only among one another.
There is a cautionary subplot involving another denizen of the forest who goes it alone. His fate is very different to that of the group.
A story of four youngsters in a forest during World War II sounds like it should be a brutal survivalist tale. But Palaces Of The Crow has more in common with Rebecca Solnit’s A Paradise Built In Hell and Rutger Bregman’s Humankind.
Towards the end, the text sometimes can’t resist saying the subtext out loud, but this is a minor quibble with what is otherwise a beautifully constructed work.
I’m not going to lie, Palaces Of The Crow is absolutely devastating in places. But it’s also life-affirming. I know that sounds trite, but this book is the opposite of trite. It’s visceral and unflinching and beautiful.
After twenty years of working with publishers and designers, I’ve taken on every aspect of producing my new book. Here’s how I created the layouts, grids, and visual language for Standout Designs for the Web.
A momentous week in the studio. I announced my new book, finished Moonwalk Audio’s website, added WebMentions to my blog, and restarted my newsletter ahead of November’s book launch.
What do a vintage Japanese robot, a cardboard castle, a Godzilla toy, and a handful of pencils have in common? They became the starting point for an interactive website that brings Moonwalk Audio’s imagination to life.
Practically every day on the socials, under the hashtag #BrokenTimes, I bring receipts to my claim that The New York Times is failing us and democracy. I do not wish to join in the cacophony from the Trumpists about “fake news.â€� To the contrary, I do this hoping to hold The Times to account and […]
The post A day in the life of the #BrokenTimes appeared first on BuzzMachine.
CodePen has “feeds” for exploring content, like our Trending feed for showing off what’s popular lately all across CodePen, and our Following feed showing off the work of people you’ve chosen to follow on CodePen. Both of those require some fairly fancy technology to pull off! We’ve pulled this tech in-house, so we have more […]
I joined Nathan Puls on his Jobs Podcast: A conversation about making stuff, making a living, and making a little room for weirdness on the web. What if you had to be Chris? Nathan opens with a strange assignment: Chris disappears into his camper van for a week, and Nathan must impersonate him at work. […]
It’s time to celebrate a little win.
The other day, my mom contacted me saying her 10-year-old iPhone wouldn’t turn on.
After getting a hold of the device and doing some family IT support, I concluded the battery was dead.
So I bought her a new (old) one on Walmart (SE 3rd gen., if you must know).
Her old phone used a SIM card, so I was expecting the process to go something like this:
But step 4 and 5 never happened.
The new phone “just” connected. I called her number. I texted it. Everything worked as expected.
Everything worked as expected.
Everything worked as expected.
I didn’t have to Google a single question.
I was floored.
Flabbergasted.
I know I shouldn’t have been because that’s how SIM cards are supposed to work, right? Like that’s the whole promise of a SIM card: you can seamlessly swap them between phones.
And in this case, the tech worked exactly as promised.
As I write this, I’m kind of amazed at how amazed I am that everything “just worked”.
Shout out to anyone who has ever worked on SIM card technology.
More things like this please :)
If we failed to do this, I feared, we would stop leading the industry and turn into “merely the CSS trick of the week.”
The post The Day the Focus Died appeared first on Jeffrey Zeldman Presents.
A favorite actor just passed away.
I’ve written another book! Standout Designs for the Web explores why so many websites look alike and how designers can create something more distinctive. Here’s what inspired it, what’s inside, and when you can get your hands on it.
There’s a lot to miss about Arc (RIP), but the feature I miss the most has got to be automatic picture-in-picture. You start a video and when you change tabs to jump to another task, the video anchored itself to the corner of your desktop. Go back to the video tab and it slurps up into the video player area. A perfect experience with no interruption.
I switched back to Edge (for work) and missed Auto-PiP a lot. I tried building a custom browser extension to restore the functionality myself but hit a platform limitation with the mix of WebExtensions, PageVisibility , and MediaSession APIs you need to get it to work seamlessly. You had to configure each site in a dropdown and run a script each MediaSession… it asked a lot of the user.
There’s been a handful of Chromium flags and experiments to add “Auto-PiP” over the years, but nothing did it as well as Arc did… until…
about://flags#browser-initiated-automatic-picture-in-picture
Chrome 142 introduced a new browser-initiated-automatic-picture-in-picture flag and it behaves the same as Arc. The browser does all the work popping the video in-and-out when expected. I couldn’t believe this near-exact replica was hiding in plain sight all this time! But I couldn’t use it because the flag didn’t exist in Edge 😭
I started up a couple internal threads about it last year but it went nowhere. When the topic came up again this year, I mentioned to Greg Whitworth, “Y’know there’s a flag in Chrome that does Auto-PiP great…” and that next week the flag showed up in Canary and I’m happy to say it’s available today in mainline Edge 156.
If you missed that feature in Arc and didn’t know it existed in Chrome or felt crushed by it not existing in Edge… then head over and turn on the flag today, I think you’ll be happy. The only thing it’s missing for me are the juicy physics-based animations that let you two-finger swipe and throw the picture-in-picture window around and pinch-zoom to resize. But it feels like the bones are there. And it feels like a good time to remind my fellow Edge users that Edge has a little feedback form inside the Three Dots menu > Help and Feedback > Send Feedback.
Here’s Dave Rupert writing about all those emails, texts, phone calls, etc. we’re getting these days:
Once upon a time it had utility, now it’s all noise and grifter slop demanding our attention and giving us to-dos
That last part really hit: “giving us to-dos”.
I feel this. So many of the messenger apps on my phone are other people laying claim to my time and attention.
It reminds me of an interaction I had with my Dad years ago.
This was back when cell phones were starting to become ubiquitous.
Not smartphones, mind you. Cell phones, i.e. telephones that were mobile. Texting wasn’t really a thing yet. These were devices for making voice calls wherever you went.
As a teen, it seemed like everyone had one: my teachers, my friends, even my friends’ parents.
But not my parents.
They were “behind the times”. (Everyone was getting cell phones and we didn’t even have caller ID on our landline!)
One night at family dinner I asked, “Dad, why don’t you have a cell phone?”
His response?
“Why would I want to allow other people to contact me whenever they want?”
I still think about that.
Back at Ability Summit, I moderated a conversation about shifting accessibility left in the age of AI. During that discussion, Yumeng Ma described a problem she’d encountered in her research: there wasn’t a magical prompt that made generated code accessible. The component mattered. The model mattered. And what we chose to measure mattered too.
That research is the subject of a paper I co-authored with Yumeng Ma, Jacob O. Wobbrock, Joe Devon, Michael Fairchild, Jennifer Mankoff, and Venkatesh Potluri: “Prompt and Circumstance: Evaluating Accessibility in LLM-Generated HTML Form Components”, for ASSETS ’26, the ACM SIGACCESS Conference on Computers and Accessibility.
This is an infuriating book. So much so that I didn’t finish it.
Don’t get me wrong; I love the idea of The Pirate Queen. Telling the life story of Gráinne Ní Mháille is an excellent premise for some rip-roaring historical fiction. It’s even got an interesting structure alternating between three different parts of her life.
I was pleasantly surprised when I cracked open the book to find a small pronunciation guide to some Irish words in the front matter. It would be nice to have some Gaeilge scattered throughout the narrative. But that’s exactly what made this book so infuriating.
First off all, by dropping in an occassional Irish word into a sentence, it just made me think, “Wait, what language would these people be speaking?” Usually the answer was Irish. In which case, why translate one arbitrary word?
Then there’s the choice of the Irish word that gets dropped into the text most frequently…
The front matter has this to say about the irish word tá:
An affirmative statement similar to yes
Um, not really. Tá is present tense of the verb “be”. The English equivalent is “is”. You can’t just drop it in as a replacement for the word “yes”. But that’s exactly what Ariel Lawhon does.
This continually took me out of the story. One person asks a question like, “Did you see that?” and the other person answers, “Tá.” That just doesn’t make sense.
I know this sounds really nit-picky, but one of the first things you learn about Irish is that there is no single word for “yes” or “no”. It’s most common to respond with the positive or negative form of the verb in the question—“Did you see that?” “I saw!”
So of all the words to pointlessly switch into Irish, “yes” is by far the least suitable.
It happened every couple of pages. I was getting so annoyed, I flipped ahead to the acknowledgements to find out who was consulted on the use of Irish in the book. I found my answer. No one.
That seems somewhat irresponsible to me. It borders on being insulting. Would it have taken so much time and effort to have one single Irish speaker check the text?
Alas, the lack of research wasn’t just evident in the book’s treatment of the Irish language.
At one point, a young Gráinne is out walking with her brother, who is playing the uilleann pipes. I had to stop and check the text a few times. Perhaps I missed a bit where they sat down. But no, this book blithely describes a character walking and playing uilleann pipes at the same time.
Now if you think uilleann pipes are pretty much the same as Scottish highland pipes, this would make sense. But they’re not. A bare minimum amount of research would show that the uilleann pipes are played sitting down (you can try to play them standing up, but you’ll need some kind of extra gadget like Johnny Doran used, but even he couldn’t play them while walking).
I carried on reading even though I was getting more and more annoyed by the shoddiness on display. Then I read this sentence:
No more distant flicker of lanterns in the distance.
I get how you would write that sentence in a first draft. Hopefully you’d catch its redundancy in a subsequent draft. If not, it’s exactly the kind of thing that your editor is there to spot.
But no, this made it all the way to the final printed book, a book that was displayed prominently on tables in major bookstores in multiple countries. Compare and contrast that with When The Boys Came Out To Play by John Mackel, a book which will be likely only ever be read by a much smaller audience but which is crafted with so much more care.
I always feel slightly guilty if I don’t finish a book. I know, I know; life’s too short and all that. But I can’t shake the feeling that I’m being a bit disrespectful to the author by not seeing their work through to the end. In this case though, the author displayed enough disrepect to her readers that I don’t think I need to feel bad about slamming this book shut.
The Pirate Queen is a sloppy piece of work. You deserve better.
Good friends and family know this, but I am bad at email. Like chronically bad. If you have tried to email me, you know this too and you either never got a reply or received one 9-months later starting with the phrase, “Sorry.”
I am truly sorry… but also I’m not. It’s a function of who I am.
I was never great at email, but in a slower time before social media it raptured my attention. I would habitually refresh my inbox hoping for a long range communication from a friend or stranger. When I was homesick in Japan, a good email (or AIM chat) could turn my day around. I have fond memories of email and its unparalleled ability to connect me to other people through a single address. It of course would never be as familiar or intimate as scribing a hand-written calligraphic letter with a big feather pen, but it proved a fine substitute.
At Paravel, I noticed that emails had a propensity to crash into my day with some new urgent task. A ding, a red badge, a bouncing icon in the dock. Could be a client or my accountant; something that required me to stop what I was doing and switch over to their need. Frank once described emails as (paraphrasing) “a to-do you send to someone else” and that resonated in my soul. For someone with an attention regulation disorder, intermittent distractions like email (or chats) can be devastating to staying focused on the work I actually need to finish.
Email being disruptive to your productivity must be a pretty common phenomenon because nearly all the productivity books out there –and I’ve read them all– attempt to “solve” productivity by turning off your notifications and checking your email less.
I must have really taken this advice to heart because I almost never check my email anymore. After a long day of expending dopamine on work, the last thing I want to do at home is check my personal email and receive more to-dos from strangers. I tend to check my personal email now once a week… or less.
Inbox Zero is a fool’s errand for me, but I try to keep my work email groomed and as close to zero as possible. Part of that was because I started with a blank slate and could actually succeed. Each morning I make an analog to-do for myself to check my work email. I set up some priority inboxing and auto-deletes for org-level notifications. Importantly, work email stays sequestered to my work laptop.
But despite the success at work, I’m sitting at nearly 500+ unreads in my personal Inbox. It doesn’t feel great and feels like a monumental task now. Since starting at Large Company I know of at least two conferences that I’ve missed out on speaking at because I’m so bad at email (sorry, PPK). If you’ve ever wondered why I don’t have a contact form on my site or do that cool “email me, let’s be friends” link in my RSS footer, this is why… because I feel shame that I will disappoint you.
There’s also the diminishing utility of email. As with phone calls and texts, the amount of spam and noise has ruined the service. It’s a needle in a haystack to find the one good email that actually deserves your attention. Just because we participated in online commerce once doesn’t mean you get the liberty to email me for the rest of my life. I wish there were more laws about this.
When did a ten drip email campaign for every online transaction become the standard? You bought an item → Item is getting ready to ship → Want another item? → Item shipped → Need more items? → Item out for delivery → The holidays are coming up → Package delivered → Let us know how we did → Mother’s day coming up → Father’s day coming up → Step-father’s day coming up → etc… It’s entirely too much.
And the grifter slop… oh, my God the grifter slop!
Hey Dave,
I came across your now-defunct company Luro. Design systems is an interesting space. Would you like to increase Luro users by six billion percent through spam email blasts? We can help.
Chad, CEO
I will never reply to this and–
Dave,
I thought I’d follow up in case you missed the last email. I’m a fucking robot obviously but we can have a conversation about this.
Chad, CEO
Grifters, spend six minutes crafting a real email and I promise you it will convert better. Or have an actual fucking product that is so good I hear about it from other people. You are one of five hundred anonymous Chads in my inbox, this pickup line doesn’t work anymore.
Along those lines… Democrats, sending text messages doesn’t work anymore. I got AOC, Nancy Pelosi, Beto, and even Raphael Warnock’s grandma texting me every other day and it makes you look broke and desperate like a friend I’m gonna need to bail out of jail again. Capture me with ideas.
That’s how I feel about email. And text messages. And phone calls. And social media. Once upon a time it had utility, now it’s all noise and grifter slop demanding our attention and giving us to-dos. Be hotter, be smarter, be more liberal, be more productive, be more, be more, be more.
Oh, and I underpaid my taxes and owe the government $5400. That was the email I missed.
In an internal analysis of accessibility bugs, a colleague found that roughly half could be traced back to design decisions. These were issues we should have caught earlier, before those decisions became codified in code and long before anyone encountered them in a shipped product.
That’s one of the reasons I started working on Accessibility Assistant for Figma. I wanted to help designers catch issues while they worked and help them build their accessibility knowledge along the way. Version 53 brings more of that work together, organizing annotations, assessments, issues, and handoff around projects.
I stopped going to conferences and lost the hallway chats with peers. With AI changing the job, we need them more than ever. I'm starting UX Insiders.
I’ve mentioned before my interest in the history and development of nuclear weapons.
Currently, I’m reading The Doomsday Machine: Confessions of a Nuclear War Planner by Daniel Ellsberg (of Pentagon Papers renown).
In the first chapter, Ellsberg talks about his time working at the RAND Corporation during the missile gap years when the U.S. thought it was falling behind the Soviet Union in nuclear capabilities.
Many of his descriptions sound strikingly similar to the rhetoric coming out of places like Anthropic. For example, note the religious overtones of working at a corporation:
I loved RAND, where I ended up spending ten years [...] Much, I imagined, like members of a religious order would, I shared with my colleagues a sense of brotherhood, living and working with others for a transcendent cause.
They were insiders: burdened with the responsibility of knowledge others did not possess.
precisely in my early missile-gap years at RAND and as a consultant in Washington, there was our sense of mission, the burden of believing we knew more about the dangers ahead, and what might be done about them, than did the generals in the Pentagon or SAC, or Congress or the public, or even the president. It was an enlivening burden.
And what was their “enlivening burden”? Saving the world:
[My colleagues] shared a feeling—soon transmitted to me—that we were in the most literal sense working to save the world.
What really stood out was this part, where Ellsberg describes how he and his colleagues truly believed the end was nigh:
From the Air Force intelligence estimates I was newly privy to, and the dark view of the Soviets, which my colleagues shared with the whole national security community, I couldn't believe that the world would long escape nuclear holocaust. Alain Enthoven and I were the youngest members of the department. Neither of us joined the extremely generous retirement plan RAND offered. Neither of us believed, in our late twenties, we had a chance of collecting on it.
This reminds me of the report that AI researchers have taken up smoking as a way to signal their belief that AI will cure cancer before smoking kills them.
At this point, I wouldn’t be surprised to hear a lab like Anthropic announce they’re discontinuing all employer-sponsored retirement benefits. It’d be a small sacrifice on behalf of the employees to demonstrate their faith that universal high income is just around the corner. (The corporate cost savings would just be a nice bonus.)
It warms my old heart to hear Jason Santa Maria wax poetic about modern CSS: CSS has exploded! Flexbox, grid, many kinds of viewport units, variables, and on and on. So many things we used to patch in to extend CSS or turn to Javascript for are now just part of the spec. The ease […]
Chris and Alex are talking this week about CodePen’s biggest release since the 2.0 drop. It’s live! We’ll be working on CodePen AI forever, since it’s a fast-changing technology that regularly adds new abilities. That sounds like a trite thing to say, but we lived it as we were building this. We started out playing […]
℅ Automattic Design:
The post Figma Tips and Shortcuts appeared first on Jeffrey Zeldman Presents.
Have you heard the good news? I bemoaned the approach of putting an icon in every menu item by default which shipped with macOS Tahoe — but now it’s gone in Golden Gate!
John Gruber wrote about this reversion back in June, including the fact that Apple’s new Human Interface Guidelines specifically call out Tahoe’s approach to menus as an example of what not to do on macOS.
As the old saying goes, it takes a big man corporation to admit when they’re wrong.
As I mentioned in my original post, one of the arguments I had in my quiver against this practice was “Apple doesn’t do it”. But once Apple started putting icons in all their menus in Tahoe, I lost a persuasive point in my argument.
But now, I think I have an argument that’s even stronger than before!
It’s no longer: “Apple doesn’t put icons in their menus.”
It’s: “Apple tried putting icons in all their menus, and even they went back on it!”
They designed it, shipped it, lived with it for a year, then publicly changed their guidance on it. To me, that’s a lot more persuasive than simply saying, “Apple doesn’t do this.”
Choosing not to do something you’ve never done is one thing.
Choosing not to do something you’ve done is experience.
That’s the essence of life: learning by doing.
A while back, Eric Meyer emailed to ask about my old post arguing that CSS variables were a bad idea. Did I still feel that way?
No. I’ve changed my mind.
For your listening pleasure: 17 new tracks of instrumental music pulling from jazz-funk, Italian movie soundtracks, electronica, hip-hop, art rock and beyond.
The post Now Playing: “Side Two” appeared first on Jeffrey Zeldman Presents.
Last week, I continued developing Moonwalk Audio’s new website, turning a collection of sketches and objects into an interactive SVG fantasy world. There’s still plenty to do, but the most extravagant animation I’ve created so far is taking shape.
The Firefox team are asking us to weigh in on proposals for Interop 2027—what features should browsers be concentrating on implementing or improving.
<install> and navigator.install(). Prompts the user to install a progressive web app.var() in size container queries. Such as @container (width >= var(--min-width)).flex-wrap: balance. Distributes items in a multi-line flex container so each line has about the same number of items.interestfor attribute triggers actions on a target element when the user shows interest, such as via hover, focus, or long-press.Those are very much in order of priority. Imagine them following a Pareto distribution; the top 20% (in other words, the first one) is 80% of my priority.
But other people have other priorities: Stu, Eric, Ana, Tyler, Paweł, Declan, Sara, and Cyd, for example.
There’s a lot of love for styleable select in there—can’t argue with that.
Get in there and rank your priorities. Then share them.
Hot on the heels of reading and enjoying The Mountain In The Sea, I picked up Ray Nayler’s next work, this tightly constructed novella.
Once again it deals with inter-species communication. This time it’s de-extincted mammoths. Once again, it’s set in the relatively near future. Many of the same gadgets reappear in this book. It may well be set in the same world.
There’s a tangible atmosphere to this story. But despite it’s high-tech premise, it’s not escapist. The narrative is driven by the all-too-real issues of ivory poaching and big-game hunting.
And this is where I start to get a nagging feeling about Ray Nayler’s work. At times it seems to relish in describing the worst that humanity is capable of. It starts to veer into misanthropy. If you yourself are misanthropic, it will just come across as realism. But it feels a bit too easy, a bit too cheap.
I remember this happening with Paolo Bacigalupi’s books. I enjoyed The Wind-up Girl, but by the time I read The Water Knife, his misanthropy (and misogyny) had become self-parody:
Instead of asking what the future might actually be like, it instead asks “what’s the absolute worst that could happen?” Frankly, it’s a cop-out.
The book takes a similar tack with its characters. It assumes everyone’s terrible and will do terrible things. It’s lazy.
So you’ve got an unrelenting series of people behaving terribly in a horrific setting. It gets boring.
The Tusks Of Extinction isn’t that. It manages to maintain a balance. But I hope that the next Ray Nayler book I read won’t tip over into wallowing in the worst of our species.
I’ll find out soon enough. I’ve already got Palaces Of The Crow on loan from the library.
Back in 2019, I read the book Other Minds: The Octopus, the Sea, and the Deep Origins of Consciousness by Peter Godfrey-Smith and said:
I really enjoyed this evolutionary tale. It’s equal parts biology and philosophy. I will never look at cephalopods quite the same way again.
Reading Ray Nayler’s The Mountain In The Sea reminded so much of that book that I couldn’t resist flipping ahead to the acknowledgements at the back. Sure enough, Other Minds gets a big shout-out.
But that’s only part of the make-up of The Mountain In The Sea. There’s just as much William Gibson as Peter Godfrey-Smith.
I don’t just mean that the book is an unexpectedly rip-roaring cyberpunk thriller—which it is—but that it’s also got that Gibsonian narrative structure; three disconnected protagonists in three separate stories that you just know are going to connect before the book is done.
The great accomplishment of this book is how well it balances the two genres of philosophy and cyberpunk. There’s plenty of musings on the nature of consciousness, be it human, octopus, or artificial intelligence. But there’s also plenty of high-tech hacking shenanigans.
Above all, it’s an excellent example of one of my favourite subgenres of science fiction: first contact. It’s as linguistically satisfying as Ted Chiang’s Story Of Your Life, the pinnacle of the first-contact subgenre. The fact that there are no extra-terrestrial species just adds to its realism.
From where I sit, figuring out what to build, building it, and shipping it are all part of the art of making software.
Many disciplines have emerged through the years to facilitate this work: product, design, engineering, devops, etc.
We need all these disciplines to make great, dependable software (granted, in different doses). Each brings different values and judgments; each sees things the others don’t.
And including all might mean going a bit slower.
Human collaboration may seem less “efficient” than handing the work over entirely to the machines.
But inefficiency may be the cost of collaborating with humans, rather than extracting an output from a machine. We gain not only from their judgment and expertise in a particular discipline, but from the relationship, trust, and shared understanding that blossoms from making something together.
As the old proverb says: “If you want to go fast, go alone. If you want to go far, go together.”
Reformulated for today: If you want to go fast, go with the LLM. If you want to go far, go with your fellow humans.
Remembrance of Poohs past.
The post Silly Old Bear appeared first on Jeffrey Zeldman Presents.
In which I explore why I ranked what I did for Interop 2027.
Marie and Chris are on the podcast to talk about our very new v2 API. This baby (slaps the hood) can do it all. It’s complete read/write access to all your Pens, Collections, and Assets. This API isn’t hamstrung by anything, so the door is open to whatever you want to build. We’ve already seen […]
The term “no-brainer” refers to a suggestion that is so compelling that it would be foolish to even consider not doing it.
I came across a no-brainer recently, thanks to Matt Hobbs:
Make all UK Government websites free to access without mobile data charges.
When I was done slapping my forehead at how obvious this proposal is, I signed the petition. If you’re a UK resident, you should do the same. And then spread the word.
This is the kind of proposal that I imagine any politician would be happy to support, regardless of party affiliation, so don’t be shy about contacting your local MP.
I guess in theory this could be opposed on the grounds that it technically violates net neutrality, but I would hope that the obvious societal benefits would outweigh that objection.
Like I said, a no-brainer.
I finally got around to importing my whole historical Twitter archive (just my own original tweets, not replies or retweets) into this WordPress site as a proper Custom Post Type: https://chriscoyier.net/tweets I haven’t added it to the navigation or anything else on the site yet. Not sure what I want to do there. Just feels […]
Look at this clock. It’s a clock. I bought it once because I needed… a clock. It’s $29, which is totally reasonable for… a clock. Then it was Daylight Saving Time one day, and I went to update it, but… it was already updated. That’s because: The Atomic Time Feature conveniently allows the clock to […]
Many years ago, I made a little jQuery plugin called Easy Sortable Tables. It works exactly as promised: point it at a table and it upgrades the UI so a reader can sort the rows by clicking a column heading. The functionality is still useful, but I can’t even remember the last time I added jQuery to a site.
The web platform’s changed quite a bit since I wrote that plugin, so I decided to modernize it as a web component: <table-sortable>.
I wrote the chapter on app icon history for The iOS App Icon Book.
The other day I found an old note stashed away where I had started researching the macOS version of that chapter (which Michael ultimately ended up writing).
At the time, I didn’t know what to write. I mean, how do you write a history of icon design for the Mac? It’s rich!
To get my creative juices flowing, I started by researching the different docks in macOS through the years and compiled them all into a single graphic (big shout out to this compilation of images on 512 Pixels).
I just wanted to preserve and archive that image. Check out the high-res version here.
That’s it, that’s the post.
I remember working in digital prepress decades ago that .eps files were just kinda weird, and I didn’t love it when I had to deal with them. Like, what even made this? Why? I assumed it was some kind of antiquated software or practice, and I’d just deal with it, and eventually the world would […]
Pete Millspaugh has a lovely article over on WIRED that peers into the world of domains. (If you’re as interested in the esoteric details of domains as I am, follow Pete’s writing at Dot Com Press.)
You should just read the article, but here are a few things that stood out to me.
Did you know who the registry for .com is? I didn’t.
The registry that owns .com, Verisign, makes a billion and a half dollars every year just by keeping the lights on. To Verisign’s credit, the lights never go out: It reports 100 percent uptime over the past 28 years. It’s a pretty slick business model, watching over a database of words and collecting annual renewal fees. Warren Buffett thought so too, investing in Verisign in 2012, when its stock price was about one-eighth what it is today.
Why is it simultaneously surprising and not surprising that the company running .com makes billions and has 100% uptime over almost three decades?
Maybe GitHub should reach out for some advice, lol.
Apparently the last time you could buy generic top-level domains (gTLDs) was in 2012.
So now that ICANN is providing another window of purchases in 2026 — and nobody knows when another window will open — there’s a lot of interest in capitalizing on these moments for new gTLDs.
most organizations [are] extremely secretive about their plans. One applicant from the last round tells me companies were flying around the world, meeting in person to avoid any risk of digital leaks, avoiding Gmail for fear of Google spying. Instead of email or video calls, she flew to London, then to Madrid. “We did it all in person. People got a little weird and scary, super paranoid.”
There’s a certain irony to this — people flying around the world to conduct business on owning a piece of the internet because they don’t trust the internet.
It appears the future of the internet is to be negotiated offline lol.
We haven’t had new gTLDs since 2012. Why do we need more?
I can imagine a future where expressive top-level domains become our personal brands, identities on the internet. This is already true in some nerdy corners of the web, like on Bluesky, where your domain is your username. I don’t call my internet friends Neal and Luke, I call them neal.fun and luke.zip. Instead of joining the land rush to grab our username on each new social media site, we’ll carry our domain around the web with us.
I’m telling you, domains are the OG handles on the internet.
More domains mean more opportunities for individual expression at the root layer of internet infrastructure — that makes sense for a web that’s meant to be worldwide!
As developers, we often joke about our addiction to buying domain names for our side projects.
But buying a domain name — e.g. my-side-project.com — is child’s play compared to buying a generic top-level domain name (gTLD). That’s where the domain whales are.
ICANN charges a $227,000 application fee for each string, to recover the cost of running the program. The $36 million or so that Nova will need to cover those fees […] is coming from the personal bank account of Yoni Belousov, a reclusive domain investor (industry jargon: “domainer”) who’s been in the business since he was 14 years old.
Next time somebody asks me what I’d do with an insane amount of wealth, I now have the jargon for an answer: “I’d become a domainer.”
A few things about domains I didn’t know until I read this:
all two-letter extensions are off the table. Those belong to countries, like .co in Colombia and .me in Montenegro and .ai in Anguilla.
If you want to run a geographic TLD, like .nyc or .paris, you need approval from that municipality
your string can’t be too similar to an existing one (.corn is a no-go; looks too much like .com)
certain special strings are off limits, like .icann and .www (wouldn’t that be a trip).
www.www would be a baller domain! Just think of the recursion you could do with that on the subdomain level.
Dave, from #735: Never trust an adult over 35 who can’t explain what they need from you in a bulleted list. I’m thinking like a laptop sticker or t-shirt, right? A little AI slop to help pick a direction can be helpful. We’ll see we’ll see.
If you liked that, have you considered you might enjoy: more of that
I love the Web platform, and it’s a privilege that we, the web community, get a chance to influence what browser makers work on every year.
For the second year in a row, Firefox has created a page that allows us to rank the Interop 2027 features that we care about the most. Hopefully this is becoming a yearly tradition.
Ranking our favorite features gives the Firefox team data they can use when reviewing which proposals should be taken on in Interop 2027: a yearly project with the goal to increase the interoperability of key features of the web platform across browsers—a dream come true for people who build the web.
I’ve been seeing some of my web friends blog about their rankings. (I’ve shared links to the blog posts I’ve seen and read at the end of this post.) So, instead of just posting about my top choices on social media, I decided to join in with a short blog post—a chance to breathe some life into this blog again, with more blog posts scheduled to come soon.
My choices are primarily based on a feature’s intended use cases/outcome; and my favorite features are the ones that allow us to improve both our websites’ user experience (UX and accessibility) and our developer experience (DX) at the same time.
Here are the features I am looking forward to the most and that I hope make it to Interop 2027. Only the first three are ranked. I don’t necessarily have a ranking preference for the remaining ones; they all got a vote from me and would sure be great to see them supported across browsers next year:
focusgroup attribute.ariaNotify() API.Both of the above features—when used correctly and responsibly—enable us to improve the accessibility of our user interfaces and reduce the amount of work we need to do manually ourselves to achieve that.
headingoffset and headingreset attributes and the CSS :heading() pseudo-class (a whole package, yay!)border-shape property. I can’t wait to ditch the old hacks when creating simple borders shapes for a simpler and more standard approach.interpolate-size and calc-size(). We’ve been waiting for these for wayyyy too long.display and content-visibility—for all those nifty UX-improving animations on common UI patterns and components like dialogs.<select>. This one’s long overdue.flex-wrap: balance. More Flexbox magic that’s nice to have.corner-shape property. Because well you know. Nice corners are nice to have and can be tricky to create.reading-flow and reading-order properties. If we get a way to break user expectations in a page layout, we must get a way to undo the effect of said ways.object-view-box property. I’ve long wanted to control HTML images the same way I can control SVG images using the SVG viewBox attribute and this is the closest we’re getting to that!@counter-style. A nice little customisation touch to our lists.We don’t get to downvote features. The ranking page states that if there are features you don’t know or even feel negatively about, leave them unranked.
There are a few features I frankly don’t care for. Some that I don’t know about. And one or two that I feel negatively about.
Favorite picks are subjective and tend to stem from our experience and professional needs and wishes. I loved reading what some of my friends have chosen and why. Here are Interop 2027 rankings from:
It was heads-down in the studio last week, working on Moonwalk Audio’s new website and finishing my book. But the week started with a bang, then another bang, then a crash.
Not much screen time for me lately, so this is a few months’ worth. But I have enjoyed some things! I have all these shows that I really wanna finish, but just can’t get the gumption to do it. Lanterns didn’t hit for me. I’m only slogging through Hacks season 2. I would love to […]
Just noting Mat’s Multipage Version zine rules. I bought both issues right away. It’s weird, it’s funny, it’s a work of art, and you might even learn something. The page that is a spliced version of Marc Andreeson proposing the IMG tag in HTML with all his recent crazy shit about how he “has” zero […]
Fun seeing CSS be a topic in WIRED. Pete Millspaugh shouting out some greats: One programmer recently re-created Doom in CSS. Others have used it to build holographic Pokémon cards and 3D-rendering engines. Cassidy Williams, who competed in this year’s inaugural March MadCSS tournament, built an experimental database-query engine entirely in CSS. I mean, hell […]
Apple’s WebKit team recently opposed the WebMCP proposal, arguing that it creates a separate semantic layer for agents when we should instead improve the shared layers people, assistive technology, and agents already use. Jason Grigsby’s post led me to Apple’s comment, and I share the concerns raised in both. In fact, I’d made a similar case over email back in April, when I first learned about WebMCP.
AI adoption is failing for the same reasons digital transformation did. It's a people problem, and this time staff fear for their jobs.
How it started: How it’s going: This is my prescription: I think it means nearsightedness. I’m mostly fine with close-up objects (like my phone/computer), but things 10+ feet away are blurry. The glasses help! Nice for driving and being inside large buildings. As my vision got worse, I honestly thought I just had watery eyes […]
I keep coming back to tabbed interfaces.
We’ve made several upgrades to uploading/hosting images on CodePen lately. One change, moving to the open-source file-uploading library Uppy, was driven by business concerns. But the benefits are good for everyone: uploads are faster, and the money we saved let us increase file size limits and total storage limits on the higher PRO plans. Plus, […]
My sister is in a currently-running-on-TV political ad. It’s against the republican chucklefuck Tom Tiffany who was one of those anti-American dingleberries that claimed Biden “stole the election” and thinks Trumps tariff plans are super neato. Kelsey’s journey with healthcare insurance has been pretty rough. She gets denied for medications and treatments all the time, […]
There’s a new web platform feature flighting in Chromium called Declarative Partial Updates. This proposal adds a new <?marker> element which enables out-of-order streaming of content. Rather than the traditional way of building the HTML you need on the server, or sending JSON down to a JavaScript framework, you can staple HTML onto the initial response and use the <template for=""> syntax to inject that content into the <?marker name=""> placeholder.
A simple example of this would be:
<!-- NOTE:
FYI, throughout this post I'm using the closing `?>`
syntax for `<?marker>` becuase the HTML highlighter
likes that better. Either syntax works.
-->
<main id="main">
<?marker name="post-abc123" ?>
</main>
<!-- ...bunch of html goes here... -->
<template for="post-abc123">
<div class="post">
<h2>Hello world</h2>
<div>First post</div>
</div>
</template>
The late-arriving template will auto-magically get slurped up into the <?marker> placeholder slot. Unlike the <slot> element though, once you use <?marker> it’s done. You burned that card and it doesn’t work a second time. If you want to chain more than one post together and create a feed of posts, you can repeat the placeholder with the same <?marker name="post-abc123"> at the bottom of the appended item.
The essence of the feature is that you can open a server connection, never close it, and keep pumping more HTML onto the page. The <?marker> element does the heavy lifting of taking that new HTML and putting it in the right place. There’s lots of potential here (e.g. my beloved HTML Imports?) but I think the canonical use case would be how an LLM chatbot could stream in content that generates at different speeds:
<div class="chat-response">
<?marker name="chat-response-abc123-widget"?>
Markdown formatted text goes here...
</div>
<template for="chat-response-abc123-widget">
Hi, sorry I took so long! Here's a whole-ass application.
</template>
That will create some other problems like FOUC and LCP hurk-jerk you will have to manage –and that’s for another post– but lazy-loaded HTML rendering adds an interesting capability to the web platform. It took me a couple walks around the neighborhood to understand how I could potentially use <?marker> and out-of-order rendering on a normal workday.
The rule of thumb I came up with is that <?marker> might be a pattern to reach for any time where you want important content to show up first and then you kind of don’t care when other content shows up below the fold.
You get the gist, a classic relational data situation. The example I’m going to go with is a nested reply thread like a Reddit thread or Bluesky post.
Building a nested thread of replies isn’t rocket science, but you have to do a lot of for-looping to first build up the tree of posts you’re going to render…
→ Fetch all root posts in thread
→ Loop through posts
→ Fetch replies for each root post
→ Loop through root post replies
→ Fetch replies for each reply
→ Add nested reply to parent reply
→ Add parent replies to root posts
→ Walk tree
→ Send HTML for all posts
Assuming the data tree gets stitched together by the database or our server backend, your first attempt at rendering nested content will probably be the most literal approach: create a for-loop for each level of content…
// pseudo-code
const postsTree = postsDb.find(p => p.thread_id === 'abc123');
postsTree.map(post =>
html`<div id="post-${post.id}" class="post">
<div>${post.username}</div>
<div>${post.body}</div>
${post.replies?.map(reply =>
html`<div id="post-${reply.id}" class="post">
<div>${reply.username}</div>
<div>${reply.body}</div>
${reply.replies?.map(nestedReply =>
html`<div id="post-${nestedReply.id}" class="post">
<div>${nestedReply.username}</div>
<div>${nestedReply.body}</div>
</div>`
).join('')}
</div>`
).join('')}
</div>`
).join('')
An obvious limitation here is that we’ve hard-coded ourselves to three levels of nesting. Adding more nesting would require backend and frontend work and break the classic “Rule of three” programming rule. Hard-coding your nesting is the “dumb way” to render nested content… but also the normal way.
Philosophically-speaking, UI should be agnostic to the content it renders. If nested content is a possibility, a content renderer should theoretically support infinite nested content. Practically-speaking, most of us have learned that it doesn’t matter if the Product Manager’s Jira ticket says “The max is two nested items”, there will always be at least three.
The smarter way to render this is to use recursion because nested for-loops are BAD and you are DUMB if you use them. But if you obfuscate the nesting into a function that calls itself, then that’s ✨recursion, baby✨ and you’re a GENIUS programmer.
Jokes aside, recursion will clean up the template code and simplify getting HTML on the page.
→ Fetch all posts in thread
→ Loop through posts
→ Build a map
→ Loop through map
→ Build a tree
→ Walk tree
→ Send HTML for all posts
Before we grabbed all posts, then all replies, then all nested replies. Walking that tree is a bit slow. We can make that faster by fetching a flat list of posts in the thread and then assemble a tree. You can do this with some crafty LEFT JOIN SQL statements but its generally easier on the server.
buildPostTree: building a nested tree from a flat list of postsfunction buildPostTree(posts) {
const postsById = new Map()
// Loop through posts to build map
for (const post of posts) {
postsById.set(post.id, { ...post, replies: [], })
}
const roots = []
// Loop through map values to build tree
for (const post of postsById.values()) {
// Parentless posts must be roots
if (!post.parent_id) { roots.push(post); continue; }
// Find this child's parents!
const parent = postsById.get(post.parent_id)
// Add child post to parent's replies
parent.replies.push(post)
}
return roots
}
Then instead of hard-coding nested for-loops, we can recursively walk the tree to render the HTML for the posts.
// pseudo-code
const posts = postsDb.find(p => p.thread_id === 'abc123');
const postsTree = buildPostsTree(posts);
const renderPost = (post) => html`
<div id="post-${post.id}" class="post">
<div>${post.username}</div>
<div>${post.body}</div>
${
// Loop through post replies and render the HTML
post.replies?.map(reply => renderPost(reply)).join('')
}
</div>
`;
postsTree.map(post => renderPost(post)).join('')
That’s a more robust and understandable way to handle infinitely nested content. Our code is more durable to future changes. Need more or less nesting? Sure, we don’t care. That’s a data concern not a component rendering concern.
Even though we’ve simplified and reduced lines of code, both strategies have the same bottleneck: You need to build the entire tree before you can render the HTML.
The user waits for us to stitch the data together and the user waits for us to walk the tree and render synchronously. That’s two render blocking operations to put a thread onto the page.
The <?marker> element gives us another path…
First we setup our page to receive the HTML, like we did at the top of this post…
<main id="main">
<?marker name="post-abc123" ?>
</main>
And our rendering strategy gets a lot more simple…
→ Get all posts in thread
→ Loop through posts
→ Send HTML for post
And code looks a little something like this…
/* get all root posts */
const posts = postsDb.find(p => p.thread_id === 'abc123');
const renderPost = (post) => html`
<template for="post-${post.parent_id ?? post.thread_id}">
<div id="post-${post.id}" class="post">
<div>${post.username}</div>
<div>${post.body}</div>
<?marker name="post-${id}">
</div>
<?marker name="post-${parent_id ?? post.thread_id}">
</template>
`
/* loop through posts */
posts.forEach(post =>
main.appendHTML(renderPost(post))
);
No tree building is happening whatsoever on the server. The microsecond after we generate the template in the middle of the for-loop we can stream/send the HTML directly to the client and let the page assemble itself. No recursion, no pre-assembly required. It all snap-fits together without us imperatively injecting content.
A brief demo in CodePen. I recommend reading the HTML first, imagine that’s the order it streamed in, then viewing the assembled tree of nested comments.
See the Pen LEFT JOIN with HTML <?marker> by Dave Rupert (@davatron5000) on CodePen.
It’s worth noting a thread of 10K posts will crush the browser, so you’ll still have to load responsibly and think about a “Show more comments” buttons. I’m hand-waving the details here, but this gets easier too because your loadMoreComments() function doesn’t need to know anything about where to append those new comments, it can just staple them to the bottom of the page.
I’m excited about the possibilities here of removing ancillary content or UI from the critical rendering path. I’m interested in HTML’s newfound ability to self-assemble content and relationships in a declarative way. And maybe… he says with a glimmer of hope in his eyes… HTML Imports will be resurrected and there will be peace on Earth.
In some ways, the short story is the ideal format for science fiction. If science fiction is all about ideas, a short story is a way to take an idea and play it out without any obligation to build a bigger world around it.
But short stories can work the other way too. If you have a bigger world that’s been established in full-length novels, a short story can be just the right format for exploring another corner of that world.
In Lake Of Souls: The Collected Short Fiction by Ann Leckie, you get both.
The stories are split into three categories. The first has standalone stories. The second has stories from the universe established in the Imperial Radch series. The third has stories from the world of The Raven Tower.
In my opinion, the standalone stories are really good. The Imperial Radch stories are also great. But the stories from world of The Raven Tower are best of all. Something about the fable-like premise of that world makes it ideal for short stories that are like twisted little fairy tales.
At this point I think I’ve read everything that Ann Leckie has published so I feel safe in saying you can’t go wrong if you pick up any of her books. There’s something deeply humanist about her writing.
I’ve still got all my WordPress sites on Pressable and happily so; I think they do a good job! They do everything you’d expect a WordPress host to do. The things I specifically like are: On that last point, I appreciated this little one-clicker: With that on (and presumably because I also use Jetpack and […]
While designing the PDF version of Standout Designs for the Web, I discovered that spreads can make parts of my manuscript redundant. But those words can still matter in a reflowable ebook. So I’m editing one book in two different ways.
Moonwalk Audio and I started talking about a new website design about six months ago, but our calendars didn’t line up until now. Plus, I made time to work on designing the PDF version of Standout designs for the Web.
This week on Unfinished Business, Andy and Rich are back on their every two week schedule and kick off by talking about the books they’re both writing. They discuss Andy’s plans for his new Standout Designs for the Web, while Rich gives an update to his second edition of his classic Web Typography.
🎧 “Side Two” LP in progress. At 14 songs, it is nearly complete. I expect to release it before the year’s end.
The post Another Saturday, Another Song. appeared first on Jeffrey Zeldman Presents.
While I’ve done a lot of reference documentation writing, my favourite type of work is writing tutorials and “how to” content. I enjoy the challenge of understanding a technology and an audience, then explaining the technology to that audience. AI seems to be particularly bad at creating this kind of content. When people try, they ... More than words
Stephen and Chris jump on the podcast to talk about our (ahem, super cool) deployment feature. Any Pen can be a website! Well, they kinda are already, but you know what I mean. If you’re a PRO user all you do is click a button and the Pen is instantly deployed at a *.codepen.app website. […]
Choosing beautiful and unusual fonts and implementing them in visually compelling ways is certainly a way to stand out in this period of so many websites looking same-y. I figure it’s a good week to send some sweet typography links I’ve saved (in the form of screenshots): Notable to me: Apple has a dedicated icons […]
On this day, twenty five years ago, I wrote my first journal entry on this website.
Twenty five years! A quarter of a century! That’s a significant portion of the lifetime of the World Wide Web itself.
Actually, this site did exist before September 30th, 2001 but it was very different. You can still look at the very first version of adactio.com if you want to wallow in the glory days of framesets and DHTML. But it was twenty five years ago that I started publishing my thoughts here, making adactio.com my online home.
The funny thing is, I distinctly remember when I started writing that I was getting into this whole blogging thing really late. So many others had been doing it for a while at that point. Now there aren’t many of them left.
It’s not just the journal though. At some point I added the links section. Later still, I added notes. Between those three sections, I usually end up posting something on this website at least once a day.
But I don’t put any pressure on myself. That might be the secret to the longevity of the site. I don’t worry about running out of things to write about. If I go for a while without writing anything, that’s okay. But that rarely happens. There’s almost always something I want to share. Like I said in that first ever post:
I’ll try and post fairly regularly but I don’t want to make any promises I can’t keep.
I also wrote:
Being a web developer, there’s bound to be lots of geeky, techy stuff but I also want a place where I can rant and rave about life in general.
I think the tech stuff has decreased over the past few years. I’m more likely to write about music or books. I expect that trend will continue.
I certainly have no plans to stop. It feels good to have my own little homestead on the web where I can do whatever I want.
After twenty five years, it feels good to occasionally peruse the archive and revisit the past.
Here’s to the next twenty five.
As I limped home from Physical Therapy, a man walking in the opposite direction suddenly planted himself directly in front of me and slammed me in the head with a heavy bag. It was a cloth bag, like a backpack with only one strap. I’ve no idea what was inside it : a beat box? […]
The post My Glamorous Life: in Which I am a Target appeared first on Jeffrey Zeldman Presents.
Since we came back from holiday, we’ve been attempting to spend equal time on work and home improvements. But last week, I found it difficult not to press ahead with book production. I suppose decorating will have to wait.
“Triple Threat” Josh Collinsworth has a brand new daily puzzler game called Tupo. On it’s surface it looks like a simplified 4×4 Sudoku block but with colored squares. That is the foundation for a simple ruleset:
Mini Sudoku with colors. Got it. How hard could it be?
Hahahahahahahahahaha. You naive bastard.
It’s deceptively difficult and then right when you get the hang of it, it jumps to a 5×5 grid on Friday. Simple yet complex is a difficult design concept to achieve and Josh did a great job. I got a sneak peek of the game a couple weeks ago and it quickly earned the top spot in my daily puzzle playlist. Before Inkwell Games, before Clues by Sam, I play Tupo. If you’re a fan of daily puzzlers, try Tupo. It won’t disappoint.
If you’re anything like me, you’re sick of hearing about so-called “AI”. We are not a fringe. The majority consensus amongst actual regular people is that this is being forced down our throats and stuffed into our eyes and ears.
That’s why it pains me that I’m about to add more fecal matter onto the ever-growing pile of shit by writing yet another hot take related to large language models.
If you stop reading now, I don’t blame you. In fact, I applaud you.
Anyway…
The last time I made the mistake of writing about large language models, I set out my stall thusly:
Software is almost certainly the killer app for large language models.
I think the artists, writers, and musicians will be okay, or at least as okay as they ever were. It turns out that humans like things made by other humans.
And y’know what? If I had to choose which endeavour I’d rather see automated away—programming or art—it’s no competition.
I stand by that. But it doesn’t mean that software development gets a free pass to use exploitative extractive tools with a clear conscience.
You can use these tools in a thoughtless way or you can use them thoughtfully. I really like Robin Sloan’s approach:
The consensus vision seems to involve AI working for you day-to-day, a constant ambient presence, sparkles in everything; even cautious Apple has succumbed. I’m not interested in that.
My alternative: get your wish, then stuff the genie back into the lamp.
He’s got a set of rules:
- You must actually and consistently use anything you build for two months before posting about it.
- Ideally, never post about it. (I know I am breaking this rule, but my purpose is pedagogical, and anyway, I’ve got more apps I didn’t tell you about.)
- Don’t let the AI agent name the app. You pick the name.
- Don’t distribute the app, not even for free. Don’t post the code on GitHub. This is not software for glory; it is software for you.
For most of my career, I’ve wanted web development to become something that anyone could do. I really like the World Wide Web and I think it would be great if more people had their own websites. But I totally get that most people have no interest in learning HTML.
One of my biggest fears for the web was that it would become the domain of professionals only; a gatekeeping priesthood who get to make stuff while everyone else goes without.
The efficacy of large language models for coding would seem to lessen that possibility. Especially if you apply Robin’s approach.
The Session has an API. There are also weekly data dumps of everything on the site. The idea is that other people can use these to build other useful tools for themselves.
Lately, there’s been a big uptick of these kinds of tools, mostly made by someone with the help of large language models. Often they have no previous experience of making software.
This is good. But I get uneasy when I see people selling these tools to other people. Call me old-fashioned but I feel that if you’re going to ask people to pay for a thing, you should understand that thing. I think that’s why Robin’s list of suggestions resonates with me.
But…
To build software with large language models today, you pretty much have to pay some money to the absolute worst sort of people, the ones pedalling tokens to their “hyperscale” products. You can’t avoid being complicit because there isn’t really an alternative.
I think that might change.
I’m not going to make predictions. That’s a mug’s game. But I might attempt a gentle unspooling of one potential future…
First off, there’s a crash coming. That’s pretty much inevitable at this point. The only question is how much collatoral damage it’s going to do to the world.
Right now, tokens are subsidised. The hyperscalers lose money when people use their products. It’s possible that we are now living through a golden age of making software relatively cheaply with large language models. That will change when the hyperscalers start charging for the true cost of tokens.
Not for everyone though. I believe that enterprise software companies will happily pay the true price for tokens. It’ll be like Google Glass; that was an absolute disaster for the consumer market but went on to have a productive second life in factories.
You know when you’re in an airport and you see advertisements that are not for you, but for faceless corporations? You know the ones. They’re all “cloud” this and “sap” that. Now they’re all about “AI”. It’s not for us. It’s for them.
Does this spell the end of making home-cooked apps with large language models?
I don’t think so.
The hyperscalers might become unaffordable to most people, but keep an eye on open models. And keep your other eye on local models.
Right now, open local models aren’t as good as the large language models that require data centres of computation and energy. But they aren’t massively far behind. And if the recent history of large language models has taught us anything, it’s that “good enough” is fine.
Take Google Search. They decided to fuck it up by forcing generated summaries into your eyeballs above the actual search results. These summaries aren’t 100% accurate. Let’s be generous and say they’re accurate more than 90% of the time. According to Google, that’s good enough.
So, sure, local open models may never reach the levels of data-centre-powered large language models. But they don’t need to. They just need to be good enough.
I’m really looking forward to that.
My issue with large language models has never been the actual technology, which is genuinely fascinating. My issue is with the power dynamics, of being robbed of agency, of the over-inflated hype and criti-hype being used to convince us of an inevitable future where a small group of very rich men are masters over our lives.
I’d love to see the technology decoupled from the current depressing narrative. I’d love to see a thousand homemade software flowers bloom from open local language models.
I bought this LP because of its Milton Glaser-designed cover featuring the colored pencil musical notations of composer/synthesist and former monk Jean-Yves Labat, alias M. Frog. It was 1973; I was about to become a high school senior, and I had hitchhiked to New York City with my friend Al Crump. The multi-floor Sam Goody […]
The post Undersung Hero: M. Frog appeared first on Jeffrey Zeldman Presents.
If I’ve been missing from the internet it’s because I’ve been spending my evenings grinding on a game from Butterscotch Shenanigans called How Many Dudes? Hot off the heels of the release of their massive multi-year sequel project Crashlands 2, the family+ operation at BS turned a little game-jam style prototype into a “Featured on the Apple App Store” game in under a year.
How Many Dudes? seeks to solve ancient internet riddles like:
The premise is pretty simple. Build a team of fighters with different abilities and power-ups and enter the arena to fight different enemies and bosses. For me this game has it all:
Those characteristics all lead toward a game with great replay-ability. Mastering lower tiers unlocks characters and power-ups in higher tiers creating a perfect “loopback” system where I want to 100% the lower tiers before moving on. A full round takes around 20 minutes but you can stop and start it effortlessly like a good casual game.
I could go on forever about the power stacking economies in this game but it’s the little bits that impress me the most. Like how the characters don’t have proper walk animations and instead rotate and bob as they shimmy their way across the screen like a paper cutout on a popsicle stick. This may seem like corner cutting but on a screen with 1000+ combatants, particle effects, blood splatter, and explosions… I bet limiting characters to a single low-frame combat animation cycle is a critical ingredient for good performance.
Then there’s the characters themselves. With nods to other game franchises, movie tropes, and popular (but litigious) dungeon crawling tabletop systems, the game about memes builds on cultural memes to add depth. And even though these characters don’t talk, interact, and you don’t technically control them, the hilarious copy hidden within the power-ups and item pick-ups gives a game with a lot of characters a ton of character.
I will confess some bias; Butterscotch Shenanigans is one of my favorite gamedev studios and I’m internet friends with their CEO Adam Coster (he’s even been on my podcast). It didn’t start like that. Before knowing Adam, I watched their GDC talks and mentioned their studio in a post I wrote about a GMTK video essay. I like their approach to prototyping a lot but what I like most about Butterscotch Shenanigans is that they have that old-world #indieweb ethos of sharing what you know. They have a blog for detailing their process, a podcast, and offer open source tools for GameMaker on GitHub. If I ever pivoted to gamedev, I’d want to build a studio like theirs. A big inspiration for me.
Anyways, that’s my review of How Many Dudes? If you like deck-building rougelikes or button mashing power fantasies, I think you’ll like this game. And here’s my Dude tier list in case you’re wondering…

It was the autumnal equinox a few days ago. For a moment our planet was balanced perfectly, with everyone experience twelve hours of daylight and twelve hours of darkness. Here in the northern hemisphere, the days will get shorter and the nights will get longer until the winter solstice.
Summer is officially over.
I saw it out with my now-annual trip to Spain for the Cáceres Irish Fleadh. It was, as always, great fun. Nothing beats playing loads of tunes into the early hours while sitting outdoors in a beautiful old town.
I visited some lovely places this summer. A trip to Greece. A trip to Italy. Even my trip to Cork was blessed with bright sunshine.
But I think the highlight was the three weeks I spent in the north of Ireland. It helped that being there meant I escaped the worst of the heatwave here in England.
All in all, it was a thoroughly enjoyable summer filled with travel, music, good food, and sunshine. Now it’s time to hunker down at home.
I trust AI most on the subjects I know least about. That should probably worry me, and it tells you something about where AI actually helps.
Dave Rupert has a new syntax highlighter: MicroLighter. He wrote about the why. There are a bunch of trade-offs with all the technologies that do this job. The trade-offs here seem pretty worth it for a relatively lightweight, infrequent syntax highlighting job. I mean 2 KB as a base library is pretty amazing, with the […]
Last week was a little chaotic, with new windows installed throughout our cottage. But I still managed to get plenty done, including real progress on my upcoming book, Standout Designs for the Web.
Marie and Chris talk about CodePen Challenges. We’ve been at it for many years! We get into the technology behind it, how we build our own CMS for it that powers the website and the emails that get sent about it. We also cover why they can be so beneficial for everyone involved. Time Jumps
Rach and Chris jump on the podcast to talk about the brand-new in-app notification system we built and shipped. We’re actively trying to balance a system that isn’t annoying, but allows us to show targetting information to targetted users when we think that’s valuable for both you and us. It started with “We launched 2.0!” […]
A frequently asked multiple-column layout question is why a new paragraph at the start of the column has additional space above it. The space is the margin on the paragraph, which doesn’t collapse if the paragraph is inside a multicol container. The only fix is to not use a top margin on paragraphs. However, the ... Remove start and end margins with the margin-trim property
Very cool that random() in CSS is coming to Chrome to join Safari who led that feature. It means one of my favorite demos I’ve made when I was first playing with it will work for more people. I just like how little code it is and how it combines some fun modern CSS features. […]
2000 was the peak year for Adobe’s website. It was a content site filled with creative how-to articles, opinion pieces, and artist interviews where you could also, if you wished, buy or get info on Adobe products. Now this is how you create a product site for a suite of creative products, I told myself […]
The post Remembrance of Websites Past appeared first on Jeffrey Zeldman Presents.
A handful of people I follow and know about have announced their recent departure. Not from a job (although plenty of that going around), but from social media, AI usage, or in more extreme cases the Internet entirely.
In Goodbye, Forever, Probably, Salma announced her retirement from the internet. She makes a sound case for leaving DevRel, a demanding job with mushy metrics that struggles to find a home between product and marketing, because community building is neither of those. I could see why Salma wanted out of the metric chasing and the daily requirement to be a public face. Salma foreshadowed this move for awhile, there was a palpable discontent with the industry in her online voice. And she did it. She made the jump to the Offline and she’s been dark on the internet since her post. I haven’t talked to her personally, but I hear second-hand she’s making good on her word and laying low at a good company as an individual contributor. I wish her the best and that the detoxing process goes well.
Then there’s Chad Whitacre who announced his retirement with a typewriter-written letter. His plan? Lay low and make a zine about religion. That seems like a charming project but I get so curious about people’s financial situations when they’re able to make such asymmetrical moves like that. He lives in a rural town near Pittsburgh, does selling $10 zines afford that lifestyle? What about healthcare? Does he have a trust fund? A recent inheritance? Bitcoin? Years of an early retirement strategy paying off? I shouldn’t ruin the anti-capitalistic vibe with such trivial capitalistic questions but I love it and my brain jumps to trying to make the math work for myself. Either way, I wish him the best in the Offline.
Brett Codes shared his post I’m done using AI (and video). It caused a stir on the algorithm. Based on followup videos, Brett seems happy with his decision which perhaps comes down to the fact that he’s a person who stands by his convictions –he’s a vegan, uses a dumb-phone, etc.– and I think he finds fulfillment in eschewing the social norms. But I admire his perspective as someone who used AI but ultimately decided to let it go. Betting your career against the thing (it seems) all companies are hyper-scaling towards is a big bet. Either you’re right and the world will always need people who know how software works, or you’re wrong and you’re left in the dust… or we’re all steam rolled by the errant contraption. Either way, I wish him the best unplugging from the machine.
I know of other stories but because three is a magic number that people find harmony in, I’ll cap it here. There’s a sad irony when someone jettisons from social media, they’re gone from our Online worlds but I’m so happy for them. And I mourn what we don’t get to hear from them. We don’t get to hear about how wonderful they feel, how beautiful their skin is now, and how they restored their mental health. We don’t know if they make more money or less money. We don’t get further inspiration from them to leave the Online. But being able to observe the Offline from the Online might ruin the magic and mystery hidden there. It’s a door hidden in an old wardrobe that you have to pass through to fully understand.
Anyways, should you leave us and follow those who have gone ahead, I wish you the best in the Offline. I hope to join you there soon.
We’ve been away from the studio for the past five weeks and on the road around southern Germany. Now, I’m back and easing myself into work with a new website design for an author.
At least one New York Times journalist — columnist Michelle Goldberg — has at last begun covering the real AI story: not the technology but the technologists and their cult of faux philosophies that lead them to claim Promethean powers to destroy humanity. With their self-fulfilling prophesies of extinction, the AI boys grab the attention of unwary media and […]
The post They’re covering the wrong AI story appeared first on BuzzMachine.
Normal text wrapping can leave big gaps in your container, which can become even more exaggerated with the new-ish CSS values of text-wrap: balance and text-wrap: pretty. You almost certainly want to use balance or pretty because they do a great job preventing “dangling” lines in your text, even with icons. But what can we […]
For “My father’s favorite food is,” the kid had written SALAD.
The post My Glamorous Life: A Good Lie appeared first on Jeffrey Zeldman Presents.
I have written much about my memories of 9/11: My story of surviving the day; recording what I witnessed; a meditation on the Yahrzeit; then five years on; ten; and twenty years and what I recalled still. Now twenty-five years after I must write of my regret. All this time, I had convinced myself of […]
The post 25 Years appeared first on BuzzMachine.
Individually, we have very little power, but together we have a great deal of power. In response to a series of Medium articles by Patrick Neeman—like this one that addressed issues of AI governance, fairness, and standardization—publisher Louis Rosenfeld invited Patrick and me to come on his show and talk about AI and standards. A […]
The post AI, Disruption, and Standards appeared first on Jeffrey Zeldman Presents.
The approach I use to diagnose digital problems, find the barriers blocking the obvious fix, and get results without getting stuck.
Stephen just implemented a great UI feature for the 2.0 editor where all the Panels that were previously stuck to the left side of the page can now be dragged away to become an “Editor Tab” just like a file (or Block!). Now you’ve got a lot more freedom for what you’re looking at, and […]
The story of labor and the Linotype is unexpected in many aspects. For this Labor Day, I’ll share a bit on the subject from my book Hot Type. (Links to buy the hardback and audiobook versions and signed copies are here). Printers waged their first strike against publishers in occupied New York in the winter […]
The post Labor and the Linotype appeared first on BuzzMachine.
In 2015 I started my CSS Layout News newsletter mostly to have my own venue to go on about CSS grid, at the time unavailable in any stable browser. I got to 2023 and 343 issues and it fizzled out. Partly because I got busy, but mostly because the newsletter signup form on the platform ... CSS Layout News in the ATmosphere
Why first impressions are often correct. Today’s jerk is tomorrow’s techno-fascist.
The post Tales o’ Tech: Suffering Fools appeared first on Jeffrey Zeldman Presents.
I got a cool trick for you. You can just paste an HTML email into Gmail. So let’s say you’re using (the very nice) MJML language to create an nice responsive cross-client-compatible email on CodePen. Like this one. You can just select all that nice visual output and Copy it. Then in Gmail, make a […]
The start of a beautiful weekend.
The post Mix appeared first on Jeffrey Zeldman Presents.
I really enjoy recording podcasts, both my own ([One Footer in the Grave](https://onefooter.show) and [Unfinished Business](https://unfinished.bz)) and guesting on others. Brad Frost just released an episode of his [Waking Up Excited](https://wakeupexcited.show) podcast with [me on it](https://overcast.fm/+ABQYIvePR0k), and it’s the podcast I’m most proud of making.
Getting UX work approved has almost nothing to do with users and everything to do with the risks and opportunities your boss already worries about.
Chris has Jacob Miller on the show, who is building a tool called DiffUI. The big idea is that it uses a diffusion model to do prompt-to-website design rather than an LLM, because image-based models are more interesting and creative. So stay there for a while, producing really detailed visual mockups and plans, then head […]
Sometimes we gotta take matters into our own hands, am I right? I was reading Aaron Gustafson’s Never Lose Form Progress Again which is just a wonderful idea for a Web Component. Basically you wrap a <form> in <form-saver> and it saves the data a person types into a form to localStorage so they don’t […]
Snow White—17 years old—didn’t come meowing for food when I entered the kitchen at 6 a.m. An hour later I found her lying motionless on the sofa. I stared until she moved, slightly, nestling her face more deeply between her back paws.
The post Her Glamorous Life appeared first on Jeffrey Zeldman Presents.
Recently implemented in Chrome are two of my top asks for CSS—the concept of rows in multiple-column layout, and gap decorations for rows and columns in grid, flexbox, and multicol. Rows (using column-wrap) provide a way to use multicol on the web without risking scrolling up and down to read if the columns become taller ... Multiple-column layout, rows, rules, and floats
With the publication of my new book, Hot Type, I’m sharing brief excerpts to give you a sense of the stories in it. Mark Twain is the Zelig of the tale, reentering often, as if a tour guide. Here, he comes on stage as Samuel Clemens, a young printer’s devil working in his brother’s Hannibal newspaper […]
The post Sam Clemens, compositor appeared first on BuzzMachine.
I made a smol client-side syntax highlighter that uses the CSS Custom Highlights API called MicroLighter. I’ll talk about why I made it in a bit but first I wanted to… ahem… highlight… some of the features.
::highlight(token-name), not spanslight-dark() themes<micro-lighter> custom elementAt some point I broke syntax highlighting on my Jekyll blog. I’ve used a handful of syntax highlighters over the years (Highlight.js, PrismJS, Rouge, Shiki, etc, etc) and I’ve felt the trade-offs between different client-side and server-side implementations. Faced with picking another, I knew I wanted to explore Bramus’s technique of syntax highlighting with the CSS Custom Highlights API.
There’s some limitations with the CSS ::highlight() pseudo; no italics, no bold, no font swapping – but otherwise it’s pretty cool syntax to idiomatically express “I want to highlight this token” via CSS instead of injecting spans everywhere. Using the Highlight API means I avoid any DOM mutation and the scope of the library shrinks down to: scan code blocks using regex patterns and send CSS.highlights.set(category, textRanges) to highlight the code blocks.
I don’t need much syntax highlighting on this site. Not all posts have code and my code samples are a whole fifteen lines long at best. My struggle is that I swap languages often. I’ll do HTML, CSS, and JavaScript all in the same post. A little bash here, a little ruby there, some markdown as a treat. Using all those languages grew the complexity beyond the limits of my regex-fu, so I decided to lean on Textmate’s established collections of patterns used by VS Code. And before I knew it, my little highlighter could do almost any language.
Knowing I use different languages often, one principle I established was that all language grammars should be auto-loaded on-demand to reduce configuration and bundle size. That way you only pay for what you use.
Inspired by PrismJS’s simplified token categories I flattened down Textmate’s granular token categories to a more human-friendly set, making it easier to style. In addition to that, one enormous nit-pick I have with codeblock styling is that light and dark themes are separate entities, so I merged them into one theme using light-dark().
The last big opinion I baked in was that I wanted the syntax highlighter to do one job: infer language and highlight code in that language. With that a guideline, I moved all extra functionality (like line-numbers, etc) over to a web component. The vanilla web component adds about ~1 KiB in size, but co-locating UI into a UI primitive like native custom elements feels right and the ShadowDOM encapsulation makes it easy to separate the code from the presentational UI.
Obviously, I’m a web component pervert but it feels like a great separation of concerns versus trying to cram everything into core library.
To get started on your site, I’d use the self-initializing minified bundle, but I’m also shipping ESM and a web component.
npm install microlighter
<script type="module" src="path/to/microlighter/microlighter.min.js"></script>
Like I said above, not all my posts have syntax highlighting, so I even wait to import the script unless I know there’s a page with code on it.
if(document.querySelector('pre>code').length) {
import('path/to/microlighter/microlighter.min.js');
}
You can use the ESM version if you’re going to do something fancy yourself:
import { highlightAll } from 'microlighter'
highlightAll({
selector: 'pre.onlyTheseGetHighlights'
})
And you can also use the web component if you want those extra features I talked about:
<micro-lighter data-syntax-theme="github" line-numbers controls="copy">
<pre><code>Code goes here</code></pre>
</micro-lighter>
Web component classes are pretty extendible too so if I don’t support something you need, you can “fork” it by extending the base class and adding your own features.
Lastly, you can use one of the pre-supplied themes or roll your own. The basic structure is:
/**
* Setup semantic `--syntax-*` tokens
* @value background | foreground | comment | keyword |
* operator |string | constant | function | type | variable |
* property | tag | selector | inserted | deleted
*/
[data-syntax-theme="my-theme-name"] {
color-scheme: light dark;
/* Code block tokens */
--syntax-background: light-dark(#f8f8f8, #3a3a3a);
--syntax-foreground: light-dark(#3a3a3a, #f8f8f8);
/* Highlight tokens */
--syntax-comment: light-dark(#6e7781, #8b949e);
--syntax-function: light-dark(#8250df, #d2a8ff);
/* ...etc... */
}
[data-syntax-theme="my-theme-name"] pre:has(code) {
background-color:var(--syntax-background);
color:var(--syntax-foreground)
}
::highlight(comment) { color: var(--syntax-comment) }
::highlight(function) { color: var(--syntax-function) }
/* ...etc... */
And that’s MicroLighter. If you end up using it and trying it out, let me know what you think.
With the release my new book, Hot Type, this week, I will post some good bits in hopes of enticing you into reading it. Here, from the introduction, is a preview of the many delightful rabbit holes I explored building the story of the invention of the first successful typesetting machine, the Linotype, and with it, […]
The post Introducing my book ‘Hot Type’ appeared first on BuzzMachine.
One Frenchman can’t be wrong. Push to the edge of what people will accept. Stop there. Raymond Loewy called it MAYA, and used it to design the modern world. The most important industrial designer you've never heard of.
The post The Most Important Industrial Designer of All Time appeared first on Jeffrey Zeldman Presents.
In 2020, the gifted makers of websites at Clearleft shared methods to achieve fluid responsive design. “Utopian” web design was (and still is) their half-kidding name for this approach, which Apple’s Jen Simmons, working indendently, called “intrinsic design.” Instead of chasing an ever-changing number of fixed breakpoints, utopian or intrinsic design follows the web’s intrinsic […]
The post Ebb, Meet Flow: Intrinsic Design in a Nutshell appeared first on Jeffrey Zeldman Presents.
Stephen and Chris hop on to talk about the couple of weeks post 2.0 launch. Things are certainly different in 2.0! Literally the location of certain buttons is in a new place, and that requires folks to poke around a bit and figure it out. Some people just do that and it’s a non-issue. Some […]
Edit (2026.08.24): Got feedback from the Web Components Community Group that I was mixing up inheritance, specificity, and the cascade and that * might be a better selector than :defined. I agree with both of those perspectives and I’ve updated this post.
Stuart Robson’s post Solving CSS @layer Ordering in Design Systems with Design Token Metadata, a response to Chris Coyier’s Thinking Horizontally in CSS @layer, came to me at a serendipitous time. You know I love a good blog-and-response, but these posts on the topic of lowering the specificity of component-level tokens and scaling that out was a problem I was having with some of my web components.
Their @layer trick didn’t work for me, but here’s what did…
Let’s say we have a global stylesheet with our design system tokens.
/* theme.css */
:root {
--system-bg: #00f1;
--system-fg: #000;
--system-font: Inter, sans-serif;
}
And then we set up our web component with some local component variables that use design system tokens as defaults.
<ui-card>
<template shadowrootmode="open">
<style>
:host {
/* Local component variables */
--card-bg: var(--system-bg);
--card-fg: var(--system-fg);
--card-font: var(--system-font);
/* Apply component styles */
background: var(--card-bg);
color: var(--card-fg);
font-family: var(--card-font);
button {
font-family: var(--card-font);
}
}
</style>
<slot></slot>
</template>
Hello world
</ui-card>
This works great… Except… I sort of expected that if we added some of those component tokens to the stylesheet we could restyle the card.
/* theme.css - This doesn't work 😭 */
:root {
/* ...system tokens go here... */
--card-bg: #f001;
--card-fg: #f00;
--card-font: fantasy;
}
:host has the same specificity as :root (0,1,0), but because :host declares the variables directly on the element, it replaces the inherited values. This confused even me, a web component front-end master, because there’s a weird styling relationship in how :host overrides :root, but classes or an element-selector the Light DOM overrides :host. But Bramus from the Chrome team explained it to me as: If you redeclared the variables in a normal div, it wouldn’t be surprising that the values didn’t inherit. Ok, that tracks.
The canonical way to solve this –of course– is to pull out a bazooka and point it directly at the element:
/* theme.css - Works, but... */
ui-card {
--card-font: fantasy;
}
This works and I often recommend direct styling the element as the primary way to customize web components from the outside.
But a system is different than laser-guided fixes. Themes should be low-specificity by design. Using an element as a selector is one of the strongest selectors in our CSS toolbox has low specificity but is super specific to that element. If we ever want to change our mind, –say updating the token value in :host(:hover)–, we’re on a one-way train to !important town. To me, it’s a problem if our CSS and DOM are super-glued together. It’s less ideal compared to a flat list of element-agnostic tokens.
What if other card-like elements use those tokens? Uh-oh, gotta update the global stylesheet. What if elements in the Light DOM also want to use those tokens? More work. We need a “Set it and forget it” way to handle themes. Also pinning tokens to the custom-element might not always work in nested component scenarios, but that’s a secondary issue.
There’s a handful of other approaches (see below) to this problem and they all have trade-offs. I was hoping the @layer solution would help, but @layer { :host {} } inside a shadowRoot is still more powerful local than :root. Drat!
But is there a low-specificity way to win the specificity war with override :host?
:defined*An earlier version of this post recommended :defined as a low-specificity (0,1,0) way to override :host from the outside (see below). I like the visual/syntactic similarities there… but we can go even lower on the specificity power-ranking chart by using * (0,0,0,0).
/* theme.css */
:root {
--system-bg: #00f1;
--system-fg: #000;
--system-font: Inter, sans-serif;
}
* {
--card-bg: #f001;
--card-fg: #f00;
--card-font: fantasy;
}
I hesitate to recommend * for a three reasons:
:defined (essentially).* { padding: 0; margin: 0; } and overrides padding and margin styles on :host, I dislike that side-effects from *.But –as it happens– that side-effect is the exact behavior we want to make our theme variables work. We abuse * to apply our tokens on the Light DOM host element, which beats the interior Shadow DOM :host.
See the Pen shadow-piercing local component variables with * selector by Dave Rupert (@davatron5000) on CodePen.
Bramus told me about one potential future CSS feature that might help this exact situation controlling inheritance inside the shadow root :host, the already spec’d out was the inherit() function.
/* ui-card.css */
:host {
--card-bg: inherit(--card-bg, var(--system-bg));
background: var(--card-bg);
}
I like that. That could work. Let’s do that? Ok. We all agree. Ship it. I’d also love a way to target my custom-elements directly:
/* theme.css */
/* Select only custom-elements */
:custom-elements {
--card-font: fantasy;
...
}
/* OR */
/* Glob select elements based on an custom-element or class prefix */
shadowdom-*,
.lightdom-* {
--card-font: fantasy;
...
}
Either way, that’s the end of my post. If you need your Light DOM and Shadow DOM components to allow for fallbacks, try the original :defined technique below that has a bit more powerful selector. And if you’re curious about other alternatives considered to handle component-level variables, keep reading!
:defined technique/* theme.css */
:root {
--system-bg: #00f1;
--system-fg: #000;
--system-font: Inter, sans-serif;
}
:defined {
--card-bg: #f001;
--card-fg: #f00;
--card-font: fantasy;
}
When you put <ui-card> on the page, it’s an Unknown Custom Element2. Because HTML is chill, it treats the unknown custom element as a HTMLUnknownElement<span> with a gigantic typo. The :defined pseudo-state triggers on customElement.define() (or when you use Declarative Shadow DOM) and with that small change we’re able to pierce the shadow boundary and win the specificity war because :defined apply styles to the host-element from the outside, which is more powerful than :host from the inside.
But… You know what else gets :defined? All native HTML elements! div:defined, video:defined, and every native element your browser supports also works with :defined.
That gives us some portability that the ui-card selector didn’t. If we need to re-use these tokens inside a Light DOM CSS class, we can also support that with :defined.
.ad-unit-block {
/* Setup variable fallbacks */
--card-bg: var(--system-bg);
--card-fg: var(--system-fg);
--card-font: var(--system-font);
/* Apply component styles */
background: var(--card-bg);
color: var(--card-fg);
font-family: var(--card-font);
}
And that’s pretty handy if your default styles and system overrides need to go more places.
See the Pen shadow-piercing local component variables by Dave Rupert (@davatron5000) on CodePen.
Below are a handful of different workarounds for local-component variables, each with trade-offs.
/* theme.css - Doesn't work, also too heavy handed */
:root {
--card-font: fantasy!important;
}
This works doesn’t work, but should be the nuclear option for specificity battles. Also makes it more difficult to override later.
/* theme.css */
:root {
/* system theme */
--system-bg: #00f1;
--system-fg: #000;
--system-font: Inter, sans-serif;
/* custom overrides */
--card-bg: #f001;
--card-fg: var(--system-fg);
--card-font: fantasy;
}
This works too. You’ll need to change your <ui-card> styles to directly access background: var(--card-bg). The problem here is that all variables are mandatory and must ship in the theme. Even if a product only uses or customizes just one component, they have to ship all the tokens for every component. And now your theme CSS will be ever-growing at the rate of num_options * num_components.
/* theme.css - Works, but can be confusing to CSS learners */
:host {
font-family: var(--card-font, var(--system-font));
}
I’ve been down this path. It works well and the first variable in the fallback chain becomes totally optional, meaning we can ship less tokens in the production theme. Fallback chains (a ternary, basically) and Aliases (a DOM walker) have dramatically different performance profiles when it comes to style calculation costs, so you may want to consider this… but that’s for another post.
In practice though, I find the beauty of the CSS cascade gets lost on a lot of six-figure engineers (waves generally in the direction of Hacker News and popular CSS frameworks).
And this also works less good if you’re going to re-use a local variable token… which leads us to…
/* theme.css - Works, but too noisy */
:host {
--_card-font: var(--card-font, var(--system-font));
font-family: var(--_card-font);
button {
font-family: var(--_card-font);
}
}
This is the fully exploded version of the previous pattern. We can re-use --_card-font on other elements inside our component. You can do this but it begins to explode the surface area of the number of tokens we’re managing inside a component. Fallbacks were confusing enough, now we’re doing fallbacks with aliases. Uf.
LLMs love to copy Tailwind’s Preflight. ↩
Thanks to Danny Engelman for the correction. ↩
Meet WordPress.com Education, a new, free product for teachers and students.
The post For students who make websites appeared first on Jeffrey Zeldman Presents.
Sinar saw digital photography coming decades before it transformed the industry. So why did one of photography’s most innovative companies struggle to survive it? I have some ideas.
On this week’s Intelligent Machines, we cohosts had a spirited disagreement about Anthropic’s watermarking of text, as required by EU regulation. The company just released more detail about how it will work, and now I’m upset for a different reason than I first thought. I’m angry as a writer. Anthropic’s method will alter its models’ word choices in a pattern […]
The post Words matter. Damnit. appeared first on BuzzMachine.