Developer News
AI Has Flipped Software Development
For years, it's been faster to create mockups and prototypes of software than to ship it to production. As a result, software design teams could stay "ahead" of engineering. Now AI coding agents make development 10x faster, flipping the traditional software development process on its head.
In my thirty years of working on software, the design teams I was part of were typically operating "out ahead" of our software development counterparts. Unburdened by existing codebases, technical debt, performance, and infrastructure limitations, designers could work quickly in mockups, wireframes, and even prototypes to help envision what we could or should build before time and effort was invested into actually building it.
While some software engineering teams could ship in days, in most (especially larger) organizations, building new features or redesigning apps could take months if not quarters or years. So there was plenty of time for designers to explore and iterate. This was also reflected in the ratio of designers to developers in most companies: an average of one designer for every twenty engineers.
When designs did move to the production engineering phase, there'd (hopefully) be a bunch of back and forth to resolve unanswered questions, new issues that came up, or changing requirements. A lot of this burden fell on engineering as they encountered edge cases, things missing in specs, cross-device capability differences, and more. What it added up to though, was that the process to build and launch something often took longer than the process to design it.
AI coding tools change this dynamic. Across several of our companies, software development teams are now "out ahead" of design. To be more specific, collaborating with AI agents (like Augment Code) allows software developers to move from concept to working code 10x faster. This means new features become code at a fast and furious pace.
When software is coded this way, however, it (currently at least) lacks UX refinement and thoughtful integration into the structure and purpose of a product. This is the work that designers used to do upfront but now need to "clean up" afterward. It's like the development process got flipped around. Designers used to draw up features with mockups and prototypes, then engineers would have to clean them up to ship them. Now engineers can code features so fast that designers are ones going back and cleaning them up.
So scary time to be a designer? No. Awesome time to be a designer. Instead of waiting for months, you can start playing with working features and ideas within hours. This allows everyone, whether designer or engineer, an opportunity to learn what works and what doesn’t. At its core rapid iteration improves software and the build, use/test, learn, repeat loop just flipped, it didn't go away.
In his Designing Perplexity talk at Sutter Hill Ventures, Henry Modisett described this new state as "prototype to productize" rather than "design to build". Sounds right to me.
How to Discover a CSS Trick
Do we invent or discover CSS tricks? Michelangelo described his sculpting process as chiseling away superfluous material to reveal the sculpture hidden inside the marble, and Stephen King says his ideas are pre-existing things he locates and uncovers “like fossils in the ground.” Paragraph one is early for me to get pretentious enough to liken myself to those iconic creative forces, but my work on CSS-Tricks feels like “discovering,” not “inventing,” secret synergies between CSS features, which have been eyeing each other from disparate sections of the MDN web docs and patiently waiting for someone to let them dance together in front of the world.
Matchmaking for CSS featuresA strategy for finding unexpected alliances between CSS features to achieve the impossible is recursive thinking, which I bring to the CSS world from my engineering background. When you build recursive logic, you need to find an escape hatch to avoid infinite recursion, and this inception-style mindset helps me identify pairings of CSS features that seem at odds with each other yet work together surprisingly well. Take these examples from my CSS experiments:
- What if view-timeline took control of the thing that triggers view-timeline? This led to a pairing between view-timeline and position: fixed. These two features are like a bickering yet symbiotic “odd couple” at the heart of my web-slinger.css library for scroll-triggered animations in pure CSS.
- What if keyframe animations could trigger other keyframe animations? This idea led to a throuple comprised of keyframe animations, style queries, and animation-play-state, which together can simulate collision detection in CSS.
- What if scroll-state:(scrollable: value) could control which directions are scrollable? That question led to a scrollytelling version of a “Choose Your Own Adventure,” which — wait, I haven’t published that one yet, but when I do, try to look surprised.
Indeed, Mark Twain thought new ideas don’t exist — he described them as illusions we create by combining ideas that have always existed, turning and overlaying them in a “mental kaleidoscope” to “make new and curious combinations.” It doesn’t mean creating is easy. No more than a safe can be cracked just by knowing the possible digits.
This brings back memories of playing Space Quest III as a kid because after you quit the game, it would output smart-aleck command-line messages, one of which was: “Remember, we did it all with ones and zeros.” Perhaps the point of the mock inspirational tone is that we likely will not be able to sculpt like Michelangelo or make a bestselling game, even if we were given the same materials and tools (is this an inspirational piece or what?). However, understanding the limits of what creators do is the foundation for cracking the combination of creativity to open the door to somewhere we haven’t been. And one truth that helps with achieving magic with CSS is that its constraints help breed creativity.
Embracing limitationsBeing asked “Why would you do that in CSS when you could just use JavaScript?” is like if you asked me: “Why would you write a poem when it’s easier to write prose?” Samuel Coleridge defined prose as “words in their best order,” but poetry as “the best words in the best order.” If you think about it, the difference between prose and poetry is that the latter is based on increased constraints, which force us to find unexpected connections between ideas.
Similarly, the artist Phil Hansen learned that embracing limitation could drive creativity after he suffered permanent nerve damage in his hand, causing it to jitter, which prevented him from drawing the way he had in the past. His early experiments using this new mindset included limiting himself to creating a work using only 80 cents’ worth of supplies. This dovetails with the quote from Antoine de Saint-Exupéry often cited in web design, which says that perfection is achieved when there is nothing left to take away.
Embracing nothingnessThe interesting thing about web design is how much it blends art and science. In both art and science, we challenge assumptions about whether commonsense relationships of cause and effect truly exist. Contrary to the saying in vernacular that “you can’t prove a negative,” we can. It’s not necessarily harder than proving a positive. So, in keeping with the discussion above of embracing limitations and removing the superfluous until a creation reveals itself, many of my article ideas prove a negative by challenging the assumption that one thing is necessary to produce another.
- Maybe we don’t need JavaScript to produce a Sudoku solver, a Tinder-style swiper, or a classic scroll-driven animation demo.
- Maybe we don’t need checkbox hacks to make CSS games.
- Maybe we don’t need to hack CSS at all to recreate similar effects to what’s possible in browsers that support the CSS if() function.
- Maybe I can impart web dev wisdom on CSS-Tricks without including CSS at all, by sharing the “source code” of my thought process to help make you a better developer and a better person.
Sometimes we can make a well-worn idea new again by taking it to the extreme. Seth Godin coined the term “edgecraft” to describe a technique for generating ideas by pushing a competitive advantage as far to the edge as the market dares us to go. Similarly, sometimes you can take an old CSS feature that people have seen before, but push it further than anyone else to create something unique. For example:
- CSS-Tricks covered checkbox hacks and radio button hacks back in 2011. But in 2021, I decided to see if I could use hundreds of radio button hacks using HTML generated with Pug to create a working Sudoku app. At one point, I found out that Chrome dev tools can display an infinite spinner of death when you throw too much generated CSS at it, which meant I had to limit myself to a 4×4 Sudoku, but that taught me more about what CSS can do and what it can’t.
- The :target selector has existed since the 2000s. But in 2024, I took it to the extreme by using HAML to render the thousands of possible states of Tic Tac Toe to create a game with a computer opponent in pure CSS. At one point, CodePen refused to output as much HTML as I had asked it to, but it’s a fun way for newcomers to learn an important CSS feature; more engaging in my opinion than a table of contents demo.
Chris Coyier has written about his distaste for the gatekeeping agenda hidden behind the question of whether CSS is a programming language. If CSS isn’t deemed as “real” programming, that can be used as an excuse to hold CSS experts in less esteem than people who code in imperative languages, which leads to unfair pay and toxic workplace dynamics.
But maybe the other side always seems greener due to the envy radiating from the people on that side, because as a full-stack engineer who completed a computer science degree, I always felt left out of the front-end conversations. It didn’t feel right to put “full stack developer” on my résumé when the creation of everything users can see in a web app seemed mysterious to me.
And maybe it wasn’t just psychosomatic that CSS made my head hurt compared to other types of coding because research indicates if you do fMRIs on people who are engaged in design tasks, you see that design cognition appears to involve a unique cognitive profile compared to conventional problem-solving, reflected in the areas of the brain that light up on the fMRIs. Studies show that the brain’s structure changes as people get better at different types of jobs. The brain’s structural plasticity is reminiscent of the way different muscles grow more pronounced with different types of exercise, but achieving what some of my colleagues could with CSS when my brain had been trained for decades on imperative logic felt about as approachable as lifting a car over my head.
The intimidation I felt from CSS started to change when I learned about the checkbox hack because I could relate to hiding and showing divs based on checkboxes, which was routine in my work in the back of the front-end. My designer workmate challenged me to make a game in one night using just CSS. I came up with a pure text adventure game made out of radio button hacks. Since creative and curious people are more sensitive to novel stimuli, the design experts on my team were enthralled by my primitive demo, not because it was cutting-edge gameplay but because it was something they had never seen before. My engineering background was now an asset rather than a hindrance in the unique outsider perspective I could bring to the world of CSS. I was hooked.
The hack I found to rewire my brain to become more CSS-friendly was to find analogies in CSS to the type of problem-solving I was more familiar with from imperative programming:
- CSS custom properties are like reactive variables in Vue.
- The :target selector in CSS is like client-side routing in a single-page application.
- The min() and max() functions in CSS can be used to simulate some of the logical operations we take for granted in imperative programming.
So if you are still learning web development and CSS (ultimately, we are all still learning), instead of feeling imposter syndrome, consider that the very thing that makes you feel like an outsider could be what enables you to bring something unique to your usage of CSS.
Finding the purposeExcited as I was when my CSS hacking ended up providing the opportunity to publish my experiments on CSS-Tricks, the first comment on the first hack I published on CSS-Tricks was a generic, defeatist “Why would you do that?” criticism. The other comments popped up and turned out to be more supportive, and I said in a previous article that I’ve made my peace with the fact that not everybody will like my articles. However, this is the second article in which I’ve brought up the critical comment from back in 2021. Hmm…
Surely it wasn’t the reason I didn’t write another CSS-Tricks article for years. And it’s probably a coincidence that when I returned to CSS-Tricks last year, my first new article was a CSS hack that lends itself to accessibility after the person who left the negative comment about my first article seemed to have a bee in their bonnet about checkbox hacks breaking accessibility, even in fun CSS games not intended for production. Then again, limiting myself to CSS hacking that enables accessibility became a source of inspiration. We can all do with a reminder to at all times empathize with users who require screen readers, even when we are doing wacky experimental stuff, because we need to embrace the limitations not just of CSS but of our audience.
I suppose the reason the negative comment continues to rankle with me is that I agree that clarifying the relevance and purpose of a CSS trick is important. And yet, if I’m right in saying a CSS trick is more like something we discover than something we make, then it’s like finding a beautiful feather when we go for a walk. At first, we pick it up just because we can, but if I bring you with me on the journey that led to the discovery, then you can help me decide whether the significance is that the feather we discovered makes a great quill or reveals that a rare species of bird lives in this region.
It’s a journey versus destination thing to share the failures that led to compromises and the limitations I came up against when pushing the boundaries of CSS. When I bring you along on the route to the curious item I found, rather than just showing you that item, then after we part ways, you might retrace the steps and try a different fork in the path we followed, which could lead you to discover your own CSS trick.
How to Discover a CSS Trick originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Atomic Design Certification Course
Brad Frost introduced the “Atomic Design” concept wayyyy back in 2013. He even wrote a book on it. And we all took notice, because that term has been part of our lexicon ever since.
It’s a nice way to divide web designs into separate layers of concern, leaning into biology terms to help scope their context by size:
- Atoms
- Molecules
- Organisms
- Templates
- Pages
Atoms are part of molecules, which are part of organisms, which make up templates, which become full-blown pages. It’s composable design that’s centered on consistency, reusability, and maintainability. Beautiful. We’ve covered this a bunch over the years.
Want to get fully versed in how it works? If so, you’re in luck because Brad and his brother, Ian, are in the process of publishing an entire online course about Atomic Design. It’s in presale for $50 (with discounts for teams).
Normally, I like to jump into a course and check it out before sharing it. But this is Brad and all he does is wonderful things. For example:
- The guy arranged his very own concert!
- He inspires folks to live creatively and authentically with his Wake Up Excited! podcast.
- There’s another podcast where he encourages designers and developers to Open Up! about their careers and what drives them. I get to participate in that one!
- Dude is a solid drummer who records himself playing along to his favorite albums, completely unrehearsed.
- He’s got a crap ton of tracks he writes and records as well.
Oh, and his newsletter is pretty awesome, too. And I’m sure I’m leaving out more things he has floating around the web, but you get the point: he’s incredibly knowledgeable on the topic, is a highly-effective educator and speaker, and most importantly, has an infectious positive posture about him.
I know the Atomic Design course will be just as awesome. Preordered!
Atomic Design Certification Course originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
A Primer on Focus Trapping
Focus trapping is a term that refers to managing focus within an element, such that focus always stays within it:
- If a user tries to tab out from the last element, we return focus to the first one.
- If the user tries to Shift + Tab out of the first element, we return focus back to the last one.
This whole focus trap thing is used to create accessible modal dialogs since it’s a whole ‘nother trouble to inert everything else — but you don’t need it anymore if you’re building modals with the dialog API (assuming you do it right).
Anyway, back to focus trapping.
The whole process sounds simple in theory, but it can quite difficult to build in practice, mostly because of the numerous parts to you got to manage.
Simple and easy focus trapping with Splendid LabzIf you are not averse to using code built by others, you might want to consider this snippet with the code I’ve created in Splendid Labz.
The basic idea is:
- We detect all focusable elements within an element.
- We manage focus with a keydown event listener.
The above code snippet makes focus trapping extremely easy.
But, since you’re reading this, I’m sure you wanna know the details that go within each of these functions. Perhaps you wanna build your own, or learn what’s going on. Either way, both are cool — so let’s dive into it.
Selecting all focusable elementsI did research when I wrote about this some time ago. It seems like you could only focus an a handful of elements:
- a
- button
- input
- textarea
- select
- details
- iframe
- embed
- object
- summary
- dialog
- audio[controls]
- video[controls]
- [contenteditable]
- [tabindex]
So, the first step in getFocusableElements is to search for all focusable elements within a container:
export function getFocusableElements(container = document.body ) { return { get all () { const elements = Array.from( container.querySelectorAll( `a, button, input, textarea, select, details, iframe, embed, object, summary, dialog, audio[controls], video[controls], [contenteditable], [tabindex] `, ), ) } } }Next, we want to filter away elements that are disabled, hidden or set with display: none, since they cannot be focused on. We can do this with a simple filter function.
export function getFocusableElements(container = document.body ) { return { get all () { // ... return elements.filter(el => { if (el.hasAttribute('disabled')) return false if (el.hasAttribute('hidden')) return false if (window.getComputedStyle(el).display === 'none') return false return true }) } } }Next, since we want to trap keyboard focus, it’s only natural to retrieve a list of keyboard-only focusable elements. We can do that easily too. We only need to remove all tabindex values that are less than 0.
export function getFocusableElements(container = document.body ) { return { get all () { /* ... */ }, get keyboardOnly() { return this.all.filter(el => el.tabIndex > -1) } } }Now, remember that there are two things we need to do for focus trapping:
- If a user tries to tab out from the last element, we return focus to the first one.
- If the user tries to Shift + Tab out of the first element, we return focus back to the last one.
This means we need to be able to find the first focusable item and the last focusable item. Luckily, we can add first and last getters to retrieve these elements easily inside getFocusableElements.
In this case, since we’re dealing with keyboard elements, we can grab the first and last items from keyboardOnly:
export function getFocusableElements(container = document.body ) { return { // ... get first() { return this.keyboardOnly[0] }, get last() { return this.keyboardOnly[0] }, } }We have everything we need — next is to implement the focus trapping functionality.
How to trap focusFirst, we need to detect a keyboard event. We can do this easily with addEventListener:
const container = document.querySelector('.some-element') container.addEventListener('keydown', event => {/* ... */})We need to check if the user is:
- Pressing tab (without Shift)
- Pressing tab (with Shift)
Splendid Labz has convenient functions to detect these as well:
import { isTab, isShiftTab } from '@splendidlabz/utils/dom' // ... container.addEventListener('keydown', event => { if (isTab(event)) // Handle Tab if (isShiftTab(event)) // Handle Shift Tab /* ... */ })Of course, in the spirit of learning, let’s figure out how to write the code from scratch:
- You can use event.key to detect whether the Tab key is being pressed.
- You can use event.shiftKey to detect if the Shift key is being pressed
Combine these two, you will be able to write your own isTab and isShiftTab functions:
export function isTab(event) { return !event.shiftKey && event.key === 'Tab' } export function isShiftTab(event) { return event.shiftKey && event.key === 'Tab' }Since we’re only handling the Tab key, we can use an early return statement to skip the handling of other keys.
container.addEventListener('keydown', event => { if (event.key !== 'Tab') return if (isTab(event)) // Handle Tab if (isShiftTab(event)) // Handle Shift Tab /* ... */ })We have almost everything we need now. The only thing is to know where the current focused element is at — so we can decide whether to trap focus or allow the default focus action to proceed.
We can do this with document.activeElement.
Going back to the steps:
- Shift focus if user Tab on the last item
- Shift focus if the user Shift + Tab on the first item
Naturally, you can tell that we need to check whether document.activeElement is the first or last focusable item.
container.addEventListener('keydown', event => { // ... const focusables = getFocusableElements(container) const first = focusables.first const last = focusables.last if (document.activeElement === last && isTab(event)) { // Shift focus to the first item } if (document.activeElement === first && isShiftTab(event)) { // Shift focus to the last item } })The final step is to use focus to bring focus to the item.
container.addEventListener('keydown', event => { // ... if (document.activeElement === last && isTab(event)) { first.focus() } if (document.activeElement === first && isShiftTab(event)) { last.focus() } })That’s it! Pretty simple if you go through the sequence step-by-step, isn’t it?
Final callout to Splendid LabzAs I resolve myself to stop teaching (so much) and begin building applications, I find myself needing many common components, utilities, even styles.
Since I have the capability to build things for myself, (plus the fact that I’m super particular when it comes to good DX), I’ve decided to gather these things I find or build into a couple of easy-to-use libraries.
Just sharing these with you in hopes that they will help speed up your development workflow.
Thanks for reading my shameless plug. All the best for whatever you decide to code!
A Primer on Focus Trapping originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Designing Software for AI Agents
From making apps, browsing the Web, to creating files, today's AI agents today can take on an increasing number of computing tasks on their own. But the software underlying these capabilities, wasn't made for agents. It was designed and built for people to use. As such there's an opportunity, and perhaps an increasing need, to rethink these systems for agent use.
When building agent-based AI applications, you'll likely butt up against a number of situations where existing software isn't optimized for what thinking machines can do. For instance, Web search. Nearly every agent-based AI application makes use of information on the Web to get things done. But Web Search APIs weren't written with agents in mind.
They provide a limited number of search results and a condensed snippet format that lines up more with how people use Web search interfaces. We get a page of ten blue links and scan them to decide which one to click. But AI agents aren't people. Not only can they make sense of many more search results at once, but their performance usually improves with larger document summaries and contents. People on the other hand, are unlikely to read through all search results before making a decision. So search APIs could certainly be rethought for agents.
Similarly, when agents are developing applications or collecting data, they can make use of databases. But once again databases were designed and built for people to use not AI agents. And once again they can be rethought for agents, which is what we did with our most recent launch: AgentDB.
Agents can (and do) produce 1000x more databases than people every day, so the process of spinning up and managing any database for an agent needs to be as easy and maintenance-free as possible. Most of the databases AI agents create will be short-lived after serving their initial purpose. But some databases will be used again and others still will be used regularly.
With this kind of volume costs can become an issue, so keeping that many databases available needs to be as cost effective as possible. Last but not least, the content of databases needs to work well as context for AI models so agents can use this data as part of their tasks.
AgentDB is a database system designed around these considerations. With AgentDB, creating a database only requires a Universally Unique Identifier (UUID). There's no setup or configuration step. So whenever an AI agent decides it needs a database, it has one simply by creating a UUID. No forms or set-up wizards involved.
Databases in AgentDB are stored as files not hosted services requiring compute and maintenance. If an AI agent needs to query a database or append to it, it can. But if it never needs to access it again, the database is just a file. That means you're only paying for the cost of storage to keep it around and because AgentDB databases are just files, they scale. Meaning they can easily keep up with the scale of AI agents.
To make data within each AgentDB database easily accessible as context for AI models, every AgentDB account is also an MCP server. This makes the data portable across AI applications as long as they support MCP server connections (which most do).
Altogether this example illustrates how even the most fundamental software infrastructure systems, like databases, can be rethought for the age of AI. The AgentDB database system doesn't look like a hosted database as a service solution because it's not designed and built for database admins and back-end developers. It's built for today's thinking machines.
And as agents take on more computing tasks for people, it won't be the only software made with agents as first class users.
Designing Software for AI Agents
From making apps, browsing the Web, to creating files, today's AI agents today can take on an increasing number of computing tasks on their own. But the software underlying these capabilities, wasn't made for agents. It was designed and built for people to use. As such there's an opportunity, and perhaps an increasing need, to rethink these systems for agent use.
When building agent-based AI applications, you'll likely butt up against a number of situations where existing software isn't optimized for what thinking machines can do. For instance, Web search. Nearly every agent-based AI application makes use of information on the Web to get things done. But Web Search APIs weren't written with agents in mind.
They provide a limited number of search results and a condensed snippet format that lines up more with how people use Web search interfaces. We get a page of ten blue links and scan them to decide which one to click. But AI agents aren't people. Not only can they make sense of many more search results at once, but their performance usually improves with larger document summaries and contents. People on the other hand, are unlikely to read through all search results before making a decision. So search APIs could certainly be rethought for agents.
Similarly, when agents are developing applications or collecting data, they can make use of databases. But once again databases were designed and built for people to use not AI agents. And once again they can be rethought for agents, which is what we did with our most recent launch: AgentDB.
Agents can (and do) produce 1000x more databases than people every day, so the process of spinning up and managing any database for an agent needs to be as easy and maintenance-free as possible. Most of the databases AI agents create will be short-lived after serving their initial purpose. But some databases will be used again and others still will be used regularly.
With this kind of volume costs can become an issue, so keeping that many databases available needs to be as cost effective as possible. Last but not least, the content of databases needs to work well as context for AI models so agents can use this data as part of their tasks.
AgentDB is a database system designed around these considerations. With AgentDB, creating a database only requires a Universally Unique Identifier (UUID). There's no setup or configuration step. So whenever an AI agent decides it needs a database, it has one simply by creating a UUID. No forms or set-up wizards involved.
Databases in AgentDB are stored as files not hosted services requiring compute and maintenance. If an AI agent needs to query a database or append to it, it can. But if it never needs to access it again, the database is just a file. That means you're only paying for the cost of storage to keep it around and because AgentDB databases are just files, they scale. Meaning they can easily keep up with the scale of AI agents.
To make data within each AgentDB database easily accessible as context for AI models, every AgentDB account is also an MCP server. This makes the data portable across AI applications as long as they support MCP server connections (which most do).
Altogether this example illustrates how even the most fundamental software infrastructure systems, like databases, can be rethought for the age of AI. The AgentDB database system doesn't look like a hosted database as a service solution because it's not designed and built for database admins and back-end developers. It's built for today's thinking machines.
And as agents take on more computing tasks for people, it won't be the only software made with agents as first class users.
Getting Creative With Versal Letters
A while back, our man Geoff Graham treated us to a refresher on the CSS initial-letter property, but how can you style drop and initial caps to reflect a brand’s visual identity and help to tell its stories?
Here’s how I do it in CSS by combining ::first-letter and initial-letter with other unexpected properties, including border-image, and clip-path.
Patty Meltt is an up-and-coming country music sensation.My brief: Patty Meltt is an up-and-coming country music sensation, and she needed a website to launch her new album. She wanted it to be distinctive-looking and memorable, so she called Stuff & Nonsense. Patty’s not real, but the challenges of designing and developing sites like hers are.
First, a drop cap recap. Chris Coyier wrote about drop caps several years ago. They are a decorative letter at the beginning of a paragraph, often spanning several lines of text. It’s a typographic flourish found in illuminated manuscripts and traditional book design, where it adds visual interest and helps guide a reader’s eye to where they should begin.
Study manuscripts from the Middle Ages onwards, and you’ll find hand-decorated illuminated capitals. The artists who made these initial letters were fabulously called “illuminators.” These medieval versals went beyond showing someone where to start reading; historiated letters also illustrated the stories, which was especially useful since most people in the Middle Ages couldn’t read.
A basic drop capOn the web, drop caps can improve readability and reflect a brand’s visual identity.
A brief refresher on properties and valuesIn CSS, drop caps are created using the ::first-letter pseudo-element in combination with initial-letter. As you might expect, ::first-letter targets the very first letter of a block of text, enabling you to style it independently from the rest of a paragraph. The first number sets how many lines tall the letter appears, and the second controls its baseline alignment — that is, which line of text the bottom of the cap sits on.
p::first-letter { -webkit-initial-letter: 3 3; initial-letter: 3 3; }Because browser support still varies, it’s common to include both the unprefixed and -webkit- prefixed properties for maximum compatibility. And speaking of browser support, it’s also sensible to wrap the initial-letter property inside an @supports CSS at-rule so we can check for browser support and provide a fallback, if needed:
@supports (initial-letter:2) or (-webkit-initial-letter:2) { p::first-letter { -webkit-initial-letter: 3 3; initial-letter: 3 3; } }The initial-letter property automatically calculates the font size to match the number of lines a drop cap spans. On its own, this can make for quite a first impression. However, drop caps really start to come to life when you combine initial-letter with other CSS properties.
Tip: Interactive examples from this article are available in my lab.
Shadows Text shadows applied to first letters (live demo)When I want to lift a drop cap off the page, I can add a single text-shadow. Shadows can be colourful and don’t have to be black. I created a full live demo you can check out.
p::first-letter { /* ... *// text-shadow: 6px 6px 0 #e6d5b3; }But why use just one shadow when two hard-edged shadows will turn a cap into a classic graphic typographic element?
p::first-letter { /* ... */ text-shadow: -6px -6px 0 #7d6975, 6px 6px 0 #e6d5b3; } Examples showing unstyled, single text shadow, and two text shadows (live demo) Strokes A text shadow applied to a first letter (live demo)The text-stroke property — shorthand for text-stroke-width and text-stroke-color — adds an outline to the centre of the text shape. It’s a Baseline feature and is now widely available. I can make the cap text transparent or colour it to match the page background.
p::first-letter { /* ... */ text-stroke: 5px #e6d5b3; } Backgrounds Solid and gradient backgrounds applied to first letters (live demo)Adding a background is a simple way to start making a cap more decorative. I could start by adding a solid background-color.
p::first-letter { /* ... */ background-color: #97838f; }To add a lighting effect, I could apply a conical, linear, or radial gradient background image (here’s a demo):
p::first-letter { /* ... */ background-color: #e6d5b3; background-image: linear-gradient(135deg,#c8b9c2 0%, #7d6975 50%); }And even an image URL to use a bitmap or vector image as a background (and here’s that demo):
p::first-letter { /* ... */ background-color: #e6d5b3; background-image: url(...); background-size: cover; } Background images and a background clipped to textThings become even more interesting by clipping a bitmap, gradient, or vector background image to the text while setting its colour to transparent. Now, the image will only appear inside the text space (demo).
p::first-letter { /* ... */ background-clip: text; color: transparent; } Borders Two examples of borders applied to first letters, one square and one roundedYou might think borders are boring, but there’s plenty you can do to make them look interesting. I could start by applying a solid border to surround the cap box (demo).
p::first-letter { /* ... */ border: 5px solid #e6d5b3; }Then, I could apply border-radius to slightly round all its corners (demo).
p::first-letter { /* ... */ border-radius: 1rem; }Or, I might round individual corners for a more interesting look (demo):
p::first-letter { /* ... */ border-top-left-radius: 3rem; border-bottom-right-radius: 3rem; } A border radius applied to the first letter, where the top-left and bottom-right edges are rounded (live demo)And then there’s the border-image property, a powerful, yet often overlooked CSS tool. By slicing, repeating, and outsetting images, you can create intricate borders and decorative drop caps with minimal code.
A CSS border image applied to a first letter (live demo)You can insert a bitmap or vector format image, or drop a CSS gradient into the border space:
p::first-letter { /* ... */ border-style: solid; border-width: 10px; border-image: conic-gradient(...) 1; } Clipping Clipping first lettersThe clip-path property lets you define a custom shape that controls which parts of an element are visible and which are hidden. Instead of always showing a rectangular box, you can use clip-path to crop elements into circles, polygons, or even complex shapes defined with SVG paths. It’s an effective way to create visual effects like this right-facing arrow. Clipping the drop cap into an arrow shape isn’t just decorative — it reinforces direction and hierarchy, literally pointing readers to where the story begins. Here’s a demo of the following example.
p::first-letter { /* ... */ padding-inline: 1rem 2rem; background-color: #e6d5b3; clip-path: polygon(...); }Or a glossy sticker shape cap, made by combining clip-path with a gradient background image and a text shadow (demo).
Transforms Two examples of transforming first letters, one rotated (demo) and one scaled (demo)You can transform a drop cap independently from the rest of a paragraph by rotating, scaling, skewing, or translating it to make it feel more dynamic:
p::first-letter { /* ... */ margin-inline-end: 2.5em; transform: skew(20deg, 0deg); }And with a little trial and error to arrive at the correct values, you could even flow the remaining paragraph text around the cap using the shape-outside property (demo):
p::first-letter { /* ... */ display: block; float: left; shape-outside: polygon(0 0, 0 200px, 250px 600px); shape-margin: 50px; transform: skew(20deg, 0deg) translateX(-60px); }Drop caps don’t just help guide a reader’s eye to where they should begin; they also set the tone for what follows. A well-designed drop cap adds visual interest at the start of a block of text, drawing attention in a way that feels intentional and designed. Because it’s often the first element the reader sees, caps can carry a lot of visual weight, making them powerful tools for expressing a brand’s identity.
Designing for Patty MelttPatty Meltt wanted a website packed with design details. Every element added to a design is an opportunity to be expressive, and that includes her drop caps.
Her biography page is presentable, but we felt a focus on where someone should start reading was lacking.
Patty Meltt’s biography without a drop capFrom the selection of designs I showed her, she felt the sticker-style cap best suited her brand.
To implement it, first, I added a cursive typeface which matches her branding and contrasts with the rest of her typographic design:
p::first-letter { font-family: "Lobster Two", sans-serif; font-weight: 700; }I changed the cap colour to match the page background and added a semi-transparent text shadow:
p::first-letter { /* ... */ color: #140F0A; text-shadow: 6px 6px 0 rgba(163,148, 117, .8); }Next, I clipped the cap box to a visible area shaped like a sticker:
p::first-letter { /* ... */ clip-path: polygon(...); }…before applying two background images — a noise-filled SVG and a radial gradient — that I blended using a background-blend-mode:
p::first-letter { /* ... */ background-image: url(img/cap-noise.svg), radial-gradient(circle, #e6d5b3 0%, #cdaa65 100%); background-blend-mode: soft-light, normal; } Patty Meltt’s biography with a stylsh new drop cap (demo)The result is a drop cap that’s as stylish as cut-off jeans and a pair of gator-skinned boots.
ConclusionStyling drop caps isn’t just about decoration — it’s about setting a tone, drawing readers in, and using every detail to express a brand’s voice. CSS has the tools to go beyond the default: gradients, textures, borders, and even complex shapes all help transform first letters into statements. So don’t waste the opportunities that drop caps give you. Make ’em sing.
Getting Creative With Versal Letters originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Getting Clarity on Apple’s Liquid Glass
Folks have a lot to say about “liquid glass,” the design aesthetic that Apple introduced at WWDC 2025. Some love it, some hate it, and others jumped straight into seeing how to they could create it in CSS.
There’s a lot to love, hate, and experience with liquid glass. You can love the way content reflects against backgrounds. You can hate the poor contrast between foreground and background. And you can be eager to work with it. All of those can be true at the same time.
Image credit: AppleI, for one, am generally neutral with things like this for that exact reason. I’m intrigued by liquid glass, but hold some concern about legibility, particularly as someone who already struggles with the legibility of Apple’s existing design system (notably in Control Center). And I love looking at the many and clever ways that devs have tried to replicate liquid glass in their own experiments.
So, I’m in the process of gathering notes on the topic as I wrap my head around this “new” (or not-so-new, depending on who’s talking) thing and figure out where it fits in my own work. These links are a choice selection of posts that I’ve found helpful and definitely not meant to be an exhaustive list of what’s out there.
WWDC IntroductionAlways a good idea to start with information straight from the horse’s mouth.
In short:
- It’s the first design system that is universally applied to all of Apple’s platforms, as opposed to a single platform like Apple’s last major overhaul, iOS 7.
- It’s designed to refract light and dynamically react to user interactions.
- By “dynamic” we’re referring to UI elements updating into others as the context changes, such as displaying additional controls. This sounds a lot like the Dynamic Island, supporting shape-shifting animations.
- There’s a focus on freeing up space by removing hard rectangular edges, allowing UI elements to become part of the content and respond to context.
Apple also released a more in-depth video aimed at introducing liquid glass to designers and developers.
In short:
- Liquid glass is an evolution of the “aqua” blue interface from macOS 10, the real-time introduced in iOS 7, the “fluidity” of iOS 10, the flexibility of the Dynamic Island, and the immersive interface of visionOS.
- It’s a “digital meta-material” that dynamically bends and shapes light while moving fluidly like water.
- It’s at least partially a response to hardware devices adopting deeper rounded corners.
- Lensing: Background elements are bended and warped rather than scattering light as it’s been in previous designs. There’s gel-like feel to elements.
- Translucence helps reveal what is underneath a control, such as a progress indicator you can scrub more precisely by seeing what is behind the surface.
- Controls are persistent between views for establishing a relationship between controls and states. This reminds me of the View Transition API.
- Elements automatically adapt to light and dark modes.
- Liquid glass is composed of layers: highlight (light casting and movement), shadow (added depth for separation between foreground and background), and illumination (the flexible properties of the material).
- It is not meant to be used everywhere but is most effective for the navigation layer. And avoid using glass on glass.
- There are two variants: regular (most versatile) and clear (does not have adaptive behaviors for allowing content to be more visible below the surface).
- Glass can be tinted different colors.
Right on cue, Apple has already made a number of developer resources available for using and implementing liquid glass that are handy references.
- Introduction to Liquid Glass
- Adopting Liquid Glass
- Landmarks: Building an app with Liquid Glass
- Applying Liquid Glass to custom views
This Wired piece is a nice general overview of what liquid glass is and context about how it was introduced at WWDC 2025. I like getting a take on this from a general tech perspective as opposed to, say, someone’s quick hot take. It’s a helpful pulse on what’s happening from a high level without a bunch of hyperbole, setting the stage for digging deeper into things.
In short:
- Apple is calling this “Liquid Glass.”
- It’s Apple’s first significant UI overhaul in 10 years.
- It will be implemented across all of Apple’s platforms, including iOS, macOS, iPadOS, and even the Vision Pro headset from which it was inspired.
- “From a technical perspective, it’s a very impressive effect. I applaud the time and effort it must have taken to mimic refraction and dispersion of light to such a high degree.”
- “Similar to the first beta for iOS 7, what we’ve seen so far is rough on the edges and potentially veers into distracting or challenging to read, especially for users with visual impairments.”
Let’s get right to the heart of where the pushback against liquid glass is coming from. While the aesthetic, purpose, and principles of liquid glass are broadly applauded, many are concerned about the legibility of content against a glass surface.
Traditionally, we fill backgrounds with solid or opaque solid color to establish contrast between the foreground and background, but with refracted light, color plays less a role and it’s possible that highlighting or dimming a light source will not produce enough contrast, particularly for those with low-vision. WCAG 2.2 emphasizes color and font size for improving contrast and does provide guidance for something that’s amorphous like liquid glass where bending the content below it is what establishes contrast.
“Apple’s “Liquid Glass” and What It Means for Accessibility”:
- “When you have translucent elements letting background colors bleed through, you’re creating variable contrast ratios that might work well over one background, but fail over a bright photo of the sunset.”
- “Apple turned the iPhone’s notch into the Dynamic Island, Android phones that don’t have notches started making fake notches, just so they could have a Dynamic Island too. That’s influence. But here they are making what looks like a purely aesthetic decision without addressing the accessibility implications.”
- “People with dyslexia, who already struggle with busy backgrounds and low-contrast text, now deal with an interface where visual noise is baked into the design language. People with attention disorders may have their focus messed up when they see multiple translucent layers creating a whole lot of visual noise.”
- “It’s like having a grand entrance and a side door marked ‘accessible.’ Technically compliant. But missing the point.”
- “The legal landscape adds another layer. There’s thousands of digital accessibility lawsuits filed in the U.S. yearly for violating the ADA, or the American Disabilities Act. Companies are paying millions in settlements. But this is Apple. They have millions. Plus all the resources in the world to save them from legal risks. But their influence means they’re setting precedents.”
“Liquid Glass: Apple vs accessibility”:
- “Yet even in Apple’s press release, linked earlier, there are multiple screenshots where key interface components are, at best, very difficult to read. That is the new foundational point for Apple design. And those screenshots will have been designed to show the best of things.”
- “Apple is still very often reactive rather than proactive regarding vision accessibility. Even today, there are major problems with the previous versions of its operating systems (one example being the vestibular trigger if you tap-hold the Focus button in Control Centre). One year on, they aren’t fixed.”
- “State, correctly, that Apple is a leader in accessibility. But stop assuming that just because this new design might be OK for you and because Apple has controls in place that might help people avoid the worst effects of design changes, everything is just peachy. Because it isn’t.”
“Liquid Glass” by Hardik Pandya
- “The effect is technically impressive, but it introduces a layer of visual processing between you and your memories. What was once immediate now feels mediated. What was once direct now feels filtered.”
- “While Apple’s rationale for Liquid Glass centers on ‘seeing’ content through a refractive surface, user interface controls are not meant to be seen—they are meant to be operated. When you tap a button, slide a slider, or toggle a switch, you are not observing these elements. You are manipulating them directly.”
- “Buttons become amorphous shapes. Sliders lose their mechanical clarity. Toggle switches abandon their physical affordances. They appear as abstract forms floating behind glass—beautiful perhaps, but disconnected from the fundamental purpose of interface controls: to invite and respond to direct manipulation.”
- “The most forward-thinking interface design today focuses on invisibility – making the interaction so seamless that the interface itself disappears. Liquid Glass makes the interface more visible, more present, and more demanding of attention.”
“Liquid glass, now with frosted tips”:
- It’s easy to dump on liquid glass in its introductory form, but it’s worth remembering that it’s in beta and that Apple is actively developing it ahead of its formal release.
- A lot has changed between the Beta 2 and Beta 3 releases. The opacity between glass and content has been bumped up in several key areas.
It’s fun to see the difference approaches many folks have used to re-create the liquid glass effect in these early days. It amazes me that there is already a deluge of tutorials, generators, and even UI frameworks when we’re only a month past the WWDC 2025 introduction.
- Create this trendy blurry glass effect with CSS (Kevin Powell)
- Liquid Glass design using CSS (Nordcraft)
- Adopting Apple’s Liquid Glass: Examples and best practices (LogRocket)
- Liquid Glass Figma File
- CSS Liquid Glass Effects (DesignFast)
- Liquid Glass UI Framework
- Liquid Glass CSS Generator
Let’s drop in a few interesting demos that folks have created. To be clear, glass-based interfaces are not new and have been plenty explored, which you can find over at CodePen in abundance. These are recent experiments. The most common approaches appear to reach for SVG filters and background blurs, though there are many programmatic demos as well.
Using a CSS-only approach with an SVG filter with backdrop-filter with a series of nested containers that sorta mimics how Apple describes glass as being composed of three layers (highlight, shadow and illumination):
CodePen Embed FallbackSame sort of deal here, but in the context of a theme toggle switch that demonstrates how glass can be tinted:
CodePen Embed FallbackComparing a straight-up CSS blur with an SVG backdrop:
CodePen Embed FallbackContextual example of a slider component:
CodePen Embed FallbackUsing WebGL:
CodePen Embed Fallback Assorted links and coverageA few more links from this browser tab group I have open:
- “Apple’s Liquid Glass is exactly as ambitious as Apple” (Fast Company)
- “Apple unveils iOS 26 with Liquid Glass” (9to5Mac)
- “Apple Announces All-New ‘Liquid Glass’ Software Redesign Across iOS 26 and More” (MacRumors)
- “Apple just added more frost to its Liquid Glass design” (The Verge)
- “Apple tones down Liquid Glass effect in iOS 26 beta 3” (The Apple Post)
- “More assorted notes on Liquid Glass” (Riccardo Mori)
- A bunch of CodePen Collections
Getting Clarity on Apple’s Liquid Glass originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
What I Took From the State of Dev 2025 Survey
State of Devs 2025 survey results are out! While the survey isn’t directly related to the code part of what we do for work, I do love the focus Devographics took ever since its inception in 2020. And this year it brought us some rather interesting results through the attendance of 8,717 developers, lots of data, and even more useful insights that I think everyone can look up and learn from.
I decided to look at the survey results with an analytical mindset, but wound up pouring my heart out because, well, I am a developer, and the entire survey affects me in a way. I have some personal opinions, it turns out. So, sit back, relax, and indulge me for a bit as we look at a few choice pieces of the survey.
And it’s worth noting that this is only part one of the survey results. A second data dump will be published later and I’m interested to poke at those numbers, too.
An opportunity to connectOne thing I noticed from the Demographics section is how much tech connects us all. The majority of responses come from the U.S. (26%) but many other countries, including Italy, Germany, France, Estonia, Austria, South Africa and many more, account for the remaining 74%.
I mean, I am working and communicating with you right now, all the way from Nigeria! Isn’t that beautiful, to be able to communicate with people around the world through this wonderful place we call CSS-Tricks? And into the bigger community of developers that keeps it so fun?
I think this is a testament to how much we want to connect. More so, the State of Devs survey gives us an opportunity to express our pain points on issues surrounding our experiences, workplace environments, quality of health, and even what hobbies we have as developers. And while I say developers, the survey makes it clear it’s more than that. Behind anyone’s face is someone encountering life challenges. We’re all people and people are capable of pure emotion. We are all just human.
It’s also one of the reasons I decided to open a Bluesky account: to connect with more developers.
I think this survey offers insights into how much we care about ourselves in tech, and how eager we are to solve issues rarely talked about. And the fact that it’s global in nature illustrates how much in common we all have.
More women participated this yearFrom what I noticed, fewer women participated in the 2024 State of JavaScript and State of CSS fewer women (around 6%), while women represented a bigger share in this year’s State of Devs survey. I’d say 15% is still far too low to fairly “represent” an entire key segment of people, but it is certainly encouraging to see a greater slice in this particular survey. We need more women in this male-dominated industry.
Experience over talentContrary to popular opinion, personal performance does not usually equate to higher pay, and this is reflected in the results of this survey. It’s more like, the more experienced you are, the more you’re paid. But even that’s not the full story. If you’re new to the field, you still have to do some personal marketing, find and keep a mentor, and a whole bunch of stuff. Cassidy shares some nice insights on this in a video interview tracing her development career. You should check it out, especially if you’re just starting out.
Notice that the average income for those with 10-14 of experience ($115,833) is on par with those with between 15-29 years of experience ($118,000) and not far from those with 30+ years ($120,401). Experience appears to influence income, but perhaps not to the extent you would think, or else we’d see a wider gap between those with 15 years versus those with more than double the service time.
More than that, notice how income for the most experienced developers (30+ years) is larger on average but the range of how much they make is lower than than those with 10-29 years under their belts. I’m curious what causes that decline. Is it a lack of keeping up with what’s new? Is it ageism? I’m sure there are lots of explanations.
Salary, workplace, and job huntingI prefer not drill into each and every report. I’m interested in very specific areas that are covered in the survey. And what I take away from the survey is bound to be different than your takeaways, despite numbers being what they are. So, here are a few highlights of what stood out to me personally as I combed through the results.
Your experience, employment status, and company’s employer count seem to directly affect pay. For example, full-timers report higher salaries than freelancers. I suppose that makes sense, but I doubt it provides the full picture because freelancers freelance for a number of reasons, whether its flexible hours, having more choice to choose their projects, or having personal constraints that limit how much they can work. In some ways, freelancers are able to command higher pay while working less.
Bad management and burnout seem to be the most talked-about issues in the workplace. Be on guard during interviews, look up reviews about the company you’re about to work for, and make sure there are far fewer complaints than accolades. Make sure you’re not being too worked up during work hours; breaks are essential for a boost in productivity.
Seventy percent of folks reported no discrimination in the workplace, which means we’re perhaps doing something right. That said, it’s still disheartening that 30% experience some form of discrimination and lowering that figure is something we ought to aim for. I’m hoping companies — particularly the tech giants in our space — take note of this and enforce laws and policies surrounding this. Still, we can always call out discriminatory behavior and make corrections where necessary. And who’s to say that everyone who answered the survey felt safe sharing that sort of thing? Silence can be the enemy of progress.
Never get too comfortable in your job. Although 69% report having never been laid off, I still think that job security is brittle in this space. Always learn, build, and if possible, try to look for other sources of income. Layoffs are still happening, and looking at the news, it’s likely to continue for the foreseeable future, with the U.S., Australia, and U.K. being leading the way.
One number that jumped off the page for me is that it takes an average of four applications for most developers to find a new job. This bamboozles me. I’m looking for a full-time role (yes, I’m available!), and I regularly apply for more than four jobs in a given day. Perhaps I’m doing something wrong, but that’s also not consistent with those in my social and professional circles. I know and see plenty of people who are working hard to find work, and the number of jobs they apply for has to bring that number up. Four applications seems way low, though I don’t have the quantitative proof for it.
Your personal network is still the best way to find a job. We will always and forever be social animals, and I think that’s why most survey participants say that coworker relationships are the greatest perk of a job. I find this to be true with my work here at CSS-Tricks. I get to collaborate with other like-minded CSS and front-end enthusiasts far and wide. I’ve developed close relationships with the editors and other writers, and that’s something I value more than any other benefits I could get somewhere else.
Compensation is still a top workplace challenge. JavaScript is still the king of programming (bias alert), taking the top spot as the most popular programming language. I know you’re interested, that CSS came in at third.
To my surprise, Bluesky is more popular amongst developers than X. I didn’t realize how much toxicity I’ve been exposed to at X until I opened a Bluesky account. I hate saying that the “engagement” is better, or some buzz-worthy thing like that, but I do experience more actual discussions over at Bluesky than I have for a long time at X. And many of you report the same. I hasten to say that Bluesky is a direct replacement for what X (let’s face it, Twitter) used to be, but it seems we at least have a better alternative.
Health issuesWithout our health, we are nothing. Embrace your body for what it is: your temple. It’s a symbiotic relationship.
— Mrs. N.
I’m looking closer at the survey’s results on health because of the sheer number of responses that report health issues. I struggle with issues, like back pains, and that forced me to upgrade my work environment with a proper desk and chair. I tend to code on my bed, and well, it worked. But perhaps it wasn’t the best thing for my physical health.
I know we can fall into the stereotype of people who spend 8-12 hours staring at two big monitors, sitting in a plush gaming chair, while frantically typing away at a mechanical keyboard. You know, the Hackers stereotype. I know that isn’t an accurate portrayal of who we are, but it’s easy to become that because of how people look at and understand our work.
And if you feel a great deal of pressure to keep up with that image, I think it’s worth getting into a more healthy mindset, one that gets more than a few hours of sleep, prioritizes exercise, maintains a balanced diet, and all those things we know are ultimately good for us. Even though 20% of folks say they have no health issues at all, a whopping 80% struggle with health issues ranging from sleep deprivation to keeping a healthy weight. You are important and deserve to feel healthy.
Think about your health the way you think about the UI/UX of the websites you design and build. It makes up a part of the design, but has the crucial role of turning ordinary tasks into enjoyable experiences, which in turn, transforms into an overall beautiful experience for the user.
Your health is the same. Those small parts often overlooked can and will affect the great machine that is your body. Here’s a small list of life improvements you can make right now.
Closing thoughtsDiversity, representation, experience, income, and health. That’s what stood out to me in the 2025 State of Devs survey results. I see positive trends in the numbers, but also a huge amount of opportunity to be better, particularly when it comes being more inclusive of women, providing ample chances for upward mobility based on experience, and how we treat ourselves.
Please check out the results and see what stands out to you. What do you notice? Is there anything you are able to take away from the survey that you can use in your own work or life? I’d love to know!
What I Took From the State of Dev 2025 Survey originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Setting Line Length in CSS (and Fitting Text to a Container)
First, what is line length? Line length is the length of a container that holds a body of multi-line text. “Multi-line” is the key part here, because text becomes less readable if the beginning of a line of text is too far away from the end of the prior line of text. This causes users to reread lines by mistake, and generally get lost while reading.
Luckily, the Web Content Accessibility Guidelines (WCAG) gives us a pretty hard rule to follow: no more than 80 characters on a line (40 if the language is Chinese, Japanese, or Korean), which is super easy to implement using character (ch) units:
width: 80ch;The width of 1ch is equal to the width of the number 0 in your chosen font, so the exact width depends on the font.
Setting the optimal line lengthJust because you’re allowed up to 80 characters on a line, it doesn’t mean that you have to aim for that number. A study by the Baymard Institute revealed that a line length of 50-75 characters is the optimal length — this takes into consideration that smaller line lengths mean more lines and, therefore, more opportunities for users to make reading mistakes.
That being said, we also have responsive design to think about, so setting a minimum width (e.g., min-width: 50ch) isn’t a good idea because you’re unlikely to fit 50 characters on a line with, for example, a screen/window size that is 320 pixels wide. So, there’s a bit of nuance involved, and the best way to handle that is by combining the clamp() and min() functions:
- clamp(): Set a fluid value that’s relative to a container using percentage, viewport, or container query units, but with minimum and maximum constraints.
- min(): Set the smallest value from a list of comma-separated values.
Let’s start with min(). One of the arguments is 93.75vw. Assuming that the container extends across the whole viewport, this’d equal 300px when the viewport width is 320px (allowing for 20px of spacing to be distributed as you see fit) and 1350px when the viewport width is 1440px. However, for as long as the other argument (50ch) is the smallest of the two values, that’s the value that min() will resolve to.
min(93.75vw, 50ch);Next is clamp(), which accepts three arguments in the following order: the minimum, preferred, and maximum values. This is how we’ll set the line length.
For the minimum, you’d plug in your min() function, which sets the 50ch line length but only conditionally. For the maximum, I suggest 75ch, as mentioned before. The preferred value is totally up to you — this will be the width of your container when not hitting the minimum or maximum.
width: clamp(min(93.75vw, 50ch), 70vw, 75ch);In addition, you can use min(), max(), and calc() in any of those arguments to add further nuance.
If the container feels too narrow, then the font-size might be too large. If it feels too wide, then the font-size might be too small.
Fit text to container (with JavaScript)You know that design trend where text is made to fit the width of a container? Typically, to utilize as much of the available space as possible? You’ll often see it applied to headings on marketing pages and blog posts. Well, Chris wrote about it back in 2018, rounding up several ways to achieve the effect with JavaScript or jQuery, unfortunately with limitations. However, the ending reveals that you can just use SVG as long as you know the viewBox values, and I actually have a trick for getting them.
Although it still requires 3-5 lines of JavaScript, it’s the shortest method I’ve found. It also slides into HTML and CSS perfectly, particularly since the SVG inherits many CSS properties (including the color, thanks to fill: currentColor):
CodePen Embed Fallback <h1 class="container"> <svg> <text>Fit text to container</text> </svg> </h1> h1.container { /* Container size */ width: 100%; /* Type styles (<text> will inherit most of them) */ font: 900 1em system-ui; color: hsl(43 74% 3%); text { /* We have to use fill: instead of color: here But we can use currentColor to inherit the color */ fill: currentColor; } } /* Select all SVGs */ const svg = document.querySelectorAll("svg"); /* Loop all SVGs */ svg.forEach(element => { /* Get bounding box of <text> element */ const bbox = element.querySelector("text").getBBox(); /* Apply bounding box values to SVG element as viewBox */ element.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")); }); Fit text to container (pure CSS)If you’re hell-bent on a pure-CSS method, you are in luck. However, despite the insane things that we can do with CSS these days, Roman Komarov’s fit-to-width hack is a bit complicated (albeit rather impressive). Here’s the gist of it:
- The text is duplicated a couple of times (although hidden accessibly with aria-hidden and hidden literally with visibility: hidden) so that we can do math with the hidden ones, and then apply the result to the visible one.
- Using container queries/container query units, the math involves dividing the inline size of the text by the inline size of the container to get a scaling factor, which we then use on the visible text’s font-size to make it grow or shrink.
- To make the scaling factor unitless, we use the tan(atan2()) type-casting trick.
- Certain custom properties must be registered using the @property at-rule (otherwise they don’t work as intended).
- The final font-size value utilizes clamp() to set minimum and maximum font sizes, but these are optional.
To make fitting text to a container possible in just one line of CSS, a number of solutions have been discussed. The favored solution seems to be two new text-grow and text-shrink properties. Personally, I don’t think we need two different properties. In fact, I prefer the simpler alternative, font-size: fit-width, but since text-grow and text-shrink are already on the table (Chrome intends to prototype and you can track it), let’s take a look at how they could work.
The first thing that you need to know is that, as proposed, the text-grow and text-shrink properties can apply to multiple lines of wrapped text within a container, and that’s huge because we can’t do that with my JavaScript technique or Roman’s CSS technique (where each line needs to have its own container).
Both have the same syntax, and you’ll need to use both if you want to allow both growing and shrinking:
text-grow: <fit-target> <fit-method>? <length>?; text-shrink: <fit-target> <fit-method>? <length>?;- <fit-target>
- per-line: For text-grow, lines of text shorter than the container will grow to fit it. For text-shrink, lines of text longer than the container will shrink to fit it.
- consistent: For text-grow, the shortest line will grow to fit the container while all other lines grow by the same scaling factor. For text-shrink, the longest line will shrink to fit the container while all other lines shrink by the same scaling factor.
- <fit-method> (optional)
- scale: Scale the glyphs instead of changing the font-size.
- scale-inline: Scale the glyphs instead of changing the font-size, but only horizontally.
- font-size: Grow or shrink the font size accordingly. (I don’t know what the default value would be, but I imagine this would be it.)
- letter-spacing: The letter spacing will grow/shrink instead of the font-size.
- <length> (optional): The maximum font size for text-grow or minimum font size for text-shrink.
Again, I think I prefer the font-size: fit-width approach as this would grow and shrink all lines to fit the container in just one line of CSS. The above proposal does way more than I want it to, and there are already a number of roadblocks to overcome (many of which are accessibility-related). That’s just me, though, and I’d be curious to know your thoughts in the comments.
ConclusionIt’s easier to set line length with CSS now than it was a few years ago. Now we have character units, clamp() and min() (and max() and calc() if you wanted to throw those in too), and wacky things that we can do with SVGs and CSS to fit text to a container. It does look like text-grow and text-shrink (or an equivalent solution) are what we truly need though, at least in some scenarios.
Until we get there, this is a good time to weigh-in, which you can do by adding your feedback, tests, and use-cases to the GitHub issue.
Setting Line Length in CSS (and Fitting Text to a Container) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.