Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
73

The Shape of Runs to Come

from Ryan Mulligan [alt+shift+b] in design

Over the last few months or so, I have been fairly consistent with getting outside for Sunday morning runs. A series of lower body issues had prevented me from doing so for many years, but it was an exercise I had enjoyed back then. It took time to rebuild that habit and muscle but I finally bested the behavior of doing so begrudgingly. Back in the day (what a weird phrase to say, how old am I?) I would purchase digital copies of full albums. I'd use my run time to digest the songs in the order the artist intended. Admittedly, I've become a lazy listener now, relying on streaming services to surface playlists that I mindlessly select to get going. I want to be better than that, but that's a story for another time. These days, my mood for music on runs can vary: Some sessions I'll pop in headphones and throw on some tunes, other times I head out free of devices (besides a watch to track all those sweet, sweaty workout stats) and simply take in the city noise. Before I headed out for my journey this morning, a friend shared a track from an album of song covers in tribute to The Refused's The Shape Of Punk To Come. The original is a treasured classic, a staple LP from my younger years, and I can still remember the feeling of the first time it struck my ears. Its magic is reconjured every time I hear it. When that reverb-soaked feedback starts on Worms of the Senses / Faculties of the Skull, my heart rate begins to ascend. The anticipation builds, my entire body well aware of the explosion of sound imminent. As my run began, I wasn't sure if I had goosebumps from the morning chill or the wall of noise about to ensue. My legs were already pumping. I was fully present, listening intently, ready for the blast. The sound abruptly detonated sending me rocketing down the street towards the rising sun. My current running goal is 4-in-40, traversing four miles under forty minutes. I'm certainly no Prefontaine, but it's a fair enough objective for my age and ability. I'll...
8th Dec 2024

Stay updated

Get a weekly newsletter with the top 5 articles worth reading every week.

More from Ryan Mulligan

Transition to the Other Side with Container Query Units

Managing the position of an element as it travels across the length of its parent container can be tricky. Assuming they both have dynamic, responsive dimensions, we might rely on JS to check the width and/or height of each element and do some calculations for a proper end result. The classic FLIP technique has proven to be a solid solution in the past. For a modern approach, the View Transition API can also work well here. I now realize there's a much simpler approach using container query units and a dash of CSS wizardry. The demo Select the container query units option if it's not already, then click and hold the container to witness the magic. Try changing the element dimensions or resizing the parent container using its handle on the bottom right. Open CodePen demo Jump down to the solution if you'd like to get right into it. Otherwise, join me on a transformative journey to this final result. Transition exploration The following CSS will transition an element smoothly to the right when its parent container is pressed: .element { transition: transform 200ms ease-out; } .parent:active .element { transform: translateX(100%); } Individual transform properties are also available and well supported in modern browsers. I tend to use them more frequently when writing simple transforms like this. In the demo, we'll find the translate property is being transitioned. Let's update the above code example to something similar: .element { transition: translate 200ms ease-out; } .parent:active .element { translate: 100%; } Keep in mind that there's a pre-defined order for independent transform properties. Stefan's article explains the fundamental differences between transform functions and individual transforms. Not an issue with our current examples, but something to remember when multiple individual transforms are being applied. Check out the demo with x checked and the percentage option selected. When we click and hold the container, the element transitions the width of itself to the right. We can see that this percentage is based on the element's dimensions. While handy, it doesn't achieve our goal of moving the element all the way to the opposite side. Explicit dimensions If we knew the exact dimensions of the parent container, we could declare a calc() function where the element's full percentage is subtracted from the explicit parent size. .parent { width: 300px; } .element { transition: translate 200ms ease-out; } .parent:active .element { translate: calc(300px - 100%); } It works, but it's uncommon to have explicit dimensions declared like that. Our elements need to be flexible and responsive in any context. What can we do instead? Position properties Properties like top and left are available to us. Could we transition the element by doing something like this? .element { position: relative; left: 0; transition: 200ms ease-out; transition-property: translate, left; } .parent:active .element { left: 100%; translate: -100%; } Seems that we can, at least in the context of the demo. However, animating position properties has a negative impact on layout and creates performance issues. The browser works harder to recalculate element positions, repaint pixels, and then composite the result. This inevitably leads to janky or sluggish animations. GPU-accelerated properties such as transform and translate avoid triggering repaints so animations run buttery-smooth and fluid. Fair enough. We'll focus on moving the element using transform properties. It's time to reveal the strongest solution. The solution In the demo's controls, check that container query units is selected. Click and hold the container. Watch as the element smoothly transitions to the opposite side of the parent container. Try changing the element dimensions using the sliders, or resize the parent with the resize handle on its bottom right. It still works! Here's the gist when we only need to transition to the opposite side of the parent horizontally. .parent { container-type: inline-size; } .element { transition: translate 200ms ease-out; } .parent:active .element { translate: calc(100cqi - 100%); } If we want to transition vertically or in both directions, we'll need the size value for our container-type so that containment is applied to the block and inline directions. The example below translates the element along the y-axis. .parent { container-type: size; } .element { transition: translate 200ms ease-out; } .parent:active .element { translate: 0 calc(100cqb - 100%); } By setting a container-type on the parent, the element is able to access the parent size via container query length units: 1cqi is 1% of the inline size. 1cqb is 1% of the block size. Notice that we're using logical properties instead of physical ones. If this isn't familiar territory, I recommend Ahmad's excellent Digging Into CSS Logical Properties article to learn more. 100cqi is the full inline size of the parent container. We can recall from earlier that a transform percentage reflects the element's dimensions. Once we subtract 100% from that container query unit, the element can gracefully transition to its proper position on the opposite side of the container. Take a moment to enjoy the wonder and magic that is modern CSS. Helpful resources Container Queries and Units How to create high-performance CSS animations Order in CSS transformations – transform functions vs individual transforms Digging Into CSS Logical Properties

11th Oct 2025 • 1 votes
Blog Questions Challenge

Hey there. It has been a minute since my last post. I was semi-recently tagged by Zach Leatherman to (optionally) participate in this year's Blog Questions Challenge. I had planned on doing it then. But life really hit hard as we entered this year and it has not let up. Energy dedicated to my personal webspace has been non-existent. I am tired. Hopefully this post can help shake off some of the rust, bring me back to writing and sharing with you lovely folks. I won't be tagging anyone to do this challenge. However, if you're inspired to write your own after reading mine, I'd love for you to share it with me. Why did you start blogging in the first place? Blogging has always been a part of my web experience. Earliest I can remember is building my band a GeoCities website back in high school. I'd share short passages about new song ideas, how last night's show went, stuff like that. I also briefly had a Xanga blog running. My memory is totally faded on what exactly I wrote in there—I'm not eager to dig up high school feelings either—but fairly certain all of those entries are just lost digital history. Having an "online journal" was such a fresh idea at the time. Sharing felt more natural and real before the social media platforms took over. [blows raspberry] I've completely dated myself and probably sound like "old man yells at cloud" right now. Anyway, I pretty much stopped blogging for a while after high school. I turned my efforts back to pen on paper, keeping journals of lyrics, thoughts, and feelings mostly to myself. My dev-focused blogging that you may be familiar with really only spans the last decade, give or take a couple years. What platform are you using to manage your blog and why? At the moment and the forseeable future, I'm using 11ty. I published a short post about migrating to 11ty back in 2021. I still feel the same sentiments and still admire those same people. And many new community friends as well! Have you blogged on other platforms before? I've definitely used WordPress but I can't remember what the heck I was even blogging about during that time. Then I switched to just writing posts directly in HTML files and FTP'ing them up to some server somewhere. Pretty silly in retrospect, but boy did I feel alive. How do you write your posts? Always via laptop, never on my phone. I manage posts in markdown files, push them up to a GitHub repo and let that automatically redeploy my site on Netlify. Editing content is done in VSCode. I've debated switching to some lightweight CMS, connecting to Notion or Obsidian, but why introduce any more complexity and mess with what works fine for me? When do you feel most inspired to write? Typically I'll write up a post about something new I discovered while on my wild coding escapades, whether at work or in my free time. If I have trouble finding solutions to my particular problem on the world wide webs, I'm even more inclined to post about it. Most of my ideas are pursued on weekends, but I've had some early morning or late night weekday sessions. What I'm trying to say is that anytime is a good time for blogging. It's like pizza when it's on a bagel. Do you publish immediately after writing, or do you let it simmer a bit as a draft? It depends. If I had been writing for a long period of time, I find it best to take a breather before publishing. When I feel ready, I'll post and share with a small group for feedback, find grammatical errors. Then I eventually add it to whatever social channels feel right. Used to be Twitter, but straight up screw that garbage temple. I'll likely post on Bluesky, toot on Mastodon. Other times I'll slap a new post on this site and not share it on any socials. Let the RSS feeds do their magic. What's your favorite post on your blog? I don't know if I have a favorite. Can I love them all equally? Well, besides that CSS Marquee one. Damn that blog post for becoming so popular. Any future plans for your blog? Once things settle down in life, I think I'll be ready for a redesign. I had a blast building the current version inspired by Super Mario Wonder. Until then? More blogging. It won't be super soon, but I do have a few zesty article ideas percolating in this old, tired brain.

25th Mar 2025 • 73 votes
The Pixel Canvas Shimmer Effect

I recently stumbled on a super cool, well-executed hover effect from the clerk.com website where a bloom of tiny pixels light up, their glow staggering from the center to the edges of its container. With some available free time over this Thanksgiving break, I hacked together my own version of a pixel canvas background shimmer. It quickly evolved into a pixel-canvas Web Component that can be enjoyed in the demo below. The component script and demo code have also been pushed up to a GitHub repo. Open CodePen demo Usage Include the component script and then insert a pixel-canvas custom element inside the container it should fill. <script type="module" src="pixel-canvas.js"></script> <div class="container"> <pixel-canvas></pixel-canvas> <!-- other elements --> </div> The pixel-canvas stretches to the edges of the parent container. When the parent is hovered, glimmering pixel fun ensues. Options The custom element has a few optional attributes available to customize the effect. Check out the CodePen demo's html panel to see how each variation is made. data-colors takes a comma separated list of color values. data-gap sets the amount of space between each pixel. data-speed controls the general duration of the shimmer. This value is slightly randomized on each pixel that, in my opinion, adds a little more character. data-no-focus is a boolean attribute that tells the Web Component to not run its animation whenever sibling elements are focused. The animation runs on sibling focus by default. There's likely more testing and tweaking necessary before I'd consider using this anywhere, but my goal was to run with this inspiration simply for the joy of coding. What a mesmerizing concept. I tip my hat to the creative engineers over at Clerk.

3rd Dec 2024 • 96 votes
CSS @property and the New Style

The @property at-rule recently gained support across all modern browsers, unlocking the ability to explicitly define a syntax, initial value, and inheritance for CSS custom properties. It seems like forever ago that CSS Houdini and its CSS Properties and Values API were initially introduced. I experimented sparingly over time, reading articles that danced around the concepts, but I had barely scratched the surface of what @property could offer. The ensuing demo explores what's possible in the next generation of CSS. Calls to action Ever seen those sleek, attention-seeking, shiny call-to-action webpage elements? Waves of sites across the web, especially the ones marketing services and software urging for you to "Upgrade your account" or "Sign up today," have discovered the look and latched on. I'm not here to knock it and admittedly think it's kind of fresh. I thought I'd give that style a try myself. Check out the result in the CodePen below. Open CodePen demo There's a ton to unpack in this demo. Let's start with that shine looping around the button. Toggle open the demo's CSS panel to find a collection of @property rules related to those custom properties that need to animate. Here's the one defined for the --gradient-angle: @property --gradient-angle { syntax: "<angle>"; initial-value: 0deg; inherits: false; } The @property rule communicates to the browser that <angle> is the allowed syntax for this custom property and its initial value is 0deg. This enables the browser to smoothly transition from 0deg to 360deg and output a rotating gradient. @keyframes rotate-gradient { to { --gradient-angle: 360deg; } } .rotate-gradient { background: conic-gradient(from var(--gradient-angle), transparent, black); animation: rotate-gradient 10s linear infinite; } I put together a simple gradient spin demo to focus on the handful of lines necessary to render this concept. Open CodePen demo We can achieve the shiny animated border effect by evolving this code a bit. We'll introduce a linear-gradient as the first value of the element's background property and set a background-origin to each value. The origin of the linear-gradient is set to padding-box. This prevents the gradient from spilling into the border area. The conic-gradient origin is set to border-box. This gradient overflows into the space created by the border width. To reveal the rotating conic-gradient, a single-pixel transparent border is added. .border-gradient { background: linear-gradient(black, black) padding-box, conic-gradient(from var(--gradient-angle), transparent 25%, white, transparent 50%) border-box; border: 1px solid transparent; } In the CSS panel of the simple gradient spin demo, uncomment the .border-gradient ruleset to reveal the shiny animated border. Looking pretty slick! For more examples, I've included a bunch of animated gradient border articles in the resources section at the end of the post. Silky smooth hover transitions A few special ingredients help facilitate a buttery smooth gradient transition when the element is hovered. Let's dig into its background values: .shiny-cta { background: linear-gradient(var(--shiny-cta-bg), var(--shiny-cta-bg)) padding-box, conic-gradient( from calc(var(--gradient-angle) - var(--gradient-angle-offset)), transparent, var(--shiny-cta-highlight) var(--gradient-percent), var(--gradient-shine) calc(var(--gradient-percent) * 2), var(--shiny-cta-highlight) calc(var(--gradient-percent) * 3), transparent calc(var(--gradient-percent) * 4) ) border-box; } Each custom property that needs to animate has a syntax declared in its @property definition so that the browser can interpolate between corresponding value changes and transition them seamlessly. The size of the shiny area is determined by the --gradient-percent value. On hover, a higher percentage lengthens the shine. The --gradient-angle-offset value is used to readjust the gradient angle so that the shine doesn't rubber band back and forth on hover. Your browser cannot play the provided video file. Demonstrating the transition behavior without the angle offset value I had to fine-tune the percent and offset values until the shine length and transition felt optically aligned. Finally, the --gradient-shine brightness gets toned down to blend more seamlessly with the adjacent highlight colors. Slow it on down This CSS tip to slow down a rotation on hover truly blew my mind. In the tip's example code, the same rotate animation is declared twice. The second one is reversed and paused, its duration divided in half. When the element is hovered, animation-play-state: running overrides the paused value and slows the rotation to half speed. The mind-blowing part, at least to me, is that the animation speeds back up at the current position when the element is no longer hovered. No snapping back to a start position, no extra wrapper elements necessary. That is one heck of a tip. The call-to-action animations rely on this method to slow them down when the button is hovered. This technique keeps all the rotations and movements in sync as they change speed. Tiny shiny dots Looking even closer, we'll discover pinhole-sized dots shimmering inside the button as the shiny border passes near them. To render this dot pattern, a radial-gradient background is created. .shiny-cta::before { --position: 2px; --space: calc(var(--position) * 2); background: radial-gradient( circle at var(--position) var(--position), white calc(var(--position) / 4), transparent 0 ) padding-box; background-size: var(--space) var(--space); background-repeat: space; } Remember that --gradient-angle custom property? It has returned! But this time, it's being used in a conic-gradient mask that reveals parts of the dot pattern as it rotates. The gradient angle is offset by 45 degrees to align it perfectly with the shiny border rotation. .shiny-cta::before { mask-image: conic-gradient( from calc(var(--gradient-angle) + 45deg), black, transparent 10% 90%, black ); } For one last touch of magic, a gradient containing the highlight color is added to the ::after pseudo element, spinning in unison with the shine area. These highlights flowing through the button add a pleasant, welcoming ambience that was previously missing. Enhancing the hover colors The hover styles looked decent. But they didn't seem totally finished. I felt the desire to enhance. Create more depth. Make it pop, as they say. The button's ::before and ::after pseudo elements were already in use so I wrapped the button text in a span element. A blurred box-shadow containing the highlight color is applied to one of its pseudo elements which is then expanded to fill the button dimensions. On hover, the pseudo element slowly scales up and down, evoking a vibe similar to relaxed breathing. Paired with the spinning highlight color inside the button, the effect finally resonated with me. This intricately designed call-to-action button felt complete. In with the new style Many of the above techniques would have been nearly impossible only a short time ago. Explicitly defining custom properties unlocks a great big world of opportunity. I'm especially eager to see how @property will be utilized in large-scale applications and design systems. Providing Type Definitions for CSS with @property by Stephanie Eckles as well as Adam Argyle's Type safe CSS design systems with @property are just a couple glimpses into a really promising future for publishing our CSS. Helpful resources Animated CSS gradient borders (no JavaScript, no hacks) Creating an animated gradient border with CSS CSS border animations Animating a CSS Gradient Border CSS border ripple effect The Times You Need A Custom @property Instead Of A CSS Variable @property: Next-gen CSS variables now with universal browser support

2nd Sep 2024 • 38 votes
Center Items in First Row with CSS Grid

Imagine the following section on a website: A collection of elements, like a series of cards with marketing information, are presented in a grid display. The elements are arranged in rows of three. When there are an odd number of elements left over, they will be center-aligned horizontally. There are a few ways to accomplish styling such a layout. Controlling Leftover Grid Items with Pseudo-selectors by Michelle Barker shares a clever CSS Grid solution. But here's a twist: What if the centered odd number of elements should appear in the first row instead of the last? I've included a CodePen demo at the end of this article if you'd like to jump ahead. Otherwise, continue on a journey of style discovery. Grid Stacks Is it a trapezoid grid? Brick grid? Grid pyramid? Pyragrid? For the sake of this article, I ultimately picked a more generic name, calling it Grid Stack. Here's how we'll build a Grid Stack that contains five cards displayed in a three-column grid. .grid-stack { display: grid; grid-template-columns: repeat(6, 1fr); > * { grid-column-end: span 2; } > :first-child { grid-column-start: 2; } } CSS nesting is being used here, which is newly supported across major browsers. Not feeling ready for that yet? We can move the nested rules into their own top-level rulesets. They just need to start with the parent selector name, i.e. .grid-stack > * { } The parent grid-stack container produces a template with six columns. Notice that the grid-template-columns repeat count is double the amount of columns we want visually present in each row. Each child element will then span across two columns instead of one. Finally, the first child element is aligned to the start of the second column. The result is a visually centered top row. Grid lines (enabled in dev tools) help show where each child element is positioned on the grid. Variations So far, the styles we've created only apply when there are five cards positioned across three columns. We may want different variations depending on our designs. Three cards displayed in two columns? Seven cards in four? Let's tweak the above ruleset to utilize a CSS variable for the grid-template-columns repeat count. Recall that this value should be twice the amount of expected columns. .grid-stack { display: grid; grid-template-columns: repeat(calc(var(--columns) * 2), 1fr); > * { grid-column-end: span 2; } > :first-child { grid-column-start: 2; } } The --columns value gets doubled as it passes through the CSS calc() function. Now we're able to define the preferred amount of columns directly on the parent container. <div class="grid-stack" style="--columns: 2"></div> <div class="grid-stack" style="--columns: 3"></div> <div class="grid-stack" style="--columns: 4"></div> Demo Open CodePen demo Bonus! Pyramid stacks In the above demo, you may have discovered some configurations for the Grid Stack that result in a pyramid-style stack. Maybe now we can call it a pyragrid? Still not sure about that one... Anyway, to achieve this layout involves a few extra ingredients. We'll need to adjust the grid-column-start position of the first element in each row. Let's jump right to the last example with the grid-stack-15 selector: .grid-stack-15 { --columns: 5; > :first-child { grid-column-start: 5; } > :nth-child(2) { grid-column-start: 4; } > :nth-child(4) { grid-column-start: 3; } > :nth-child(7) { grid-column-start: 2; } } This calls for a five-column grid visually, so it sets --columns: 5. Recall that this value gets doubled and outputs a template of ten columns. We'll nudge the first item in each row with grid-column-start. The top row element's start position is equal to the --column value. Subsequent rules will decrease this value by 1. It's surely possible to develop a Sass or PostCSS function that could dynamically generate this CSS output but that seemed a bit overkill for the demo. As an added bonus, check out Temani Afif's Stack Overflow answer that styles elements in a pyramid using float and shape-outside. Very cool! Limitations Each layout variation expects a specific odd-number of child elements to be rendered. I have explored ways of automatically adjusting the layout based on the element count but there were too many edge cases to consider. It created more problems than it solved. Additionally, while these layouts work nicely on a wider viewport, it may not fare as well where less space is available. A media or container query ruleset can ensure our content adapts appropriately, but it certainly couldn't be a one-size-fits-all conditional set of styles. From the community Updated on September 5th — Kevin Powell reached out on Mastodon and shared a CodePen Example that uses :has(:nth-child():last-child) to result in this layout. I certainly dig the approach! Keep in mind that the same aforementioned limitations still apply.

19th Aug 2024 • 55 votes

More in design

There Will Always Be Someone Faster, Cheaper, and Better

My feed is mostly designers talking about AI, and the temperature of all of it is the same: Something is coming. You need to get out in front of it. You need to be the one doing the changing rather than the one being changed. I don’t disagree with most of the individual advice. But read enough of it in one sitting and what surfaces underneath is competition — ordinary competition, of the kind that has always been there. The worry takes two shapes. Either the client no longer needs a designer, because the tool supplies the design. Or another designer, holding the tool, takes the job you would have had. Both are worth taking seriously. Neither is new. The Permanent Condition There has always been someone faster than me, someone cheaper than me, and someone better than me. This was true the day I started my career and it will be true on the day it ends. It describes a market, not an era. Designers — actually, all in the professional services — have a folk theorem for living with this reality: Fast, cheap, good — pick two. I’ve heard it said and read it written countless times. I’ve said it and written it, too, to win arguments and contracts. Today I got curious about where the saying comes from, and went looking. Nobody knows. The phrase itself gets attributed to something called the Common Law of Business Balance, and from there to John Ruskin — an attribution with no evidence behind it whatsoever. The idea underneath has a slightly better paper trail: an engineer named Martin Barnes drew it as a triangle, with cost, time and quality at the corners, for a project management course in 1969. Even that isn’t an origin. Versions of it were in use by the 1950s, and no one can say who put it that way first. This beaten-to-cliché piece of professional wisdom is a cipher. Which is useful to remember because it’s also, often, wrong. There are as many designers who are fast and cheap and good as there are those who are slow, expensive, and bad. I have worked with some of each. Thinking back on this now, I am surprised that no one ever responded to the “pick two” challenge by saying, “I want all three, I just won’t be getting them from you.” Faster and Cheaper My entire career has been punctuated by tools that sped up production. Desktop publishing collapsed typesetting and layout into something one person could do in an afternoon. Then Photoshop. Then the rest of the Adobe suite, Illustrator and InDesign. Then Sketch. Then Figma. Each one arrived, each one made producing the work materially faster, and each one set off a race to master it before everybody else did. Each of them may have changed what a day at work felt like, but none ended the profession. What makes faster feel dangerous is that we convert it immediately into cheaper, and we do that because most designers price their time. If the work takes four hours instead of twelve, charging the twelve-hour price feels indefensible, and somebody else will happily charge for four. That’s a pricing problem rather than a technology problem, and it’s been the same pricing problem through every acceleration I just listed. A practice that charges for outcomes absorbs a speed increase as margin. A practice that charges for hours absorbs it as a pay cut. As for cheaper, it arrived long before any of this, and it arrived by geography. I work closely with a designer who lives in the Balkans. They are, by some distance, among the most talented people I have ever worked with. They didn’t go to design school. I went to the Rhode Island School of Design, which bought me a credential the marketplace still reads as shine, and I’m clear-eyed about the fact that on the measure that actually matters they aren’t behind me. They can also charge less than I can, because rent where they live isn’t rent where I live. None of that has anything to do with AI. It has been available to every client I have ever had as long as we all had an internet connection. And just in case anyone thinks that professional design competition from overseas is a more recent phenomenon, the first designer I met when I visited Malaysia in 2004 was born and raised in Penang, had no design degree, and had dozens of clients most of whom were headquartered in the United States. He was fast, cheap, and good. That’s why they paid him, rather than someone else. What Better Means There is an objection to what I’ve said so far. I’ll try to put it in its best form rather than a convenient one. Every tool I listed made production faster without making the thing. Desktop publishing didn’t design the page. Photoshop didn’t choose the image. Figma doesn’t know what the product should be. AI, as a design tool, is different in kind, because it produces the artifact. So the comparison to Sketch and Illustrator is a weaker argument than it sounds. I think that objection is right about the tool and wrong about the job. It only holds if the artifact itself is better. Imagine two designers given the same brief — an identity system for the same client. Put the results in front of a panel, blind, with no knowledge of how either came to be. The panel picks a winner. They’re qualified to do it and their reasoning will be sound. Now ask the client which designer they’d hire again. It is by no means a given that the panel and the client will agree. The two answers can fork, and when they do it isn’t because the client has poor taste. The panel is assessing an artifact. The client went through something. They were asked questions nobody had asked them before. They had a position drawn out of them that they couldn’t have articulated on their own. At some point they were told that the thing they wanted was the wrong thing, by somebody who had earned enough credit to say so. None of that is necessarily visible in the file — nor should it be. I wrote recently that expertise is becoming subjective — that where knowledge is cheap, experience is what differentiates. If that’s true, it cuts in both directions. Experience doesn’t only distinguish you; it aligns you with some people and not others. A prospect’s fit with how you’ve worked, what you’ve lived through, and how you talk about it is doing as much work as the expertise itself. Which means there’s no better in the abstract. There’s better, for them. There’s also a posture question here, and it matters more than it looks. To believe that your prospect couldn’t do better than you is a level of audacity that runs counter to how every designer I know actually thinks — and yet it’s close to what we’re advised to project. In a world this populous it’s absurd to imagine you’re the best at anything, and more absurd still to build a public persona on the premise. That advice persists because it works on a seductive technicality: whether or not you believe you’re the best, behaving as though you are turns out to be convincing to other people. Notice who that advice is sold to. We’re the market for it; our clients have never heard of it. Rejecting that is accuracy rather than modesty. The world is too crowded with designers to spend a second ranking yourself among them. Beside the posture, the same feed sells a tempo, and that carries its own irony. All that commentary argues that you should move faster in order to stay ahead. But if experience is the differentiator, speed is the thing eroding it. Going slowly through a problem — getting it wrong, trying it on a real client, finding out — is where the differentiation was built all along. The race is being run on the wrong road, and running it costs you the one you needed. Nobody Asks About Tools When I moved from Sketch to Figma I thought it mattered. I had reasons. I argued about it. Designers I respected stayed on Sketch for years afterward and we all thought the other was wrong. In the end, it didn’t really matter. I know designers who still work primarily in Photoshop. In the last twelve months, I saw a website design produced in Illustrator. Of course I think these are mistakes, bordering on the absurd. But it’s also none of my business, and as far as I can tell it hasn’t cost them a job. Isn’t that interesting? But it makes some sense. After all, nobody has ever asked me what tools I use. Except another designer. I don’t know what this technology does to the profession over twenty years, and I notice that the people who claim to know are usually the ones with something to sell. What I can see from here is smaller and steadier than that. Somebody out there is faster than me. Somebody is cheaper. Somebody is better — that isn’t modesty, it’s the realism that comes from decades in the work, and I have met them. All three have been a phone call away from every client I have ever worked with. Any prospect can have them, faster, and at a cheaper price. But they don’t have me. And they don’t have you.

7 hours ago • 1 votes
You are the best artist
5 days ago • 1 votes
Code review was never about the code

If you work in software (as a designer, developer, PM, or any adjacent role), you’re probably aware of just how much AI has changed the way code is created. And if you’re an engineer, you’re definitely feeling the downstream impacts of AI’s speed. Way, way more code is being written. But the way we review code hasn’t changed that much. But if you’re not familiar, code review is a step in the software development process where developers look at code before it gets shipped. It’s been a cornerstone of engineering teams’ workflows for almost as long as there have been engineering teams. AI coding has turned code review into a bottleneck; a human can only look at so much code, so reviews have started taking longer, and each engineer’s review backlog is growing exponentially.1 AI code review tools have stepped into the breach, eliciting an earnest grumble from developers: AI reviewing AI-written code is like a student grading their own test. Execs don’t have much sympathy, though. We can move faster, so we should. So teams are stuck in a traffic jam, trying to figure out how to review code faster, without sacrificing quality. I’d like to argue that the source of this problem is more fundamental than speed vs. quality. We’re in this situation because somewhere along the way we swapped one question for another. Code review was always meant as a verification step: does the code produce the outcome we want? But it’s a hard question to answer, so we started asking an easier one instead: is this good code? Let’s go back to the original question. Code review should get better at verifying outcomes — faster, yes, but more accurately, and for the system as a whole, not just single lines of code. What is code worth? The premise of code review is that code can be good or bad, and that bad code can be turned into good code, and that good code is important. This is a sound premise. Bugs are obviously bad for business. But we’ve made an attribute substitution: we decide whether code looks good as a stand-in for whether the software works. Engineers use mental shortcuts to determine the goodness or badness of code. They range from very well-documented patterns (use guard clauses to return early, use pure functions where possible) to vibe-based ‘code smells’ (magic numbers, deep nesting). But these aren’t written in stone. They are debated and re-evaluated constantly. Even if there are inviolable definitions of good code, writing good code isn’t guaranteed to result in valuable software products. Sometimes, it works out that way; before Facebook bought it, WhatsApp kept its codebase lean enough that 35 engineers could serve 450 million users.2 But there are far, far more examples of software succeeding despite being written poorly: Undertale would never have passed a single code review (almost all of its dialog was one giant switch statement), but became one of the most beloved games of the past decade. Good code does not strictly equal good software. Code itself has no intrinsic value. So what are we actually reviewing when we review code? Verifying There’s just no substitute for actually running the code and verifying that it does its job well. I don’t mean unit tests, or even integration tests, but running the code in total, a full end-to-end test. Doing this at scale for large applications is difficult. At an org with hundreds of AI-enabled engineers, you likely have thousands of pull requests a day. Spinning up and breaking down complete copies of your entire application (likely, application cluster) is time- and resource-intensive. Even if you do manage it, verifying under realistic conditions may be completely infeasible (imagine simulating the activity that Stripe handles — 1.75% of global GDP). Which is to say that you won’t know if your code works or not until it’s in users’ hands. Traditional code review has two outcomes: You are confident enough that the code works well enough to ship it You are not confident enough that the code works well enough to ship it Verifying your code under the most realistic scenario possible (i.e., yolo, straight to production) has three: You are sure the code works, and it’s already shipped. Good job! You are sure the code doesn’t work, so you improve the code until it works You don’t know because the system failed in an interesting way. Instead of confidence, you have information, and can use it to improve the system. Verifying a major change by shipping it is risky, but the closer you can get to it, the more your review is actually worth. Feature flags, canary releases, observability and CI/CD — the stuff of modern devops — have all been commoditized to the point where it’s cheap to build in the safeguards that make the yolo approach tractable (even at Stripe scale). Stop reviewing code3 You’re probably doing code review for the wrong reasons. Let AI do the work of making code better: tracing code paths, finding, and fixing obvious issues. And if it’s a student grading their own test, so what? AI can make the code better. People can check if it works. Invest in the tools to verify your code in realistic conditions, deploying it directly to users if possible. Use your own eyes and brain to see it working, or see it not working. Learn from the difference, share what you learn with your team, and build feedback loops that improve the system the more it fails. And eventually the failures will become rare and incredibly interesting. Footnotes & References GitHub saw 1 billion commits in all of 2025; by April 2026 it was seeing 275 million a week. ↩︎ Cade Metz, “Why WhatsApp Only Needs 50 Engineers for Its 900M Users,” Wired, September 15, 2015. ↩︎ Of course, there are situations where safety means protecting human lives or massive economic outcomes, and reviewing code is the only safe option. The IRS shouldn’t ship updates to the code it uses to disburse tax refunds without reviewing the code. Waymo shouldn’t ship updates to its self-driving cars’ algorithms without reviewing the code. But you probably aren’t working for the IRS or Waymo. And if you are, get in touch ;) ↩︎

a week ago • 2 votes
The new age of software

If you think about it, there is *a lot* of software that hasn’t been built across hundreds of industries because our best paid designers and engineers were occupied with building social networks and ad networks. "The best minds of my generation are thinking about how to make

2 weeks ago • 2 votes
Prompting as self-portrait

Weekly curated resources for designers — thinkers and makers.

3 weeks ago • 1 votes
📚 BoredReading

You seem to be enjoying this.

Join free to unlock everything.

Create free account

Already have an account? Sign in