A presentation at CSS Day in June 2025 in Amsterdam, Netherlands by Ana Rodrigues
When I’m not poking on my personal website, I’m probably doing some DIY. As we recently bought our first family home, an old Victorian home, and I’ve been the project manager, interior designer and handywoman in it! And guess what? I’m going to take you on that journey too. And as it got renovated, or shall I say: refactored, I’ve found similar struggles.
Let’s talk about refactoring CSS And redecorating a new home!! Today we’re going to talk about my recent experiences in refactoring and maintaining CSS. This talk came to my head after returning to work following a year of maternity leave two years ago. That year away coincided with an explosion of new CSS features I’m still catching up on even now.
I was always wondering ”What was the latest approach? How do you build a hero nowadays? What about a card? What had the experts written while I was away?” Experts that are here in this room right now - I created this overwhelming pressure that I needed to prove that I “still had it” and that becoming a mother hadn’t somehow diminished my technical skills.
So my return to work became unexpectedly miserable. I was terrified of making mistakes or choosing suboptimal solutions. The blank canvas that once excited me now left me anxious and riddled with decision paralysis. I’m talking about both CSS and new home!
I felt the same when picking colours for my new home. I took weeks to decide because I was so afraid of making the wrong decision. Even though my family reassured me that if I picked the wrong colour, I could just paint over it, I couldn’t see it that way. In my mind, getting it wrong meant wasting time and money.
When assigned maintenance tasks or small feature additions to existing projects, I felt a wave of relief. The scaffolding was already established and the major decisions had been made. I could make incremental improvements based on new knowledge I’d acquired. These constraints weren’t limiting. They were liberating.
I felt the same when looking at my home. The empty living room with striped carpets, exposed floorboards and bare walls terrified me. I worried about choosing the wrong colour, wrong flooring, wrong placement of electrical outlets and lighting. It was a mess.
But in the kitchen, which I had accepted I couldn’t fully refactor because of time and money, I looked for quick improvements. For example, I replaced all the cabinet handles to match the existing countertop. They were a bit used, dirty, and grimy, and simply replacing them made everything feel brand new. A small improvement that made a big difference.
When I made a change, I could see the before-and-after, giving me confidence that my work was valuable. Similarly, when tackling legacy code, I had baseline for comparison, a tangible measure of improvement. When I made a change, I could see the before-and-after, giving me confidence that my work was valuable. There was something deeply satisfying about this incremental progress that building from scratch couldn’t provide. Perhaps it’s psychological, but this revelation shifted my perspective completely. What once prompted sighs of “what a pain to work on this old codebase” now gets me excited! Because I know I can make it better, and I can clearly see the value I’m adding with each change
Because we’re not always building something brand new from scratch. Whether it’s code or a new home. I work in an agency and many of you might be freelancers, independent developers or at other agencies who understand that we’re not always building something brand new from scratch. Even in product teams, you may work on internal tools built over many years. You’re not always jumping from project to project building something new with with the latest technology, giving in to “CV-driven development”. It’s also very unlikely you’re building a new home from scratch.
This talk is not about dissing legacy codebases. And I could wrap quickly if I only said: I want to make it clear that this talk is not about dismissing legacy codebases and that you should immediately be keen to refactor once you inherit one. And, I could wrap up quickly if I only said, “Well, just implement a design system and carry on.” But it’s not really about that either. It’s particularly not about ignoring budget constraints of smaller organisations as it’s very easy to say “we should rebuild this from scratch,” as that’s not their budget reality, especially in today’s economy.
CSS just works. And when it doesn’t, it is fantastic at failing. The reality of CSS maintenance is that it often just works without requiring a lot of attention. CSS has evolved significantly in recent years. We’ve spent the last two days being amused by the most recent features and proposals!
But the website built 8 or 9 years ago won’t be using these. And it still works.
Legacy CSS can reveal undocumented product decisions. When you inherit projects built by someone else (whether you’re at an agency, a freelancer, or just joining a team) you have to do a lot of figuring out. We all want to live in a perfect world where everything is documented and there’s a design system and a dedicated team for said design system. But the reality is often different.
And the code that is live, is your documentation. More often than not, the code that is live is your documentation. It may have special considerations for user needs. For example, specific features or specific accessibility concerns which means that the product fundamentally has a need to work for a group of people (which should be the default regardless) and that probably isn’t explicitly written anywhere.
An example that we quite often forget about is print stylesheets. At Hactar we work with a lot of charities that provide support for a variety of illnesses. Imagine a website that informs people about a certain illness and provides recipes for meals that help manage such condition. People will often want to print those. Or print helpful notes and questions to take to a doctor’s appointment like questionnaire results for being at risk of a certain illness.
In projects with high team turnover, the moment the someone leaves and another joins, they’ll have a brand new vision that can clash with what currently exists. Very small organisations may not have sophisticated documentation systems because they’re typically overstretched, overworked and have no money.
I took this photo in our loft when we removed the old carpet. The builders left some documentation behind! “Danger!” Definitely danger. Maybe pipes?. The code is often the only place where developers documented technical constraints they faced. It’s not unusual to find comments saying “sorry” or explaining unexpected bugs, browser quirks and their fixes. I remember looking at one project that had a comment apologising and saying “this was the solution due to lack of time”. We’ve all been there. We all want to handover the best code in the world, but we’ve all been in situations where time constraints and simply being human have made us come up with solutions that worked in the circumstances we had at the time.
So what motivates CSS refactoring?
When I think about a CSS refactor, I think about some projects that we inherited where adding new features became very painful and time consuming. So I’m mostly going to focus on this one.
And if you think you’ve ever sweep under the rug some questionable code… well whoever lived in my house before me put a whole fusebox underneath the stairs. “Wow!!! Oh my god! Why did they do this???” Are the words you will likely hear when approaching a legacy issue… in my house. Happened 90% of the time a contractor came over to give me a quote. They look around and assess the situation and I know I’m about to have a bad time when I heard those words.
When I inherit a codebase built by someone else, I go through a general high level audit to see what I’m dealing with. In the past, I’ve definitely said something similar about code. Thankfully, I’ve grown out of that attitude.
Styles cascade and interact in complex ways that aren’t immediately obvious. The visual nature of CSS means that problems are very likely context-dependent. What you perceive as a flaw might just be your opinion. Remember: you’re not fixing something broken. It’s working already. And that’s what makes issues difficult to identify.
More often than not, when I approach a CSS audit, I have to forget about the shiny thing that everybody talks about - the design system.
And if it exists, the majority of the times it hasn’t been updated since the original design sign-off and build. The good news is that nowadays it’s common to inherit a codebase that doesn’t stray too much from what we’re used to seeing. Even for small organisations with large websites, you’ll almost always find a main file that imports a bunch of components, base variables, utilities, and things like that. The structure doesn’t normally stray too much from this pattern, which is why after a while, you’re just looking at code that seems sensible and it becomes hard to spot issues.
When you have zero documentation or style guides, you may need to start by figuring out how many different components exist and what features make up the website. If you have files like hero.css, cards.css, footer.css, navigation.css, banner.css, newsletter.css, you get a sense of how many components you might be dealing with.
Nowadays, I can spot a few glaring issues a bit more quickly. Like multiple selectors to fix specificity issues. In this case, this codebase was constantly overwriting styles set at a very high level for a heading 2.
I was recently looking at a codebase with lots of support for CSS grid, which immediately gave me a timeline of when the code might have been built (we had no git history - by the way, this can happen! You can be a sent a whole codebase without git history). Grid has been baseline for a long while and this means there’s a lot to clean up and if we don’t we must pay special attention to where we’re adding new code. I still find IE11 targets too. I know! With that particular example being in sass, by the way. This tells me that conversations need to be had about browser support and testing.
If you keep finding that inside many components there’s a margin or padding being reset, it may mean that something on a higher level has gone wrong or is no longer suitable, and is now constantly being reset instead of tackling the issue at the source.
One time I had to find a class called “accordion__title” - and was confused when the search returned nothing. So what was done here, using sass, was to store the current parent selector, accordion, in a variable called root. Later on, the root variable can be used when nested and avoids hardcoding class names. Now, I’m sure this technique is helpful in some specific scenarios - but I personally prioritise readability and easier developer experience than this type of optimisation. So what I learned here was that, on this project, finding css by searching by its class name was always going to be an uncertain task.
In the UK, when you buy or sell a house, a surveyor is normally involved and they love this little device. It’s a called a moisture reader and one of the things I hate the most. They audit your walls to check for damp. But beware! The same wall will produce different values wether it is tested during summer or during winter! Sure, if you do have a case of water damage in a wall, it will tell you. But it may also shout if your wall is fine. During your own CSS audits, you might think about using automated tools for part of your audit and data gathering. The topic of CSS refactor isn’t new and throughout the years we’ve had tools come and go. Today I will share some that are active and maintained and that I personally find helpful.
ProjectWallace is a free tool that can be used in your browser or on your CLI and it does what it says in the package: it tells you what it thinks about the complexity, specificity, performance etc. of your css.
CSS Stats is also a fun one as it includes a “spacing resets” check which, as mentioned, is something I personally look at and having a number gives a high level overview of how big of an issue it is.
In Chrome dev tools there’s a simpler overview of your CSS. I’ve had some shaky experiments with it but I like that since it is integrated in the browser, within one click I can check which elements are using the things mentioned. It includes things like colours, fonts, unused declarations and media queries.
Also in Chrome, you can have a quick look at the Coverage tool that returns and overview of unused javascript and css on a loaded page. I agree with Chris on this - the idea of finding unused CSS is a whole chore. But looks like you could even go a bit further and bulk find unused CSS/JS with Puppeteer. When trying this out with my blog, it tells me that 64.8% of my CSS isn’t used on the homepage -which yeah. The code above relates to my guestbook which isn’t available on the homepage… But my personal website is small enough that I might be able to do something about it… on my spare time.
Recommended read: https://developer.chrome.com/docs/devtools/coverage/
In Firefox, I like this view in the Style Editor where it lists all the At-Rules found in the CSS loaded. By looking at my own website, I immediately spotted two that could use some review and update.
When I use the same on a real website, it tells a more complex story. This may be necessary complexity or a sign that the designs were strict and fluid layouts were not applied here. Definitely not smart layouts like we learned from Ahmad’s amazing talk.
And different brains process information differently. The same website, but using different tools. Chrome compiles the same media queries together while Project Wallace gives you options to sort the view by source order, sort by count or alphabetical order.
When you run a CSS automated audit the high numbers in reports can seem alarming, but they might be completely appropriate for your project. If an audit tool flags that you have 100 different colours in your CSS, without understanding the context or purpose of those colours, it doesn’t give me much useful information. You might be dealing with a website (like an online shop) that needs to catalog many colours. It may also bring up colours used in svgs which again, are unlikely necessary to know about.
These raw statistics don’t capture the nuance of intentional design decisions. Even assuming that specificity data is useful, these tools cannot distinguish between necessary complexity and technical debt. Sometimes you need complexity to override third-party libraries that you have no control over like cookie banners or chat bots.
After you feel like you have a sense of the codebase you’re dealing with and have high-level notes of technical bits that need attention, let’s consider practical next steps.
Ask questions about browser support, roadmap, known problems and document them.
Create your baseline wish list https://github.com/web-platform-dx/baseline-status https://chrome.dev/google-analytics-baseline-checker https://web.dev/articles/how-to-choose-your-baseline-target
Throughout these days, many exciting things have been shared but it can be easy to forget about them. If something got you excited and it would be perfect for existing or future projects, make it visible to everyone. Spin your own baseline status and add the things that you can’t wait for them to be baseline and make it part of your toolkit. Google recently announced a handy tool called baseline checker which should help you be even more specific to your projects by allowing you to upload your analytics. This is great for me because the websites and demographics I normally work with, may use older devices.
Once I realised the quote to do up all the rooms was more than I could a afford, I was forced to scale back. I decided not to work on the kitchen and bathrooms. They were clean and functional. It could wait. And my contractor told me something interesting: “you should wait anyway. Live in it and find the pain points. Then you’ll know what you want, what isn’t working and what works for your family and your routine.”
Low hanging fruit and glaring issues are great motivators to start conversations and feel motivated to improve the code. But like we know, CSS just works and it will take some time to fully grasp how much impact the legacy codebase is having on the developer experience until you actually work on it. It may take a while. Which is ideal if you’re not a freelancer or consultant brought in to work within a timeframe. A little bit like a therapy session, ask yourself a few questions and document them.
So I thought I’d share some real life examples of pain points I’ve recently experienced.
We need to discuss how to talk to our product people and they want to know why they should give you the time and money to make these changes. And it can’t just be for your own vanity. Now, I’m going to use product owner or product people but this means whoever calls the shots on what is being worked on next - whatever their role name is called.
In other words:
So you need to make a business case. Product people are tasked with delivering experiences that make money. Or at minimum, don’t lose money. We need to connect refactoring to specific business problems being solved. We must demonstrate how technical debt creates real business impact and document the before-and-after developer experience.
Not just metrics like reduced file size or faster loading times. Those improvements are great, but if your website is primarily visited by people on fast desktop connections, they won’t make a significant business impact. You need to highlight specific pain points that genuinely need addressing.
If I tell a product person, “If I do a CSS refactoring, I’m going to prevent all future issues”—can I really make that promise? How can you measure that? The same challenge applies to improved maintainability. When you enhance the codebase for developer experience, the value takes time to materialise. It’s rarely immediate because subsequent tasks might have been quick regardless of whether the code remained the same. Organisational and structural benefits take time to appear, and knowledge transfer and documentation value is quite difficult to quantify. You might create documentation primarily for yourself, making it a significant time and money investment that may not yield obvious rewards.
How does this mitigate risk? If we don’t do this, this very annoying thing can happen and it will delay many other things. fi But what they also want to know is how does this mitigate risk. Risk can sound like: “If we don’t do this, this very annoying thing can happen and it will delay many other things.”. This is a rare occurrence with CSS but if you have specific development pain points this is the time to explain them.
Let’s imagine a time in the future where Carousels with CSS are baseline and it’s a fully accessible feature. Then you’ve got a website that every once in a while gets new features added and has maintenance budget. That website actually has some carousels all over it. The content editors can add one to every page if they wish to do so via the CMS. But that carousel was built many years ago with the help of a third party library. And you love the craft. You thrive on doing the best you can and you find yourself itching to go back and improve things as you learn more. But it’s not your personal website. And we all got bills to pay. If you work for an agency, they 100% do not want you to work for free. If you work in a product team, trust me, your product person has a lot of other things in their priority list that they would rather you work on! fi How could this conversation go?
And you love the craft. You thrive on doing the best you can and you find yourself itching to go back and improve things as you learn more. But it’s not your personal website. And we all got bills to pay. If you work for an agency, they 100% do not want you to work for free. If you work in a product team, trust me, your product person has a lot of other things in their priority list that they would rather you work on! How could this conversation go?
But this is the opportunity to mitigate the risk of third party fallout. Context dependent of course but ask - what happens if this third party stops being maintained? What if it is bought? What if I suddenly changes pricing plan?
Isn’t going to cut it.
If they entertain the idea, they may ask a few more things. For example: these are examples of questions to be prepared to answer if you want to make a case with product regardless of the feature you’re keen to refactor.
Sometimes a good selling point can be: If we do this we can, imagine, make a website go from 90% accessible to 100%. However, it may not be a convincing argument if the website isn’t accessible and only this feature would be. But it may be the upsell, especially a number, that product people can use for justification.
Mitigating the risk of accessibility standards increase.
You may work somewhere that allows you to spend some time on personal development or projects that are work related. This is part of the job!
This is a shorter version of how I would lay out the issue but don’t be shy to go in depth on examples.
We love a big refactor and we feel really special when we see big numbers on that “ les changed” tab on our pull request. But the world doesn’t spin around us front-end developers and those big changes also imply big testing. This may also be a project that only gets a few allocated hours per month.
Changes that need a big general regression testing impact everyone so increase the chances of getting your refactor approved by product by suggesting it to be aligned with planned feature development. Or who knows, holiday periods, slow business periods or new team member onboarding.
Product people prefer refactors to rebuilds. And you can speed your approval by preparing your arguments. Is there metrics to track CSS related bugs reaching the User Acceptance Testing? Have you documented development roadblocks or slowness due to the need of a refactor? Can you demonstrate how the current CSS impacts feature development? Has this need been documented to the product team before? Do you have visibility of the product roadmap? If so, are there upcoming features that might be a affected or benefit from this refactor? Are you able to share how the lack of refactor impacts your time estimates? Would it improve predictability which allowing product people to plan with more confidence?
It can be intimidating to approach the product team. As someone who has experience working in a enormous company with hundreds of developers where only some voices are heard, it can be a frustrating experience too. Don’t be discouraged to document your opinions and your knowledge and share them and those people might actually be your team mates, a team leader, an engineering manager etc.
So you’ve waited, figured out what are the pain points and a priority from a developer perspective and got the green light from your product owner to carry on and do the work. Here’s some bits that I’ve kept in mind recently.
Harry Roberts wrote and gave some talks about CSS refactors nearly 10 years ago and they still hold on on their practically. Harry calls them the refactoring tunnels and the idea is to refactor by component or feature in isolation, keep a temporary css file to allow overrides on the newly refactored component and then delete those overrides once you tackle the higher level utilities that are causing the need for those overrides. The temporary file can be called something sensible like overrides or defend.css - which is what they suggest or whatever you want like final-final.css anyway - this is the most helpful and hopeful route to tackle a big css refactor in one go.
But let’s think about improvements that can be one on any component level. I’m the first to admit that I still get caught up on naming things and then regretting it. My brain is sometimes on auto-pilot and I may take the designs literally. It wouldn’t be un-usual on my daily work to build the typical card block! And from the designs you can see there’s a card style of latest articles and another card for team members. Some shared styles, so you may go with the approach of breaking them into two and then you deliver the website with some content in and it is glorious and beautiful. And then the website is live…
…and BAM! The person card block turns out to fit other needs that weren’t really specified!
So “think about how a feature will evolve” - is exactly what Josh Tumath says in his talk “How do we keep going wrong? Roundabouts and APIs”. Regardless of the reason why a refactor is being done, using that opportunity to make your life easier in the future is a great idea. I particularly loved the example Josh shared: booleans can restrict you once you need to go beyond a true or false.
Once we figure out how to name things, we can go one step forward and move on from the idea of the using the class property as the only way to represent the state of an object. This can also depend on the project you’re working on.
Or a better example, a progress bar. Styling based on the value of the data attribute makes your life easier when updating the status of something via javascript and removes the need to add and remove css classes. Keith goes in depth on why they also think this is approach is reasonable.
If we go back to the cards example, the article also includes the following example which I quite like. And I quote: “Attribute selectors like [attr~”value”] allow you to treat the value as if it were a list. This can be useful when you want flexibility in styling parts of a component, such as applying a style to one or more borders.
Embrace CSS logical properties as translations are here to stay.
If you got a small task to in a component, take that opportunity to give specificity issues some love. Nested classes give us a visual hierarchy that is satisfying to our brains but are actually not needed if the the elements have their own class name and you’re not overriding something in particular.
If a component is being fully tested because of a small change, increase the time estimate a little bit just to take care of that likely small css file. This is also the time to move on from pixels to rems where font sizes are being set, use logical properties, font-clamp and get rid of media queries etc.
What about automated testing? We’ve moved beyond the era of pixel-perfect design to fluid, adaptive designs that respond to user’s needs, devices and accessibility preferences. This is a challenge for traditional automated visual regression testing. Even with thresholds this may not the best use of resources unless you have a very specific product that needs this. Individual component testing is in my experience the best.
Imagine a faulty boiler, faulty tap, grimy cupboard handles and a drafty window being components of your website. That eventually got some love and sit tightly with everything else.
And sure, around the edges some extra love is needed but the pain points were fixed. Progress… not perfection.
Writing tidy and non-chaotic CSS is a skill. A skill that I work on every day. A skill that you can work on by going back and mending - not just building brand new from scratch. A lot of loud people think that using a framework removes the need to work on a skill but you’re only adding risk rather than mitigating. And you already have enough risks by even trying to do an npm install! CSS simply works. Use it.
Thank you!