
This book is dedicated to all of the CSS professionals who have ever been told they were “not real programmers.”
As an international community, the W3C starts with a mission statement: Web for All, Web on Everything. This means the Web is a medium designed to adapt to the needs and preferences of every user, and the constraints of every device. But that poses a problem for design. To quote the very first website:
“This implies no device-specific markup, or anything which requires control over fonts or colors, for example.” 1
In the early days of the Internet, web design seemed impossible. How could we have planned for every possible combination of user-needs and device capabilities into the future—from text-only terminals to smart speakers, watches, HD displays, and accessibility devices?
Nearly 30 years later, CSS is the standard web language of design, used on nearly every website and application we develop. But that broad use, and the low barriers to getting started, can lead us to underestimate this powerful language and the complex problems it is designed to solve.
As the web continues to grow its more important than ever for professional developers to understand how and why CSS works, and how we can get the most out of it.
In this book, Martine and Michael bring their experience and teaching expertise to the topic – leading us through every layer of the language: from cascade and inheritance, to progressive enhancement, web layout, responsive design, and architecture. This book is packed with guidance to keep your CSS resilient and organized.
Miriam Suzanne
Miriam Suzanneis a project manager, user-experience designer, and front-end architect. An accomplished writer and novelist, she authored “ Jump Start Sass” and is a staff-writer for CSS Tricks ( https://css-tricks.com/ ). Suzanne is a member of the Sass core team, and creator of popular open-source tools including Susy, True, and Herman. She is an Invited Expert with the W3C CSS Working Group and a teacher for the Mozilla Developer channel, producing resources for web professionals including tools, videos, articles, and demos. Suzanne is an international conference speaker and in 2017 she won the “Best Of” speaker award at CSS Dev Conference.
Writing a book is a project of passion and commitment and takes a tremendous amount of time and support from friends and family, doubly so when writing multiple books in a single year. We would like to thank our children, Brook and Xander, for their patience during this process. And of course their grandparents, Marc and Elisabeth Ebtinger, for making it possible for us to dedicate time to writing and conferences.
Without the support of the technology and speaking community, we would not be where we are today. Lee Brandt and Kevin Miller were instrumental in getting Martine to start speaking at conferences. Along with Lee, Jeff Strauss and Jon Mills helped us expand our conference presence and meet a large number of other generous people. Nate Taylor prompted Michael to start speaking about CSS. Michael first met Chris DeMars because of CSS and Chris later nominated us for the GDE, which was significantly helpful. And of course we must mention Philip Japikse, fellow speaker and Apress author, who has been tremendously supportive and without whom this book would not have happened.
Many of those who influenced us on CSS are already mentioned within the pages of this book, but it’s important to note the early and long-lasting influence that Dave Shea and Molly Holzschlag have had on our understanding of CSS.
Finally, we must mention those who contributed directly to the pages you’re about to read. The Apress team of Louise Corrigan, Nancy Chen, and Phil Nash has been supportive the whole way, from walking through the proposal process to ensuring the quality of the finished book. Our last thanks goes to the many people who went above and beyond to support our research on the history of accessibility and the people involved, including Sarah Bourne, Fred de Villamil, Jon Baggaley, Andy Budd, Eric Meyer, Steven Pemberton, Dylan Wilbanks, Chris Wilson, and Chris Lilley.

is the CTO of Andromeda, Founder and Lead Developer at FlexePark, and an international speaker. She focuses on web interfaces that are beautiful, functional, accessible, and usable, approaching user experience from both art and science, drawing from her degrees in psychology and visual communications. Martine is a 2019 Google Developer Expert in Web Technologies, a 2019 Microsoft MVP in Developer Technologies, and the author ofProgramming Languages ABC++ andApproachable Accessibility: Planning for Success .

is the CEO of Andromeda, Founder and Product Architect at FlexePark, an international speaker, a 2019 Google Developer Expert in Firebase, and a 2019 Microsoft MVP in Developer Technologies. For more than 20 years, he has been writing code and geeking out over technology. He is passionate about keeping things simple and focusing on what provides real value to the end user. Michael is the author ofProgramming Languages ABC++ andApproachable Accessibility: Planning for Success .

is a developer evangelist for Twilio and a Google Developer Expert. He’s been in the web industry for more than 10 years building applications and integrating APIs with JavaScript and Ruby. He’s British, but currently enjoying life in Melbourne, Australia. He can be found hanging out at meetups and conferences, playing with new technologies, or writing open source code. Phil tweets at @philnash, and you can find him elsewhere online at https://philna.sh .
This book on Cascading Style Sheets (CSS) takes a very different approach from most. It isn’t trying to teach you how to design web pages and, aside from a cursory overview, isn’t focused on teaching you how to use CSS. This chapter introduces the focus of this book, which is how (and why) to treat CSS as a programming language.
Cascading Style Sheets (CSS) are a web technology that allows layout, theme, and style to be applied to a document. In most common cases the document in question is a Hypertext Markup Language (HTML) file and the rendering is performed by a web browser.
Often CSS is seen as a design tool since it allows the author or designer of a web page to determine the visual look of that page. Because of its control over the final look of a web page, CSS has a direct impact on both usability and accessibility. Due to these factors, creating style sheets and writing CSS are sometimes assumed to be design tasks, and it may be the designer on a software team who is tasked with maintaining the style sheets.
“CSS had one feature that distinguished it from all the others: It took into account that on the Web, the style of a document couldn’t be designed by either the author or the reader on their own, but that their wishes had to be combined, or cascaded, in some way; and, in fact, not just the reader’s and the author’s wishes, but also the capabilities of the display device and the browser.”1
At its core, then, CSS puts control in the hands of both authors and readers. This makes it somewhat interactive and subjects it to the will of the reader of a web page, since they are able to influence the final look of a page based upon their own preferences. Most often when author intent meets the end-user influence to create a unique hybrid output, this is known as programming. So this begs the question: Exactly what is CSS? Should writing style sheets be considered programming, and should those who write CSS be considered programmers?
For starters, just like popular programming languages such as JavaScript and Python, CSS is a language. As shown in the “Structure” section, CSS has a specific syntax that must be followed and the rules you write cause actions to be performed. Additionally, the WorldWideWeb Consortium (W3C) refers to CSS as a language.2
One measure of a programming language is to ask if it is Turing complete. Skipping the formal definition, the simple explanation of a Turing complete language is one that can solve any arbitrary computation. Note that this isn’t a strict requirement and there are some very useful programming languages that are not Turing complete, most notably Structured Query Language (SQL) and Regular Expressions (RegEx). However, if a language can be shown to be Turing complete, it should remove all doubt about its classification. The combination of CSS + HTML has received the formal proof necessary to be classified as Turing complete.3
This means that CSS + HTML meets the requirements for any general-purpose programming language, and that writing CSS and HTML counts as programming. This means that you are most definitely a programmer (or web developer, if you prefer).
Despite the classification of CSS as a programming language, we can probably agree that using CSS + HTML for general programming tasks wouldn’t be particularly convenient. That is because this really isn’t the point of CSS (or HTML).
Variables
Functions
Calculations
Imports
Scope
Comments
Polymorphism
Mixins
Extension
Namespaces
List and map data structures
Mathematical expressions
See Chapter 2 for a more in-depth exploration of the CSS language features and Chapter 7 for more on CSS precompilers.
It is important to note that CSS is a declarative language rather than an imperative one. This means that rather than writing code that tells a web browser how to apply styles to a page, we instead tell the browser what styles to apply and where to apply them. These declarations are called rulesets in the specification, but may be referred to simply as rules.

CSS Ruleset
Each declaration is made up of a property-value pair. As of this writing, the CSS Working Group listed 564 possible properties. Each property must be supported by the user agent (typically a web browser) for it to have any effect. Unsupported properties are simply ignored.
Rulesets may be further grouped and modified by at-rules such as @media or @supports and are collected into style sheets. A style sheet is simply a text file with a .css extension that contains any number of rules which describe the presentation of a document or web page.
Once we accept that CSS has all the sophistication of a programming language, we need to accept the implication that we must treat style sheets like code. This means that we can take advantage of many principles, best practices, and design patterns of software architecture and apply them when writing CSS.
You may find the term software architecture used interchangeably with the term software design. This is common within the industry and both terms refer to the same high-level design thinking and process methodology. Since CSS is often used for visual design, we chose the term architecture throughout this book to avoid any confusion between these concepts.
Software architecture looks at the structure and components of a system and weighs the pros and cons of various possible combinations and approaches. The strengths, weaknesses, and limitations of various systems and approaches should all be considered. The architect’s approach is much more of the high-level bird’s-eye view than a developer’s (although often the same person will do both).
For example, if you wanted to animate an image moving across the page when a button is clicked, how would you implement that functionality? Would you use CSS or JavaScript? Would you use an <img> element, Scalable Vector Graphics (SVG) , or Canvas? Which will yield the smoothest visual animation? Which approach will be the easiest to maintain when requirements change? These are the types of questions that software architecture attempts to evaluate.
You do not have to start from scratch when making these types of decisions. There are some well-established principles of software architecture and best practices that can guide you on your journey to more strategic decision making with regard to CSS.
The term separation of concerns is credited to Edsger Dijkstra4 and refers to the idea that it is very helpful for us to focus on one aspect of a problem at a time. As shown later in the “Web Architecture” section, a web application separates content, style, and actions and even uses different technologies for each of these concerns.

CSS Areas of Concern
Now, let’s say you have a style sheet with 20,000 rulesets. This is clearly unmanageable and these rulesets need to be split into multiple files. How do you determine how many files you need and which rulesets go into each file? One approach is to split files based upon concern (e.g., layout vs. theme), while another approach would be to group rulesets based upon the specific components to which they apply. This question is quite fundamental to the discussion of different CSS architecture models in Chapter 10.
Two of the most widely accepted principles of software architecture, cohesion and coupling, serve to better define the idea of separation of concerns. These metrics were first published in Structured Design5 and have since become standards in software engineering.
Cohesion can be described as a measure of responsibility. It is a qualitative measure of the breadth of different tasks or effects a given unit of code is responsible for and the nature of the relationship between those tasks or effects. Traditionally there are seven levels of cohesion ranked from coincidental (worst) to functional (best).
Lack of side effects – If a function does just one thing, then there is little risk of side effects or unintended consequences from its use.
Only one reason to change – Every time code changes, it increases the risk of introducing errors and bugs. If we reduce the number of changes, we can diminish risk. Additionally, this helps avoid side effects from system-wide change.
The goals of both cohesion and single responsibility are to promote simplicity and reduce risk, which are important goals for all of our architectural decisions.
Coupling describes the interdependence between two or more units of code. Loose coupling is associated with good cohesion and generally describes a module with good reusability that may be updated independently of other modules with minimal impact on the overall system. This is an important attribute of robust and flexible systems.
Tight coupling is associated with poor cohesion and describes modules that are hard to test or modify independently. Such modules generally cannot be reused freely and may require larger testing efforts when changed. Favor looser coupling whenever possible.
When building web applications, we will find a lot of value in decreasing the coupling between content and design. Ideally we should be able to create style sheets that work for a wide range of content without adjustment. When we achieve this, we may say we have orthogonality.
While an important and common term when discussing system design, the word orthogonality has accumulated some disfavor in recent years. This is likely due to a combination of misuse and poor definitions leading to it sometimes being described as “technobabble.”6 However, orthogonality is an important concept that is directly related7 to cohesion and coupling, and it will factor into many decisions we discuss later in this book.
Orthogonality describes a relationship that is cooperative without being codependent, where two things work together toward a common goal while maintaining a level of independence.
In mathematics the simplest form of orthogonality of two vectors is when they are perpendicular to each other, meaning they form a right angle and intersect only once. Orthogonality can also be described as statistical independence, meaning two (or more) factors that vary without being influenced by one another
Taken into computer software, we use orthogonality to describe a relationship between two modules or components that are able to change independently of one another. For example, an HTML page may be considered orthogonal to its CSS if we’re able to edit an HTML file to change the content and/or structure of the page with no corresponding change to the CSS, but the visual design of the page remains unaffected after the change.
In fact, this separation of concerns between document layout and structure is one of the original design considerations behind CSS.
In order to exercise separation of concerns, we must first practice the art of breaking down complex and challenging problems into simpler pieces. Often we find that seemingly impossible tasks are simply large accumulations of a great many simple tasks. In learning to see the individual pieces, we now have the building blocks that we need to create solutions.
Beyond the technical aspects of software architecture, there are practical considerations that must weigh into our decisions.
It’s easy to buy into the idea that the cheapest thing to build today is the best financial decision; however, the true cost of ownership of a software product must include the ongoing cost of maintenance in the calculation. Often the thing that is the cheapest to build may be the most expensive to maintain. Perhaps we can purchase an existing third-party library or template and pull in updates from them instead of building and maintaining ourselves?
We’re often working on tight deadlines in an ongoing effort to deliver value to our customers and produce revenue for our company. The total time and effort of an architectural decision is an important decision point as it may affect both cost and timeline. Sometimes it’s worth taking a big hit initially to ramp up on a new approach that will be faster over time. Other times we need to acknowledge that going with things we’re already familiar with is the best choice. But do consider that development time is very expensive, so sometimes a decision that seems trivial (shaving 10 seconds off page reload time during development) may pay dividends later (10 seconds x 100 times a day x 260 work days x 5 developers = 15 days a year in savings).
While the technical and financial impact of our decisions are relatively straightforward, the impact decisions can have on morale are just as important and easy to overlook. So when deciding between CSS, Sass, and Less or selecting your next CSS framework, the attitude and buy-in from your team is an important consideration. Sometimes the friction can be the usual resistance to change or the pace of change; sometimes it’s a legitimate concern that the decision is not the best fit for the product or team. Yet other times it’s because the developers don’t feel the decisions are helping them build useful skills. Take these concerns seriously because morale affects performance, quality of life, and turnover.
It is important to acknowledge that the study of architecture revolves around defining solution patterns for common problems, but also that there is no absolute answer. No single approach is always right and no single decision will work in all cases. Practicing architecture is all about understanding your available options, weighing the positive and negative outcomes of each one, and then making a decision. Documenting these decisions, and the reasoning that went into them, is another important part of being an architect. It’s essential that we – and others – can learn from both our successes and our failures.
There are a series of practices that are generally good defaults in decision making. Not that they are always the right answer, but that using them absent any compelling reason to the contrary will generally produce good results.
Often referred to as DRY, Don’t Repeat Yourself indicates that duplication can be an antipattern. When a bit of code is duplicated ten times within a project, this means we must update ten places anytime this code changes. If we only update eight of these places in a future update, we may find that hard-to-diagnose bugs stick around long after we thought they had been fixed.
The same can be true for CSS – duplicating the same rulesets and declarations can lead to additional maintenance effort and inconsistency in appearance over time.
There are a number of available mechanisms to reduce duplication in our style sheets including cascading, inheritance, variables, and mixins.
The logical razor credited to Occam is: “Do not multiply entities without necessity!”.8 While Occam never wrote these exact words, this principle comes from his work on problem solving, making it relevant to a programming context. The principle of Occam’s razor is perhaps better known as “the simplest working solution is likely the best one.”
A logical razor is a rational principle used to shave off possible but unrealistic or unlikely explanations for a given phenomenon.9
Simplicity provides great value to our code. It can make code easier to debug, easier to read, and make it easier for new teammates to get up-to-speed. Also, this provides an excellent default barring any external factors – the simplest solution we can come up with should be sufficient for many cases.
Sometimes referred to as YAGNI, the principle of You Ain’t Gonna Need It is that we should generally avoid adding anything to our code that we don’t have a specific requirement for. We should generally avoid premature optimization by keeping our code as simple as we can until there’s a compelling reason to do otherwise. Often this even means ignoring the DRY principle until we know that we’ll need a bit of code three to four times or more as the cost of minimizing duplication may be too expensive for just two to three cases.
Use existing architectural patterns and approaches, such as those presented in Chapter 10. Use Google to find other people with similar challenges and learn from them. Take to social media to get help from colleagues.

Browser Engine10
All of the source files must be retrieved from a web server, and then the text must be parsed according to its type. The HTML and JavaScript combine to build and manipulate the Document Object Model (DOM) , which will be described in more detail in the following text. The Style Engine combines the DOM with the CSS to produce a layout, including any media files such as images or video. But even this layout is just a nonvisual model that must be rendered onto the screen using the paint and composition steps.
While it is not necessary to fully understand all of the activities undertaken by the browser, the relationship between HTML and CSS is of particular interest throughout this book. Since we’ve already covered an explanation of CSS, an overview of HTML and the DOM is provided in the following sections.
There are a large number of CSS features that are simply not available using inline styles, including most of the at-rules. Additionally, this “breaks” cascading and inheritance, which is described in more depth in Chapter 3.
The fact that an HTML document must specify its own style sheets implies an authoritative relationship from the document to the style sheet. A style sheet does not get to specify what documents it belongs to, but it can specify selectors and conditions that determine the situations where rules get applied to a document, a concept which gets expanded upon in the following chapters.

HTML Structure
As illustrated earlier, HTML is made up of tags which are demarcated by angle brackets. An HTML element refers to the entire content of a tag, from the first angle bracket of its opening tag to the last angle bracket of its closing tag. Some elements, such as <img>, have no body and thus no closing tag. Some elements, such as <div> or <button>, may contain text or even other tags between their open and closing tags. All tags may have attributes such as ID, class, or title. Some tags have mandatory attributes, which are required to be considered valid.
There are CSS selectors for tags, attributes, and values, which will be covered in detail in Chapter 2. One thing worth noting here is that some HTML tags exist primarily to provide semantic context.
Semantics is the branch of linguistics and logic concerned with meaning. When applied to code, including HTML, we use the word “semantic” to indicate a word or tag that conveys a meaning or purpose beyond serving as a simple label.
For example, <div> can be used to group any arbitrary set of tags, but all this communicates is a generic division or grouping. The <nav> tag indicates navigation, <article> indicates independent and self-contained content, and <aside> indicates related but secondary content, much like the preceding Note. This additional meaning is helpful for user agents and screen readers, but can also be used within our CSS to write more robust and meaningful selectors.

Document Object Model
Every item in the DOM is called a node. A node may be an element, attribute, or text, reflecting the underlying HTML. Also like HTML, elements have attributes. All of these can be read and modified directly using JavaScript for dynamic web pages.
Note that CSS doesn’t factor into the DOM directly. However, it is possible to use the DOM to alter visual output by directly modifying the class or style attributes. Looking back at Figure 1-5, we see that the DOM provides the structure that the Style Engine applies the CSS to when producing a layout.
October Browser: WorldWideWeb, Tim Berners-Lee – first web browser | 1990 | |
1993 | January 23 Browser: Mosaic – first browser to show with images inline with text | |
October 1 WorldWideWeb Consortium (W3C) Founded December 15 Browser: Netscape Navigator 1 | 1994 | October 10 CSS first proposed by Håkon Wium Lie |
August 16 Browser: Internet Explorer 1.0 | 1995 | April 10 Browser: Opera 1 |
1996 | December 17 Cascading Style Sheets, level 1 (CSS1) Recommendation, W3C | |
May 12 A List Apart, Jeffrey Zeldman | 1997 | |
August Web Standards Project (WaSP) | 1998 | May 12 Cascading Style Sheets, Level 2 (CSS2) W3C Recommendation |
1999 | June 22 First 3 CSS3 Drafts: Color Profiles, Multi-column layout, and Paged Media | |
April 14 CSS3 Introduction, W3C Working Draft | 2000 | |
2002 | October 10 Wired News CSS Redesign11 | |
January 7 Browser: Safari 1 May 7 CSS Zen Garden, Dave Shea | 2003 | February ESPN CSS Redesign12 |
2004 | November 9 Browser: Firefox 1.0 | |
October Sass CSS preprocessor | 2005 | |
2007 | July 4 CSS-Tricks, Chris Coyier | |
December 11 Browser: Google Chrome 1.0 | 2008 | |
2009 | August Less CSS preprocessor | |
April caniuse.com | 2010 | |
2011 | June 7 Cascading Style Sheets, Level 2 Revision 1 (CSS 2.1), W3C Recommendation | |
June 19 Media Queries, W3C Recommendation13 | 2012 | |
2013 | November 7 Style Attributes W3C Recommendation14 | |
March 20 CSS Shapes Module Level 1 W3C Candidate Recommendation15 | 2014 | |
2015 | December 3 CSS Custom Properties for Cascading Variables W3C Candidate Recommendation16 | |
March 1 CSS Flexible Box Layout Level 1, W3C Candidate Recommendation17 | 2016 | September 29 CSS Grid Layout Module Level 1, W3C Candidate Recommendation18 |
September 25 Scrollbars Module Level 1, W3C First Public Draft19 | 2018 | November 6 Selectors Level 3, W3C Recommendation20 |
November 24 Writing Modes Level 3 W3C Proposed Recommendation21 | 2019 |
Affecting the visual style of a web page was a known problem from the very beginning of the Web. Initially some basic visual controls were built into HTML, but problems with this approach were quickly identified. As mentioned at the beginning of the chapter, many people proposed, and even implemented, mechanisms for styling the Web, and during this time Håkon Wium Lie proposed the idea of CSS. Together with Bert Bos, he developed a proposal which was submitted to the newly formed W3C.
Lie and Bos went on to found the first W3C CSS Working Group along with Chris Wilson and Vidur Apparao, with Chris Lilley as the first WG Chair. The CSS level 1 recommendation was published 2 years later in 1996.
In 1994 Håkon Wium Lie joined the WorldWideWeb project at CERN where he joined web pioneers Tim Berners-Lee and Robert Cailliau. In this first year, he drew upon his background in electronic publishing from the MIT Media Lab and produced the proposal for CSS. He went to work for W3C the following year, on the CSS Working Group.
Lie became the CTO of Opera Software in 1999, which was the most CSS-friendly browser at the time. He continued in that role until 2016 when the company was sold.
While Håkon Wium Lie was working on his proposal for CSS, Bert Bos was producing his own stream-based style sheet proposal.22 He reviewed the initial proposal for CSS and he and Lie determined that the two proposals could be combined. During the transition of the WorldWideWeb project out of CERN in 1995, Bos was hired to the newly formed W3C, where he continued working with Lie on the CSS1 specification.
Bos remains an active member of the CSS Working Group, having previously served as a chairman of the group. Together with Lie, he wrote Cascading Style Sheets: Designing for the Web, one of the very first books on CSS.
Chris Lilley started establishing web standards as a member of the Internet Engineering Task Force (IETF) working on HTML 2.0 and the PNG graphics format. He joined the W3C in 1996, initially working with graphics and fonts, chairing a Working Group on Web Fonts. When the CSS Working Group was formed a year later, he became the chair of this group. The following year he began 10 years as chair of the W3C Scalable Vector Graphics (SVG ) Working Group. Over the years Lilley has authored and edited a large number of web and graphics specifications, and books on the same.
Chris Wilson was a founder of the first CSS Working Group and was credited by Håkon Wium Lie as the programmer who actually added CSS to Internet Explorer version 3, before the specification was even finished.23 He has remained active in the W3C ever since, where he has held positions including chair of the Web Platform Incubation Group, chair of the HTML Working Group, and a member of the Advisory Board. He worked on Internet Explorer for Microsoft until 2009, and in 2010 he joined Google where he works on Chrome, specifically augmented and virtual reality capabilities.
Vidur Apparao joined the initial CSS Working Group while working as Chief Architect at Netscape, where he was working on the Gecko Layout Engine. In addition to his work on the CSS group, he also contributed to the Document Object Model recommendations. After more than a decade as a web architect, Apparao has continued his career as a cloud software executive.
Before CSS was to become a well-known and proven technology, a few early web sites would need to take the plunge and update their old HTML3 web sites with inline styles to a more “pure” CSS-based layout and theme.
This new design is more accessible, faster to download, more flexible and much easier on the Web server itself. Anyone interested in the future of the Web need look no further than this.24
While Wired was making their big announcement, another team at ESPN was working hard on their new web site. Announced just 4 months later, their big victory was the (nearly) tableless layout.25 By proving that building sites with CSS-based (instead of table-based) layouts could work for sites receiving millions of monthly pageviews,26 these two web sites helped solidify the place of CSS as a powerful web standard.
Without early advocates to inform and educate web developers, we might have a very different Web today. A large number of developers, including the authors of this book, were educated and inspired by these advocates, and this book does not stand alone but builds upon their years of work.
The very first major effort for web and CSS education and advocacy got its start as an e-mail list. A List Apart was founded in 1997 by Jeffrey Zeldman, who was soon joined by Eric Meyer. This early mailing list has grown into an entire ecosystem, including books and conferences, which continue to be influential to this day.
Jeffrey Zeldman started his career in web design in 1995, after a decade in advertising copywriting. The year after starting A List Apart, he cofounded the Web Standards Project (WaSP) along with George Olsen and Glenn Davis, starting a career-long push for open web standards. Zeldman was inducted into the SXSW Interactive Hall of Fame in 2012 and was the first person to ever receive the honor.27
Along with Håkon Wium Lie and Tim Boland, Meyer developed the very first test suite for CSS1 which was intended to help assess conformance to the standard. He joined WaSP the same year and cofounded its CSS Action Committee. Meyer has written six books on CSS along with countless articles for some of the most influential web design publications, including A List Apart. He also founded the css-discuss mailing list, and cofounded An Event Apart with Jeffrey Zeldman. In 2006 Meyer was inducted into the International Academy of Digital Arts and Sciences for his international work on HTML and CSS.
In 2003 a magical new web site was launched which demonstrated the power of CSS. CSS Zen Garden had a unique approach – it provided a fixed HTML document which designers were encouraged to style and theme as much as they wanted, using nothing but CSS (and images). By preventing edits to the HTML, web designers were forced to decouple their design implementations and the result was magical. The first few themes were provided by the site author, Dave Shea, but soon designers around the world were submitting their themes for consideration. This provided a powerful, hands-on lab that proved once and for all that CSS had a place as a first-class citizen of the web ecosystem.
In 2005 Dave collaborated with Molly Holzschlag to produce a book, The Zen of CSS Design, which sold over 70,000 copies and became the international standard for web design for some time.28
Shortly before launching the CSS Zen Garden, Dave Shea started a weblog about web design titled mezzoblue. For the next few years, he became a prolific blogger, providing valuable insights on a wide range of topics. Shea was active in the Web Standards Project as well as writing for A List Apart.29
While the Web was being conceived at CERN, Molly Holzschlag was launching her career in Internet technology. She published her first book on web design in 1996, going on to write more than 35 books on web technology and design. She has been widely recognized as one of the most influential women on the Web.
Holzschlag has worked directly with CERN, AOL, Microsoft, BBC, eBay, Opera, and Netscape to ensure browsers support modern standards. She has been project leader for WaSP, chair of the W3C CSS Accessibility Community Group, and a W3C invited expert to both the Internationalization Guidelines, Education & Outreach Working Group, and the HTML Working Group.30
It is unlikely that anyone reading this book has performed a search for answers about CSS without coming across the CSS-Tricks web site created by Chris Coyier. For more than a decade, this web site has been sharing practical tips and tricks about CSS and other web development topics.
In 2007 Chris Coyier founded CSS-Tricks as a personal blog about CSS. Today the web site hosts articles from a large number of web developers and designers, including many listed in this chapter. Together with Tim Sabat and Alex Vazquez, Coyier founded CodePen, a very popular online code editor and sharing platform.31
The CSS Working Group at the W3C is still going strong under fantastic leadership. The current modular approach to CSS level 3, along with a new trend of evergreen browsers, has led to a steady pace of progress. The following are a handful of active and influential people who, in addition to many of those already mentioned, are continuing to improve the state of CSS.
Rachel Andrew is the author of more than 20 books about web development. She was a member of WaSP and is an invited expert to the W3C CSS Working Group. She is a Google Developer Expert, contributor to A List Apart, and the Editor in Chief of Smashing Magazine.32
Jen Simmons is a designer and advocate at Mozilla, where she works on Firefox specifically the Grid Inspector. She has spoken at many conferences, including An Event Apart and SXSW. Simmons is an active member of the W3C CSS Working Group where she has been extremely influential in the design and deployment of CSS grid layout. She has been an active web developer since 1998 and her clients have included CERN, the W3C, and Google.33
Nicole Sullivan is a popular speaker, with her conference appearances including An Event Apart and SXSW. She has coauthored two books on web performance and is an advocate for CSS and web standards. Sullivan started the Object-Oriented CSS (OOCSS) project which provides an architectural framework for CSS. Along with Nicholas Zakas she also created CSS Lint, a tool which helps catch common CSS errors.34
Miriam Suzanne is a project manager, user-experience designer, and front-end architect. An accomplished writer and novelist, she authored Jump Start Sass and is a staff-writer for CSS Tricks. Suzanne is a member of the Sass core team, and creator of popular open-source tools including Susy, True, and Herman. She is an Invited Expert with the W3C CSS Working Group and a teacher for the Mozilla Developer channel, producing resources for web professionals including tools, videos, articles, and demos. Suzanne is an international conference speaker and in 2017 she won the “Best Of” speaker award at CSS Dev Conference.35
The names of the various parts of a CSS ruleset
That CSS is a programming language and why this is important
How a user agent such as a web browser applies CSS to a web page
In the next chapter you will get a review of the basic CSS language features, with special attention to advanced and less commonly used language features.
While you may already be familiar with the basics of CSS, this chapter provides a quick overview of the language features at your disposal when making architectural decisions. An important part of software architecture is having a deep understanding of the tools and methods available to accomplish various tasks to achieve our system goals.
As we saw in Chapter 1, selectors are the part of a CSS ruleset that determine exactly which elements get style declarations applied. Proficient use of selectors can go a long way toward decoupling HTML and CSS making for robust and consistently styled web sites and web applications.
The basic selectors allow elements to be selected based upon their obvious qualities as rendered in HTML: tag names, attributes, and class names. The CSS selector syntax is so expressive that there is a DOM function querySelector which accepts a CSS selector string to locate elements in the DOM tree. See Chapter 8 for more about JavaScript.

Universal Selector
However, this convenience comes at a cost – the universal selector effectively short-circuits inheritance for the associated declarations.
Unless you have a specific use case, it’s generally best to avoid the universal selector in favor of inheritance. It’s also a good idea to use the universal selector in combination with other selectors.
One use case for the universal selector is to apply a declaration to all children of another element, even if that property wouldn’t be inherited, such as a border. For this use case, consider either custom properties or mixins as an alternative to this approach.
Selecting an element in CSS can be as simple as using the tag name. This is called the type selector , and all HTML tags are valid selectors.

Class Selector
In this example, the <img> element does not receive a border due to the type selector. The OK button has a thicker, darker border than the Cancel button.

ID Selector
This will make the button text bold on the OK button. The example shows that the ID selector may be combined with type selectors in the same way as the class selector. By combining these selectors in a way that doesn’t match the HTML, the contents of the paragraph tag will not become pink in this example.
The attribute selector matches an element based upon one of its attributes. This selector uses square brackets to contain the attribute match and may be optionally combined with a type selector. For example, a [rel] can be used to match all anchor tags with a provided relationship. To allow <area> tags to also match, use [rel] by itself.

Attribute Selector
Basic Selector | Attribute Selector | |
|---|---|---|
Select by ID | #contactForm | [id=contactForm] |
Select by Class | .outline | [class~="outline"] |
To minimize duplication of declaration blocks, selectors can be grouped together into a comma-delimited list. For example, a, button { ... } would apply the declaration block to both anchor and button elements in the HTML.
Name | Combinator | Example | Description |
|---|---|---|---|
Descendant | " " (space) | nav a | All anchor tags inside of a nav element |
Child | ">" | nav > ul > li | First list items inside a navigation list, ignoring any items after the first level |
Sibling | "~" | p ~ p | All paragraphs (after the first) that share the same parent element |
Adjacent Sibling | "+" | h2 + p | All paragraphs that immediately follow an <h2> tag on the same hierarchy |

Combinators
By combining selectors together, we can select elements to style based upon their natural location and ordering within an HTML document. This can help us separate concerns between layout, theming, and content for more manageable rulesets.
Pseudo elements allow you to select elements that do not exist within the HTML document, but show on the screen visually. Both ::first-letter and ::first-line select a portion of text within an element.

Pseudo Elements – ::first-line and ::first-level

Pseudo Elements – ::before and ::after

Pseudo Elements – ::placeholder and ::selection

Pseudo Elements – ::backdrop
Have you selected text on a web site and noticed that the selection highlight was in the site’s brand colors? This can be accomplished with *::selection {background-color: cornflowerblue}.
The background in full-screen browsing mode can be customized using ::backdrop.
Both are also seen in the preceding example.
The CSS specification calls for a two-colon prefix before a pseudo element, such as ::after. However, most browsers support pseudo elements with just a single colon (:after) without throwing an error. It is likely that you will see this usage in the style sheets you encounter and it is important to understand why it works. In general, we recommend the standard two-colon prefix for two reasons: (1) it adheres to the CSS specification and (2) it clearly distinguishes pseudo elements from pseudo classes.
Pseudo classes select elements based upon information that is not available in the HTML document. This may include state or context metadata.
:hover – Match when an element is being hovered over (such as using the mouse)
:focus – Match an element selected with the keyboard (by tabbing), or with the mouse (by clicking the element)
:active – Match an element in the process of being activated (such as clicking, while the mouse button is depressed)
:target – Select an element that has an ID matching the URL’s fragment (the portion after the #)
Displaying tabular data with beautiful formatting is made easy with the positional pseudo classes. Select the first and last rows of a table with tr:first-of-type and tr:last-of-type, respectively. Use the same technique to select the first and last columns using <td>. Highlight every other row using tbody > tr:nth-child(even).
:in-range, :out-of-range – Numeric value compared to defined range
:placeholder-shown – If the placeholder text is currently visible
:invalid, :valid – Checks the validation status of form fields for error and success indicators
:checked, :indeterminate – Used to select a checkbox or radio button that is currently selected or if the selected option cannot be determined
:default – Matches only if this element is the default in a group of elements (such as the default submit button or the default radio option on a form)
:disabled, :read-only, :read-write – Matches the current status of a form field based on availability to user interaction
:optional, :required – Matches fields based upon their required status
Another important pseudo class is the :not() selector, which select elements that do not match a list of selectors. While many of the pseudo classes have their inverse state defined (e.g., :optional vs. :required), there are many other scenarios where negation can be useful. For example, you can select every direct child tag of an <article> that is not an <img> by using article > *:not(img) { ... }.

Pseudo Classes
Selecting the elements doesn’t do any good if we don’t apply styles to those elements. The declarations section of each ruleset is where the individual style properties, and their values, are specified for the matching elements.
The properties in CSS refer to the various aspects of layout and style that can be affected. Many properties are available for some elements and not others. Sometimes the visibility of a property will depend upon the display setting of an element. For instance, the height property is ignored on elements with display: inline, but is rendered on display: inline-block.
Each of the individual border properties is available as an optional value parameter to the border shorthand property. Some of the other shorthand properties include background, box-shadow, font, padding, margin, and outline. Each of these has a different list of properties they summarize, and they have a specific order in which the properties should be provided. Be sure to check a reference when using these until you become comfortable with the syntax of each.
First Property | Second Property | Description |
|---|---|---|
margin: 2px; | padding: px; | Margin is outside the box model and can be collapsed when adjacent. Padding is inside the box. |
border: 2px solid black; | outline: 2px solid black; | Border adds to the box model dimension and exists between margin and padding. The outline exists outside the border and takes up no space on the box. |
visibility: hidden; | display: none; | A hidden element still exists on the page and can take up space and receive events. An element that is not displayed effectively doesn’t exist in the render tree. |
The outline property is very useful for highlighting elements on the screen where you do not want the item to reflow. This is commonly used to highlight elements in combination with the :focus pseudo class. See Chapter 4 for details of the box model, as well as on properties related to layout, including display, grid, and flex.
A comprehensive review of the available CSS properties and values is outside of the scope of this book. For an excellent reference, we recommend the MDN CSS Reference from Mozilla, which can be found at https://developer.mozilla.org/docs/Web/CSS/Reference.
There are a number of CSS properties that expect a <length> data type. This length is a scalar (numeric) value with an associated unit of measure. Selecting the correct units can make the difference between a nice fluid, responsive layout and one that breaks anytime the user resizes the window or zooms. The correct units can also have a tremendous impact on the amount of work it takes to achieve certain layouts.
There are three basic categories of units that we’ll discuss. The first category includes absolute measures that are established at the time of the design. The second category is font-relative, meaning that if the user zooms the page or changes their default font size, the meaning of these values will change relative to one another. And the third category contains viewport-relative lengths, meaning they will change relative to the browser size or the specific display on the user’s device.
px – The traditional unit of measure for computer graphics; this is only suitable for screen-based displays.
in –Inch. 1in. = 6pc = 72pt = 2.54cm. This will be a true inch on printers, but defined relative to a reference pixel for screens which is 96px regardless of the screen resolution.
pc – Pica. A traditional unit of measure in typography.
pt – Point. A traditional unit of measure in typography.
cm – Centimeter. 1cm = 10mm. See the earlier note on inches relating to printers and screens.
mm – Millimeter.
Absolute units of measure do not scale relative to user settings such as font-size. As a result, the use of these units (especially on-screen) is likely to cause significant issues for accessibility and is not recommended.
ch – Represents the width of the “0” character in the element’s font (consisting of both typeface and size).
ex – Represents the height of the “x” character in the element’s font (consisting of both typeface and size).
em – The calculated font-size of the element. If this unit is used on the font-size property, it will be relative to the inherited font-size.
rem – Exactly the same as em, but always relative to the font-size of the root element (which is the <html> for HTML documents). This is the preferred default unit for many web designers as it allows for manageable fluid layouts while addressing accessibility concerns.
vh – Equal to 1% of the height of the viewport
vw – Equal to 1% of the width of the viewport
vmin – Equal to the smaller of vh or vw
vmax – Equal to the larger of vh or vw
Many CSS properties will accept a <percentage> or a <length-percentage> (meaning either a length or a percentage). While the rem is the best choice for many purposes, especially those relating to content and accessibility, percentage works relative to any inherited size including font-relative, view-relative, or even absolute units.
While CSS does not allow user-defined functions, there are a large number of available functions to perform a variety of tasks, some of which are described as follows:
Shape – There are a number of nonrectangular shapes supported through the functions circle(), ellipse(), inset(), and polygon(). Combine with the shape-outside property to wrap text to a specific shape, or with clip-path to crop an image or container.
Transformation – There are a large number of transformation functions, including rotateX(), scale(), and skewY(). There are also 3D transformations such as perspective(), matrix3d(), and scaleZ(). These transformations can adjust the shape, orientation, and position of elements on the screen to create a wide range of visual effects and layouts.
Gradients – There are a large number of functions to support the creation of gradients, including linear-gradient(), radial-gradient(), repeating-linear-gradient(), and repeating-radial-gradient(). The blending of colors enabled by gradients supports a large number of visual effects.
Effects – There are other visual effects beyond just gradients. The blur() function will produce a Gaussian blur on the selected element, even an image. This can be useful for the backdrop of a modal dialog. The drop-shadow() adds some dimension to a theme. And opacity() allows elements to be somewhere between fully opaque and fully transparent, to allow dimensional overlays. (Note that if you want opaque text but a semitransparent background, you may want to consider using the rgba() or hsla() color functions as described in the following text.)
Color – The most common way of specifying color in CSS is with the 3- or 6-digit hex code preceded by a hash symbol, such as #FF0000 for the color red. Colors can also be specified by hue, saturation, and lightness using the hsl() and hsla() functions, or as RGB (red, green, blue) using rgb() or rgba(). The “a” in each of these function sets refers to the alpha channel which specifies level of opacity or transparency.
Colors can also be manipulated in a consistent fashion using the filter property with alterations such as contrast(), saturate(), and hue-rotate() and effects applied such as grayscale() or sepia(). These functions are particularly useful because they can apply to an image as well as text on the page.
Resources – The url() function is used to add image resources to a design through CSS. This allows the <img> tag in HTML to be reserved for images that are relevant to the content, rather than to the layout and design.
Counting – The counting functions counter(), counters(), and symbols() are used to manage counter variables. See more about counters in the following “Variables” section.
Math – Sometimes the built-in units aren’t enough and you need to calculate size or position based upon other elements. The calc() function makes it possible to do some basic math with a mix of units. Addition, subtraction, multiplication, and division are supported along with parentheses. As an example, you could use height: calc(10vh - 1rem) to calculate the height of a header that was 10% of the viewport height, but accounted for a 1rem border.

Functions
The example shown in Figure 2-12 highlights a number of functions. The position of the stars in the example is dependent upon the size of the browser window since calculations are based upon the vw and vh units.
There are a few ways to use dynamic data within CSS (examples found in Listings 2-21 and 2-22 and Figure 2-13):
Custom properties – These variables are defined much like any other CSS property and can contain any value that would be legal in CSS. They can then be referenced later in a style sheet using the var() function.
Attributes – Using the attr() function , you can pull in the value from an HTML attribute. Combine this with the content property to display attribute data in unique ways.

Variables
The CSS at-rules (so-named because of the @ or “at” symbol in the name of each) are language features that provide some control over the structure of your styles. Among other things these rules provide a mechanism for collecting or grouping other rulesets.
In Chapter 1 we looked at three ways to include CSS in an HTML document, including the <link> element. The @import at-rule provides a similar function for CSS. Both of these include pull mechanisms in a CSS file, effectively inserting its contents at the position of the import statement.
This is very useful as it allows us to break up style sheets into more logical and manageable files without any impact on the HTML document. See Chapter 7 for a more in-depth discussion of @imports along with other mechanisms for pulling in external style sheets.
The @supports at-rule allows rules to be applied based upon specific support of CSS features by the user agent. This is a way to provide styling and formatting based upon what a web browser declares support for, rather than using old-school hacks in an attempt to detect if a given rule will work as expected.

At-Rules, text-underline-offset Supported in Firefox

At-Rules, text-underline-offset Not Supported in Opera
The CSS media at-rule is used to perform queries against the system, environment, or user agent. This use is called a media query .
“Media Queries allow authors to test and query values or features of the user agent or display device, independent of the document being rendered. They are used in the CSS @media rule to conditionally apply styles to a document, and in various other contexts and languages, such as HTML and JavaScript.”
—Media Queries Level 41
Additional control over printing can be obtained by providing page-specific instructions using the @page at-rule.2
Specify printer-specific layout and designs
Insert document icons based upon the file extension using nothing but CSS
Combine multiple selectors together into more advanced expressions
Highlight alternating rows on a table
Provide alternate styles based upon browser and device capabilities
The next chapter covers the cascading part of Cascading Style Sheets and demystifies the process by which a user agent decides the values of every property of every element on the page.
As mentioned in Chapter 1, one of the important features of CSS is the ability for the user, browser, and web developer to all exert influence over the final output of the page. The user agent, the author, and the user can all three influence the output of the page. To dictate what property value “wins,” a multistep calculation is performed.
Inheritance is the mechanism by which CSS allows a value set on a parent element (such as <body>) to propagate to its descendants. This helps determine what value is used when no property is declared on an element property. The inherited value is determined by the computed value of a parent or ancestor. If none exists, the initial value, or default set by the browser, is used.

Cascading and Inheritance
The body attribute has a text-align property value of justify. Styles are not set on paragraph attributes; however, the paragraphs are in fact justified. The paragraph text-align value is inherited from the body’s text-align property. Padding however is not inherited, which is why even though the body selector has a padding value of two rems, paragraphs, the image, links, and so on do not also have a 2rem padding value.
One of the main benefits of inheritance is that it prevents the need to write values for the same properties over and over again across different selectors helping with consistency of the styles and maintainability of the code.
In this example the color is also inherited, but the first letter of the first paragraph does not display in gray as set in the body selector, but in gold as set in the p:first-of-type::first-letter selector. The reason the first letter of the first paragraph is gold rather than gray is a question of specificity; p:first-of-type::first-letter is more specific than body.
Inherit, unset , and initial are a little different from the rest of the property values available in CSS. These values are available on all properties and have the distinct difference of either resetting a value to default or to that of an ancestor rather than a new value. These values give you explicit control over how a property is inherited.
Unset works differently depending upon the property to which it is being assigned. If the value can be inherited from the parent, it will inherit; otherwise, it will set the property value to initial.

Inherited Unset

Unset on a Non-inheritable Property

Initial

Inherit
As this example shows, we can force inheritance through the use of the inherit property, giving us direct control over the cascade.
Category | Selectors |
|---|---|
A | ID selectors |
B | Class selectors, attribute selectors, pseudo classes |
C | Type selectors, pseudo elements |
0 | Universal selector |
Example Selector | A | B | C | Specificity |
|---|---|---|---|---|
* | 0 | 0 | 0 | 0 |
button | 0 | 0 | 1 | 1 |
ul li | 0 | 0 | 2 | 2 |
button:not([type=submit]) | 0 | 1 | 1 | 1 1 |
a[href$=".pdf"]::before | 0 | 1 | 2 | 1 2 |
button.outline.bold | 0 | 2 | 1 | 2 1 |
button#submit | 1 | 0 | 1 | 1 0 1 |
Specificity plays an important role in determining which styles will get applied during the cascade.
Styles applied directly to the element in the HTML such as
<p style="margin-left: 10px">Lorem ipsum am met...</p>
are inline styles. They are the equivalent of adding property values directly in the DOM with the use of JavaScript. Inline styles are given a specificity of [1 0 0 0],3 which is higher than anything possible using normal selectors, as shown in Table 3-2. Inline styles are generally considered bad practice because they ignore inheritance and cascading. There are a few exceptions where they may be unavoidable though, including HTML e-mails.
The order in which rules are applied matters. Directly targeted rules will always take precedence over rules which inherited from a parent or ancestor. If two rules with the same level of specificity are applied, the last one in order will be applied. This concept lies at the core of CSS and has since its inception, as indicated by its name “Cascading Style Sheets.”
The !important annotation is well known among CSS practitioners as both a powerful tool and a great liability. It’s sometimes used by web developers to force a style to take effect when nothing else seems to work. But did you know that the purpose of !important is actually to improve accessibility? Because important user declarations always have the highest precedence, it gives the user the final say on which properties and values are set when the page is rendered.
Order | Origin | Importance | Precedence |
|---|---|---|---|
1 | User Agent | Normal | 8 |
2 | User | Normal | 7 |
3 | Author | Normal | 6 |
4 | Animations | 5 | |
5 | Author | !important | 4 |
6 | User | !important | 3 |
7 | User Agent | !important | 2 |
8 | Transitions | 1 |
In cascading, the last item to be applied wins; therefore, transitions will win over user agent !important containing rules over user !important rules and so forth.
Cascading represents the way properties and values from a variety of sources, at varying levels of precedence and specificity, come together to determine the final set of styles that will be rendered.
It is important to note that it is properties that are cascaded to elements, not rulesets. The final state of an element may include properties that were declared in many different rulesets.
The declarations with the highest precedence are selected.
The remaining declarations with the highest specificity are selected.
When all other factors are equal, the declaration that appears last will be the one that is applied.
First, all the declared values applied to an element are collected, for each property on each element. There may be 0 or many declared values applied to the element.
Cascading yields the cascaded value. There is at most one cascaded value per property per element.
Defaulting yields the specified value . Every element has exactly one specified value per property.
Resolving value dependencies yields the computed value. Every element has exactly one computed value per property.
Formatting the document yields the used value. An element only has a used value for a given property if that property applies to the element.
Finally, the used value is transformed to the actual value based on constraints of the display environment. As with the used value, there may or may not be an actual value for a given property on an element.
Declared – These are all the values (0–many) that match the element and property under review.
Cascaded – This is the value (0–1) that is selected after processing the cascade.
Specified – This is the value of the cascade, if available, or the default value for the property and element. There will always be exactly one (1) specified value for each property and element.
Computed – The absolute value of the specified value which can then be inherited by child elements.
Used – This is the final value that the user agent uses for the layout of the document.
Actual – This is the value that is actually shown on a device, which may be adjusted from the used value due to device or environmental limitations.
The final actual values used for each property on each element are determined by a wide variety of factors external to your project code, including device, user agent or browser, user agent style sheet, and user-provided style sheet.
How HTML inline styles and !important annotations affect cascading
How to calculate the specificity for any given selector
The way properties are inherited within the DOM tree
In the following chapter, you will learn about the different options CSS provides for building fluid and responsive layouts that can adapt to variations in device and content.
Individual elements form a layout when they are put together on a page. Using CSS we rely on the box model to control the width and behavior of each element without the layout. To control how elements place themselves in relationship to each other, we can use properties such as display and float. In this chapter we define the box model and look at float, flex, inline-block, and grid for specific layouts.

Box Model
Each of these properties, including the content, will be governed by dimension, type, positioning, relationship to other elements, and external information.
Box-sizing , or the property that defines the height and width of an element, by default has a value of content-box which means that when a width and height is defined for an element, it is only applied to the content. Adding padding or margin to the element therefore increases the percentage width of the total available viewport that the element utilizes.
If a two-column layout, with each div equaling 50% of the width of the viewport, is desired, the amount of padding applied to each column needs to be subtracted from the width given to the element or the total width of both elements will exceed 100%.

Content-Box – No Padding

Content-Box – With Padding

Effects of Padding and Border When Using box-sizing: content-box
Border will behave the same way as padding; therefore, any border width applied will need to be included in the sum of content and padding to calculate the full width or height of the elements included in the layout.

Margin Collapsing
There is nothing separating the margin of the parent and the margin of its child including padding, border, inline parts, block formatting context, or clearance property clear (e.g., clear: right, used with floats).
Elements are adjacent siblings except if the latter needs to be cleared past floats (more about floats later in this chapter).
Even when one of the margins is equal to 0.1

Margin Collapse

Floated Divs – No Margin Collapse
table-row-group(<tbody>)
table-header-group (<thead>)
table-footer-group (<tfoot>)
table-row (<tr>)
table-column-group
table-column
Margin cannot be set on elements with table display types (e.g., <tr>, <td>, etc.) except table, inline-table, and table-caption.2
Although the mixins pixels and percentage-based values can lead to some interesting math, the benefit of keeping the box-sizing value as content-box is that when a width or height value is assigned to the content, it will not be subject to side effects from the padding added. The content will be exactly the height or width it was assigned by the developer. Furthermore, because it is the default value, the element’s sizing will exhibit “normal” expected behavior without having to know anything about the other properties already set on the element.

Outline and Box Shadow

Box-Shadow and Outline Do Not Occupy Space in the Layout
When both outline and box-shadow are set, they will overlap each other.
As described earlier, box-sizing: content-box has some disadvantages when mixins absolute units and percentage-based units. Content-box can also add extra complexity when setting content as a percentage of total width or height when the content has padding.

Border-Box with Padding

Element Using Border-Box
Box-sizing is not inherited. It will need to be applied to all elements for which it needs to be changed.
Margin and padding allow for manipulating the display of the element; the display property manipulates how elements are displayed in relationship to one another by specifying the type of rendering the box uses for the element.
Level 1 | Level 2 (Revision 1) | Level 3 |
|---|---|---|
1996 | 2011 | 2018 |
block inline list-item none | inline-block table inline-table table-row-group table-header-group table-footer-group table-row table-column-group table-column table-cell table-caption inherit | Contents flow-root run-in inline list-item flex * inline-flex * grid * inline-grid * ruby** |
*Deprecated
**Obsolete

Inline Elements

Element Using border-box
Also considered flow content, block elements stack atop one another unless they are affected by another property such as float.

Default Block-Level Behavior Diagram

Default Block-Level Behavior

Block and Inline

Inline-Block
The default behaviors of inline, block, and inline-block elements are not enough to create the layouts often desired. Before such options as grid and flex were introduced in 2018 or float, introduced until 1996,6 we relied on tables even when the data was not tabular. For a long time, this was the only option in creating some more complex layouts, as even if the CSS specification described better methods, browsers did not necessarily support them. Today, this is no longer the case, and the use of tables for display purposes is now thoroughly frowned upon as it prevents assistive technologies from properly conveying the content being displayed to the users. Some exceptions, such as e-mail templates, still exist. Similarly to why historically they had been used for display purposes in web sites, most e-mail clients have little to no support for CSS layout properties, but the accessibility concerns remain and therefore tables for layout should be avoided whenever possible. For general web use, however, using tables for layouts is considered bad form and inaccessible. We are going to cover three commonly used patterns for laying out content: float, flexbox, and grid.
Unlike flex and grid, float is not part of the display property, but a property in and of itself.

Floated Image

Floated Navigation

Attempt a Layout Using Floats
Getting the text around the image worked really well, as it is what float was designed to do. The rest of the layout had some issues, however. The padding and combination of a set height and width would more than likely make longer content expand out of its container in the far-left bar. Also of concern, 100% is not easily divisible by 3, so depending on how the browser decides to calculate the width of each nav element, content could be pushed around in ways it was not supposed to. Lastly the background on the columns just doesn’t line up.
For all of these reasons, this would be a bug-prone and hard-to-maintain layout. To achieve this layout, and have the UI be fluid, the use of tables, the CSS display: table property, and/or JavaScript would have been required before flexbox and grid were introduced.
Multiple columns where the background color is to align no matter the content within them
Centering content vertically
Today we have flexbox. Solving those two problems is where flex really shines. Flexbox also allows for dynamically determining the width of the column based on the amount of content. Using display: flex is particularly useful when creating a layout that necessitates control over the spacing of elements across a container.
Dissecting the preceding layout, we apply flex to three areas of the layout, the three content columns, the navigation, and the far-left column itself. For both the far-left column and in the navigation, flex is used in order to distribute the content across their container vertically and horizontally, respectively.

Flexbox Main Axis
nowrap – This is the default. All items will be placed on one line following the main axis and cause overflow if necessary.
wrap – Items will wrap from top to bottom.
wrap-reverse – Items will wrap from bottom to top.

Wrap
By adding to row, column, or wrap, we can alter the sequence in which elements are displayed. If we want to move a specific element in the sequence individually, we can use order. The property takes an integer, by default 0. If an element is assigned a 1, and all others are set to the default 0, it will appear at the end. If assigned a -1, the element will appear at the beginning. The order is therefore based on the sequence provided and then weighted based on values assigned to each element using the order property.
To determine the position of each element across the main axis, we use justify-content on the container. Possible values are as follows:






Flex-start, middle, and flex-end have some advantages over inline-block. For many use cases, the use of display: inline-block in conjunction with text-align can achieve the same result; however, inline-block has some intricacies regarding spacing. Even when margins are set to 0, a small gap will appear between elements. A layout where the sum of the elements equals 100% of the width of the container therefore becomes challenging to create. Let’s look at the code and its output (Listings 4-22 and 4-23 and Figure 4-24).
Example 1

Inline-Block Gap
Notice the gap between the inline-block elements. There is no margin on the list element. Flexed items do not suffer from this unintended behavior.
The cross-axis is perpendicular to the main axis. Looking back at our original flexbox example (Figure 4-24), we have three columns of content, the left number section, the middle content section, and the right aside. For each of them to have the same length, denoted by their respective background colors aligning at the bottom, we can use the align-content property. The values are as follows:

For the purposes of columns, this behavior allows flexed elements to grow in size, similarly to a table row, so that all the elements included will have the same height as the latest in the array.




The preceding properties are set on the container and will apply to all elements within. To manipulate a single element and make it behave differently from the others, we can use align-self. Its values are the same as those available for align-items, listed earlier.
Flex-basis allows for setting a base width elements should start at. Their content will determine whether they need to grow or shrink to fit the space available in the container.
To ensure that content fills 100% of the available space in the container, a flex-grow property can be applied. By default set to 0, it specifies the growth factor of the flexed item. This value is ratio based. If all siblings of the container have the same value, then they will all grow by the same amount so that the sum of the elements equals 100% of the available width (or height if flex-direction is set to column). Otherwise it will be distributed according to the ratio defined on each flexed element.
Flex-shrink works similarly to flex-grow but for shrinking content to prevent overflow. By default set to 1, it can be set to 0 and used in conjunction with flex-basis to ensure a flexed element has a fixed width (or height if flex-direction is set to column).
Because of its ability to dynamically deal with the space provided, display-flex makes generating fluid designs and aligning content easier than ever before, without resorting to the use of tables for display purposes but it is very one directional. Grid, however, brings in the second dimension.
Grid-template-columns defines four columns. The first three of equal width and the last of 300px. The “fr” unit used here represents a fraction of leftover space as a ratio.7 The last column will be given a width of 300 pixels; the other three will receive equal amounts of leftover space as their width.
Grid-template-rows defines three rows, the first with height and last with height of 46 and 36 pixels, respectively. The middle row, set to auto, will adjust its height to accommodate its content.

Grid Template Areas

Grid Output
Each UI element is set to its named grid-area. The advantage is the naming can follow the purpose of the container being positioned, making the code easy to read and then maintain. Furthermore, when repositioning elements for responsiveness, the only property that needs to be updated is the grid-template-areas .

Grid Rows and Columns
Grid-row and grid-column can be defined using a single integer (grid-row: 1) or two integers separated by a / (grid-row: 1/3). When only one integer is used, the section will start that the line specified and span one column or row such as for the main element in the example earlier. When two integers separated by a / are used, the section will start at the line specified by the first integer and end at the line specified by the second, as seen on the footer’s grid-column value.

Justify-Items Values
The same values can be used to change the alignment of a specific cell using the justify-self property on the specific section.

Vertical Alignment Using Align-Items
Similarly to justify-self, the same values can be used with the align-self property on an individual section to allow for a cell to behave differently than the default set on the container.
Just like flexbox, grid also has a justify-content and an align-items property. They have the same values and work in the same fashion as for flexbox. They position grid cells horizontally and vertically within the container. This can be very useful when the grid itself is smaller than the grid container.
To add space between the cells, grid-gap can be used. This will determine how much space is between each row and/or column. Individually, they can be set using grid-row-gap and grid-column-gap, respectively. For example, grid-gap: 5px 2rem would set a gap of 5 pixels between each row and a gap of 2 rems between each column.
By row with row value – Fills in rows and adds new rows as necessary
By column, using the column value – Fills in columns and adds new columns as necessary
To have the grid fill in any gaps that may exist, dense may be added to both the row and column values, grid-auto-flow: column dense.
Grid is now much more widely supported across evergreen browsers but has some compatibility issues in older browsers such as Internet Explorer 11 which currently has an incomplete implementation of the specification.
Accessibility When Using Flexbox or Grid
Grid and flexbox give the ability to reposition and reorder content at will simply by changing just one or two properties regardless of the sequence in the HTML. This can become problematic for accessibility.
The Web Content Accessibility Guidelines states
When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined. (Level A) Criterion 1.3.2 Meaningful Sequence (Level A)8
When changing the order of elements using CSS, it is important to make sure the programmatic sequence still makes sense.
At the core of responsive design implementation is the media query.
“Media Queries allow authors to test and query values or features of the user agent or display device, independent of the document being rendered. They are used in the CSS @media rule to conditionally apply styles to a document, and in various other contexts and languages, such as HTML and JavaScript.”
—Media Queries Level 49
where the styles are applied when the viewport widths are between 500 and 700 pixels.

Responsive Design
Notice that in Listing 4-33, it takes very little CSS to readjust the layout for desktop users. This is because base styles are already applied and do not have to be duplicated. We also see here the advantages of named areas for grid layout and the ease of organizing them for the correct layout.
Elements are subject to the box model, which dictates how its width, padding, margin, and border will behave. When put together, the elements form a layout. There are as many ways to approach a layout as there are layouts to be created, but each technique has its own strengths and weaknesses. We have looked at float, flexbox, and grid as well as media queries for responsive layouts.
In the next chapter, we will look at scenarios where CSS doesn’t seem to work as expected, with a special focus on differences between browsers.
When writing CSS, it doesn’t take very long for most developers to realize that the same code, when run in different browsers or even on the same browser but on a different device, just doesn’t behave in the same way. This chapter covers browser differences and techniques to handle cross-browser compatibility.
Browser | Layout Engine | JavaScript Engine |
|---|---|---|
Chrome | Blink, WebKit | V8 |
Firefox | Gecko, Quantum | SpiderMonkey |
Internet Explorer | Trident | Chakra, JScript |
Microsoft Edge | EdgeHTML, WebKit (on IOS), Blink (on Android) – switching to a Chromium platform1 | Chakra |
Opera | Blink (Chromium) | Chrome V8 |
Safari | WebKit | Nitro |
The layout engine is responsible for how the page should look. It determines, based on the CSS, how the view should be laid out, painted, and animated. See Chapter 1 for rendering details. Furthermore, many are available as open source and are maintained by different groups and agencies, allowing for differences in the implementation and status of any given specification.
![]() |
Looking over time, it is clear that using this property would yield different results across browsers. Furthermore, browsers include CSS defaults, and these also have slight differences.

Default Styles

Firefox

Safari
Notice the default typeface in the textarea; in Firefox it is a monospaced font vs. a sans-serif in Safari. The alignment also differs slightly. On Firefox, the textarea is aligned to the baseline of the text, while in Safari it hovers slightly above the baseline. These subtle differences can be infuriating when trying to get a design to look and behave the same across browsers and versions.
A robust technique for counteracting this is to manually set defaults so that all browsers are running off the same base styles. Although this does not address compatibility differences, it will address subtle unintended behavior differences such as the one outlined earlier.
Reference | Link |
|---|---|
Project Site | |
Style sheet |
Reference | Link |
|---|---|
Project Site | |
GitHub Repository | |
NPM | |
CDN | |
Style sheet | https://necolas.github.io/normalize.css/latest/normalize.css |
Although normalizing base styles addresses differences in CSS defaults, it does not address differences in implementation or support.
Normalize and reset are not being endorsed in any way, and the quality and relevance of any package can be subject to rapid change. Please research any dependencies you intend to use.
Cross-browser compatibility, making sure the UI looks the same across multiple browsers, ranks right up there among the hardest things to do in CSS. There are multiple ways to tackle this problem and they are often used in combination with one another.
Prefix | Browsers |
|---|---|
-webkit- | WebKit-based browsers (Chrome, Safari, etc.) |
-moz- | Firefox |
-o- | Pre-WebKit versions of Opera |
-ms- | Internet Explorer and Microsoft Edge |

Grid in Firefox

Grid Without Vendor Prefixes in IE

Grid with Vendor Prefixes in IE
A better solution to vendor prefixes when a browser does not support a property is to create a fallback . When a browser encounters a property or value it does not support, it will ignore it, and therefore, the previously set value will be maintained. If the element does not have a previously set value or does not inherit a value, the default will be used.

Fallback to Background Image

Cross-Fade
Finally, browsers that support the final version will display the specification-defined cross-fade.
The @supports at-rule allows for checking if a particular property and value pair is supported or not, allowing the user experience to be customized accordingly. This feature is generally well supported apart from IE. The expression @supports(property:value {} returns true when the property is supported, while @supports not (property:value){} is true for when it is not. Styles within the selector are only applied if the selector returns true. These can be conjoined with either the and or or operators to create new expressions. Generally it is prefered to use @supports for progressive enhancement of newer features, while fallbacks can be used to provide backwards-compatibility with older browsers.

Theming
By setting variables, default styles on elements, and creating default container classes, like the .card class in Listing 5-9, a theme can be created. Properties one might include in as part of the theme are things that revolve around look and feel such as color, typography, borders, padding, and so on. From there, creating views becomes easier because the primary concern remaining is layout. By setting up default theming, even in a component-based architecture, the look and feel, or brand, can be kept consistent. Furthermore, updating the theme can be as simple as changing the custom property values.
This chapter covered browser differences and techniques to standardize CSS across them. Techniques for dealing with differences in CSS support in different browsers were also discussed. Finally, theming was addressed. The next chapter will look at supporting user interaction using transitions and animations.
When we think of HTML and CSS, we often think “static.” JavaScript much more commonly comes to mind when thinking of interactions or animations. CSS, however, includes several features that allow for manipulation of elements as a result of user interaction. In this chapter, we will look at how we can respond to user interaction using CSS and how to support those interactions using animations and transitions.
One of the most commonly used ways of responding to user interaction in CSS is by using the pseudo elements :hover, :focus, and :active.
The pseudo element :hover matches when an element is interacted with using a pointing device – most commonly, when the user hovers with the mouse over the element1 such as a link or button. This can be used to give the user a visual indication that the element can be interacted with.
The :focus pseudo class is triggered when an element receives focus, such as when tabbed to using the keyboard or being clicked. Often overlooked, focus is important as it gives a visual indicator to the user as to which element they are currently interacting with or about to interact with. Changing border styles on an input field when it is in focus will tell the user which field they are about to type in, which is incredibly helpful in orienting the user as to where in the page they are currently at. Not all elements can natively receive focus. Outside of some exceptions such as the video element, buttons, anchor tags, and form items like input and select are the only elements that can receive focus without adding a tabindex attribute to the element.
The :active pseudo element triggers when an element is being activated such as a button being pressed or a link being clicked. A change in button style, such as removing a shadow when a physical button is being pressed, reflects real-world expectations of the action of depressing a physical button. Although a user might not be able to articulate why, small interactions such as this will make the interaction feel more natural to the user.
Most browsers will have default behavior around elements when focus is applied. If squashing the default behavior, some visual indication of focus needs to be reapplied so that a user can visually distinguish the element that is in focus from other elements.2 Furthermore, focus should not change the context, functionality, meaning, or operability.3,4
From the interaction, a response can then be set such as changing the element’s looks, size, or even position. Adding a transition to a visual change, if the animation is informative of the change about to take place, will help the user understand the change being applied. When expanding an accordion for example, animating the opening of the accordion section will help the user stay oriented to where they are in the page especially since content below will be moved to a different location, possibly outside the viewport.
When responding to a CSS-triggered event such as hover, focus, or active, it is much more maintainable to keep the associated transition in CSS as well rather than to use JavaScript. This allows both the trigger and the reaction to stay together and for their association to remain clear and evident. This helps keep visual instructions within the style sheets.

Transform Matrix
Function | Description | Dimension |
|---|---|---|
matrix() | Shorthand for matrix3d(). See the earlier description. Takes six parameters. | 2D |
matrix3d() | Linear transformation and translation over three dimensions. See the earlier matrix description. Takes 16 values. | 3D |
translate(tx, ty) | Translation by the vector, where x is the first translation value and y is the second. To individually manipulate the x- or y-axis, translateX(tx) and translateY(ty) can be used. | 2D |
translate3d(tx, ty, tz) | Same as translate() but on three dimensions. TranslateZ(tz) can be used to translate the element on the z index. This tz value cannot be a percentage, it must be a length. | 3D |
scale(sx, sy) | Scaling vector, where x scales the height and y scales the width and initial value is 1. To scale the height or width independently, scaleX(sx) and scaleY(sy) can be used. | 2D |
scale3d() | Same as scale() but on three dimensions. ScaleZ(tz) can be used to translate the element on the z index. | 3D |
rotate(∠) | Rotates the element from the point of transform-origin by the angle provided. | 2D |
rotate3d(x, y, z, a) | Rotates an element around a fixed axis in three-dimensional space, where x, y, and z describe the axis of rotation and a describes the angle of rotation. | 3D |
skew(∠x, ∠y) | Distorts an element by the provided angle on the x- and y-axes. To skew the element by axis, skewX(∠x) and skewY(∠y) can be used. | 2D |
perspective(z) | Gives perspective to three-dimensional elements where 0 is the default. When z is increased, the element becomes larger, and when it is decreased, the element shrinks. | 3D |
When the styles for an element are changed, transitions allow for the shift from initial state to the new state to be visually smooth. As its name implies, the transition property controls the visual aspect of how values change from one state to another over time.
Value Name | Behavior | Initial Value |
|---|---|---|
transition -property | Defines the property the transition will affect | all |
transition -duration | Defines how long the transition will take to complete | 0s |
transition -timing -function | Defines the acceleration curb for how the values get applied during the transition | ease |
transition -delay | Defines the delay period before the transition starts | 0s |

Animation Code Output Over Time
Transitions can be a great way to help guide the user through an application by enhancing the relationship between elements when an action is performed. To achieve this goal, however, the animation should be informative, focused, and expressive.6 Animations should last between 200 and 500 milliseconds with smaller, less complex animation, or when on a smaller screen, in the 200–300 millisecond range.7

Animation Code Output Over Time
Background-color , border-color, color, border radius, and scale are only defined at 0 and 100% and are therefore interpolated. The element will rotate to the specified degree at each percent. Even though 100% does not specify a rotation degree, at the end of the animation, the element will set its rotation to what ever is set on the element, or 0.
Value Name | Behavior | Initial Value |
|---|---|---|
animation-name | Defines the keyframe at-rule the animation will use | none |
animation-duration | Defines how long the animation will take to complete | 0s |
animation-timing -function | Defines the acceleration curb for how the values get applied during the animation | ease |
animation-delay | Defines the delay period before the animation starts | 0s |
animation-iteration -count | Defines the number of times the animation will play | 1 |
animation-direction | Defines whether the animation should play forward, backward, or toggle forward and backward | normal |
animation-fill-mode | Defines how styles are applied to the target before and after animation completes | none |
Another property that can be used with animation is animation-play-state which allows the developer to pause and start an animation. When resumed, the animation will restart where it was paused rather than the beginning of the sequence. The default value for animation-play-state is running. It needs to be defined individually as its own property, however, and is not part of the animation shorthand described in Listing 6-4. Giving the user the ability to pause an animation, especially if the animation is not necessary to understanding the content or the state of the application, can radically improve the usability of the application. When considering an auto-advancing carousel, for example, adding the ability to pause the auto-incrementation of panels will allow the user to control the speed at which they view the content.
When the element is “closed” or hidden, first a class with the exit animation is added. Once the animation ends, the animationend event listener is triggered and only then can the display property value be changed to none. The same can be achieved with transitions using the transitionend event listener. Adding and removing classes, rather than handling the close animation in JavaScript, helps keep display-related logic in the CSS style sheet, increasing maintainability and keeping separation of concerns.
Whether creating a transition or an animation, a common value to define is the timing function. It determines the speed at which values change over the time it takes for the animation to complete. Timing can help make the animation feel more natural and reflect physical world interactions more closely. When animating a bouncing ball, one would expect the ball to accelerate after hitting the ground. If the animation was linear, and the ball always moved at the same speed, the animation would seem off. There are two specific types of timing functions available.
Easing functions define smooth transitions based on the Bézier curve, named after the French engineer Pierre Bézier. The curve is parametric,8 and the cubic variant is defined by four points: P0, P1, P2, and P3. P0 and P3 define the beginning and end of the curve, respectively. P1 and P2 represent the control points which give the curve its shape. Each point is defined by (x, y) coordinates.
The CSS cubic-bezier predefines P0 and P3 at fixed points of (0, 0) and (1, 1) representing the initial and final states of the animation. Left to be defined are P1 and P2 whose x values need to remain in a [0, 1] range, while the y values may exist outside of the bounding box.
The CSS function looks as follows: cubic-bezier(x1, y1, x2, y2).
Name | Formula | Curve |
|---|---|---|
linear | cubic-bezier(0.0, 0.0, 1.0, 1.0) |
|
ease | cubic-bezier(0.25, 0.1, 0.25, 1.0) |
|
ease-in | cubic-bezier(0.42, 0.0, 1.0, 1.0) |
|
ease-in-out | cubic-bezier(0.42, 0.0, 0.58, 1.0) |
|
ease-out | cubic-bezier(0.42, 0.0, 0.58, 1.0) |
|
To create bouncing effects, either or both y values should be set outside the [0, 1] range . For this, a custom function needs to be written such as in the following function: cubic-bezier(0, 0.71, 0.64, 1.23).

Sample Bounce Curve
Name | Function | Steps |
|---|---|---|
step-start | steps(1, start) |
|
step-end | steps(1, end) |
|
jump-start | steps(3, jump-start) |
|
jump-end | steps(3, jump-end) |
|
jump-none | step(3, jump-none) |
|
jump-both | step(3, jump-both) |
|
When applied, the code and output would be as in Listings 6-9 and 6-10 and Figure 6-5.

Jump-Start Output
Notice how the animation is already partially started. Because jump-start is used, the initial state of width 0 and color white is skipped and the animation starts with the container at a width of 20% of final state width. If jump-end had been used, the container would have started at a width of 0, but never reached a width of 100%. The container would only have a width of 80% when the animation ended.
When considering timing, it is important to make sure that the content does not flash more than three times in a one-second period. This is to prevent the induction of seizures due to photosensitivity in users.11
When considering the effects of animations on performance, not all animations are created equal. Animations that cause layout changes or the view to be repainted are particularly taxing.12 For example, changes to height, width, or position affect layout and cause elements on the page to be repositioned. Properties that cause the view to repaint include color, background-position, and visibility. Animations affecting layout and paint will be less performant than those that don’t.
Generally, for best performance, using the transform property is the best way to go as it can lean on the GPU. Whenever possible, it is best to try and stick to animation using opacity, translate, rotate, and scale.13
Sparse use – It should only be used when it is actually needed. The browser already attempts to optimize everything. Unnecessary use will actually slow down the page.
Only on when needed – Should be turned on before the animation will trigger and then turned off again to free up browser resources being used for optimization.
Enough time – Optimization is time-consuming; therefore, will-change needs to be applied to the element with enough time to take effect before the animation is set to begin.14
This chapter covered transition, animations, and their differences as well as the functions used to change the timing of how animations and transforms are applied. Also covered were performance and accessibility considerations when dealing with animations. Chapter 7 will go over preprocessors and their architecture considerations and benefits.
For CSS there are several preprocessors available. These will take data, written in their own particular syntax, and then output CSS for the browser to consume. The benefit of these includes access to functionality such as color-editing functions or nesting rules that are not yet available in CSS. They also gave us access to CSS variables before they were supported by the language itself. Some of the most popular processors include Sass, Less, and Stylus.
Examples in this chapter will use SCSS.1 These techniques are available using other preprocessors; however, feature availability and syntax will vary based on the preprocessor used.
The way in which code is organized and architected can be very different when using preprocessors than when using pure CSS because of added functionality, such as mixins and the ability to extend classes. The ability to compute values in ways simply not available outside of preprocessors today brings the ability to write DRY semantic code. It can be defined in just one place and then reused throughout the style sheet not much differently than some of the object-oriented principles used in other programming languages.
The downside of using preprocessors is that they add a layer of complexity to the application that does not exist when using pure CSS. Even though some preprocessors, such as Less,2 can be run in the browser directly, it is not recommended for production use because it is less performant and reliable than plain CSS. When using preprocessors, we therefore need some sort of build step in order to compile the code into CSS.
Debugging can also be a challenge, especially when using some of the more complex or advanced features of the code. This stems from the CSS being generated not matching one-to-one with the code being written. For example, the properties and attributes being added to a class could come from a mixin (more about mixins later in this chapter) rather than part of the ruleset. The CSS being applied is the output, not the mixin itself, so tracking back to which mixin created the output can be difficult. Sourcemaps can help with this. A sourcemap is a file that can be generated with the CSS which links the output back to the code that generated it. But again, this needs to be set up specifically as part of the build process.
So first, before even choosing the processor to be used, the question of whether the added complexity is necessary should be asked.
As much as it makes it very easy to know that the styles set on the list item will only be applied to the navigation list items, nesting makes it really easy to create overly specific rules. In the preceding example, nesting the list item inside of the unordered list is superfluous and does not add any value. Having it nested under the navigation only, one level higher than its current location, would be sufficient.
The ampersand refers back to the parent element, so the hover is placed on the anchor tag when it is a link, or a visited link. Nesting here makes it very clear that the hover, focus, and active selectors are all children of a:link and a:visited.
Without nesting, it is more difficult to tell at a glance that links and visited links also have styles for hover, focus, and active states. Furthermore, the nested code is more concise and does not repeat the root elements, decreasing the chance of typos or errors.
Care when nesting must be taken in order not to create rules that are overly specific. This happens when elements are nested too deep. However, it can help with code legibility.
Variables, although available today in CSS as discussed in Chapter 2, were first made available through the use of preprocessors. The CSS version (custom properties), although influenced by preprocessor variables, does have some advantages over the preprocessor variables. Custom properties can be accessed and changed via JavaScript, while preprocessor variables cannot. In creating the CSS output, preprocessor variables do not remain variables; they are replaced by their assigned value. CSS custom properties, however, stay variables and can be manipulated at any time, including during runtime.
![]() |
The colors can be set to semantic names based upon what their usage will be and then manipulated using color functions when the color value or saturation needs to be altered (Listing 7-1).
By using color transformation functions and variables, not only don’t we have to remember the exact values for each of the colors and any variations we may have in use, but we also increase our ability to keep our theme consistent. Furthermore, if the colors were to change, this could be done in one place. The eventuality of a color changing is why color names should be based upon their usage rather than their actual color. If the variable name was $pink, for example, and the accent color was changed to purple, we would now have to either find the variable name everywhere and update it, or we would have a variable name that does not represent the color that is assigned to it. Situations like this make maintainability very difficult and code confusing. Selecting semantic variable names is incredibly important to the maintainability of the code.
Mixins allow developers to create sets of properties and values that can easily be reused throughout the application.

Mixins Output

Informational Boxes
Even though the padding could have been included in the mixin, it is separated out into its own class because when a mixin is added, it does its computation and outputs all the code each time; therefore, mixins are not a good use case for static information. Static styles are simply programmatically being copied over into each class increasing the size of the CSS and therefore upload time. Classes, defaults on elements, or the use of the @extend at-rule are much better options for static styles.
Extend, unlike mixins, prevents the duplication of code in the resulting CSS. While a mixin copies the declaration block for each selector it is included within, extend creates a single declaration block and consolidates the selectors.
The advantage of this methodology is in creating base classes for basic styles toward which semantically named classes will be pointed. The code is neither duplicated nor copied and prevents the use of numerous nonsemantic classes on an HTML element. For code maintainability it also means that the style of the elements is controlled in the CSS. If the style coming from extending another rule is no longer wanted, we need only to remove the @extend. By simply adding the class name to the HTML instead of using @extend , we would have had to edit the HTML in order to change look and feel. By using @extend instead of adding the same class name to a multitude of elements, we continue to maintain a separation of concerns. Our elements can have class names that match their purpose, rather than how they display, and we handle the styling via the CSS.

Informational Boxes – Revisited
Notice the message class now has multiple other selectors as well, but was not duplicated in the output.
Imports allow the user to create partial files in which variables, mixins, and reusable code can be placed. Sass imports work similarly to CSS imports in that it copies the SCSS they contain to the style sheet they are being imported by. They must therefore be used with caution. It is very easy to bloat code by repeatedly importing an entire theme, for example, into each component. Sharing mixins and variables, since they are not copied, but produce an output within a style, is a perfect application of the use of @import, because unlike classes, they do not get copied.
Creating import files to have information accessible from anywhere in the application becomes very interesting when dealing with components because more often than not, such as when using Angular out of the box or creating component in JavaScript and Shadow DOM, the CSS is scoped and therefore in a separate file or area of the application than the rest of the CSS. Adding variables and mixins to a partial – a file to be imported into other files that do not have a use on its own – helps keep the code DRY.
Partials are sometimes denoted by having an underscore at the beginning of their name to separate them from style sheets.
In this chapter we looked at a very small subset of functionality brought to use via the use of preprocessors. We looked at mixins, imports, extends, color function, and variable and how they impact how we might organize and structure our application’s CSS. In the next chapter, we will look at how JavaScript can interact with our CSS especially in the context of modern frameworks.
In a real-world application, your CSS does not function in isolation. This chapter covers some of the important considerations of a modern front-end web application, including how your choice of CSS or JavaScript frameworks may impact your application styles.
OK, yes this is a CSS book, so why are we suddenly talking about JavaScript? The reality is that a significant amount of front-end development is done using some sort of framework and/or UI library, many of which rely on JavaScript. Furthermore, JavaScript is often used to manipulate CSS as a result of state change or user interaction.

Most Popular Programming, Scripting, and Markup Languages in 2019 According to Stack Overflow

Most Popular Web Frameworks in 2019 According to Stack Overflow
Property and Methods | What It Does | Effects on Specificity |
|---|---|---|
element.ClassList • add() • remove() • replace() • toggle() • item() • contains() | Reads and manipulates the classes attached to a particular element. | Because styles are being applied by referencing classes, cascading and inheritance will not be significantly impacted. |
element.style Examples: elem.style = "color: blue, font-size: 12px" Or elem.setAttribute("style", "color:blue; font-size: 12px" Or elem.style.color = "blue" | Adds styles inline on the element. | Very difficult to override because they are inline. If the element already has inline styles, they will be overridden by what is being applied by the JavaScript. |
Both a huge advantage and risk, JavaScript has the ability to easily override anything declared by CSS but does not have to.
If we don’t want to mix CSS rules in our JavaScripts, the methods given to us via classList allow for adding and removing classes to the element without ever touching the CSS itself. This brings with it the huge benefit of styling definition staying in CSS files and not being split between JS and CSS files.
Affecting styles or the DOM itself brings the advantage that the specificity of CSS being applied matters very little since it will be overridden. Via this technique, regardless of any other context, as a developer, we can dictate that if a user performs a certain action, or a particular state is achieved, a particular set of styles will be applied regardless of other styling, or context. This technique is often used to show and hide dialog boxes, for example, notification messages. The advantage of this technique is that the style changes can stay with its trigger, or action, and regardless of context, the styles being applied will not be overridden by preexisting CSS.

Animation Listener Output
The button triggers the JavaScript to disable the button and add a class of rotate which makes the image spin once. Because on page load, we set up the JavaScript to listen for animation’s ending, once the animation is terminated, we can reset the page. The button is reenabled, and the rotate class is removed. In this example, even though we are manipulating the CSS with JavaScript, the CSS classes are still defined and maintained in the CSS file, and therefore, inheritance and cascading are not altered or affected.
When using component-based architecture, it becomes very important not only to theme your application to your application’s brand/specification, but also the UI library itself. Each library will have various levels of themability and intricacies in terms of ease, as well as what is actually possible to style. When selecting a UI library, understanding the customizability and how themable a library or framework is can save you from a lot of headaches down the road. Encapsulation – restricting the component CSS to the component itself – allows for writing CSS that only applies to a particular component and not to the rest of the application. If a UI library’s components has very strict encapsulation and few theming options, it will be incredibly difficult to style.
There are many different libraries and frameworks that create or use components. Each has slightly different implementations. We are not going to look at all of them. When looking at how modern-day JavaScript frameworks create and interact with components, most either use web components or emulate them. Worth noting is that especially if the component is emulated, it will behave slightly differently than what I will be describing in the following text. Angular has an emulated model and continues to support an equivalent shadow piercing combinator (::ng-deep) but can be set to use web components instead, or set to have no encapsulation at all. In React, it depends on how the CSS was set up in the project. The options also run the gamut. Understanding exactly how much encapsulation your framework provides will help make better decisions as to how to structure your CSS.
By definition, libraries are collections of declarations that the application will use. A framework is an abstraction and provides basic functionality, or a skeleton for the application. A framework may contain one or more libraries.
UI libraries such as jQuery UI3 or Angular Material4 provide a series of components or widgets that can be added to an application. They come ready made with styles and functionality. To customize their looks, they need to be themed. Theming can either be done via tools, which spit out the necessary CSS such as themerollers, or be done more manually following guidelines. Either way, the themability and therefore customizability of the said library will vary. The variability is a direct result of how the components are constructed and how easy the author has made it for element to be customized. Further customization beyond what the theme will allow can often prove quite difficult and result in using extremely specific selectors, such as the use of !important. It is therefore very important, when considering libraries, to look into what theming capabilities it has as well as how easy it is going to be to customize so its elements can match your application.
The architecture of the library itself may also affect how it is used and in some cases may provide for multiple approaches. Bootstrap5 is interesting because its structure allows for two radically different implementations, each with their downfalls and benefits.
The first, and probably most common implementation, is importing both the CSS and JavaScript directly in the page, from a local source, via CDN, or using a package manager such as NPM, NuGet, or RubyGems. The framework in its entirety is available and being applied. This means that a number of classes already have styles added to them and are ready to use on the web site. Some components, like modals, have functionality that relies on JavaScript associated with them. These will also be readily available.
Naming is no longer semantic.
The styles are essentially being controlled by the HTML.
Everything is imported in its entirety even if it’s not being used.

Bootstrap Output
If the btn or the btn-warning class is updated, all buttons that include this generic class across the application will be updated, whether a call to action or not. The class gives no indication of what it might be used for, or worse, such as in this example, it is being used because of its color, rather than for a warning. The only other option is to go find all the call to actions in the application and update their class name.
Rather than the style sheet controlling how elements look, the style is now tightly bound to the HTML. This is also true of the layout, wanting to change the aside to taking a third of the page rather than a quarter would involve going to each page, and updating the HTML.
This approach is not going to be as easy to get up and running with. Since it uses SCSS, it will require the ability to process SCSS into CSS. Knowledge of SCSS and of what is available for mixins in the framework is also required. Once past the setup and learning curve, however, we get some great benefits. Because we are now assigning the styles to classes via @include and @extend instead of applying generic class names to elements in the HTML, we know our elements will look the same on all the pages. Elements across the entire application can also be updated from one place rather than searching the site for all instances of a particular concept. Lastly, only the parts of Bootstrap I am using are being imported which reduces page weight.
Whenever trying to theme a component library or tweaking the styles from a CSS framework, specificity can sometimes be challenging to wrangle, as the library or framework may already be using selectors that are quite specific; therefore, it may be tempting to use !important. Here be Dragons!
Although there are situations where there truly is no other choice, or important truly is the lesser evil, these instances are few and far between.
The use of !important increases the precedence of a declaration making it very difficult to overide or to include in a normal cascade. No longer can you target a more specific selector to change the style of the element. You now need another more specific important. This vicious cycle makes code incredibly difficult to debug and maintain and even harder to expand.
So when overriding styles, take care that if you do use !important, it is done sparingly and with intent rather than frustration.
Knowing the architecture of the library selected, and its capabilities, will help make informed decisions as to how to structure your code for better long-term maintainability and performance.

Shadow DOM
Components created using this technique are fully encapsulated, and the author has complete control as to what the consumer will be able to style vs. not because everything within the Shadow DOM is akin to a black box from the perspective of the parent page or component. For a short while, we were able to ignore the encapsulation by using shadow piercing combinators such as >>> or ::deep, but these have been deprecated or removed from most browsers in favor of the upcoming CSS Shadow Parts 6 specification currently being refined. Even after this new specification is implemented, however, the author of the component will still control what users will be able to fiddle with; specificity, !important, and shadow piercing combinations will continue to fail to edit styles that the component author did not open to being altered.

Web Component Output

Node Tree Including Slots
Some styles do bleed into the component. Elements in the slot or classes assigned directly to the host (the custom tag) are subject to both the component styles and page styles. Notice the paragraph tags in Figure 8-6; they take styles from both the parent and the component as described below.
:host is more specific than the browser defaults, and in turn, .dark is more specific than :host.
So why did the header also become monospace, when the buttons were unaffected? By adding a font-family of sans-serif to .dark, we essentially set monospace as the default font-family on the :host, that is as far as we can pierce into the component. The buttons have their own typeface specified inside the component CSS which is overriding the :host default and were therefore unaffected. Further attempts to reach into elements inside the component via specificity, such as doing .dark button { ... } even if no styles are set for that property, will not work.
The reason we were able to style the paragraph tags, and will continue to be able to do whatever we want with them, is because they are in a slot. The contents of the slot are actually being controlled by the parent. Looking at the DOM tree, one can see that the slot content lives outside of the shadow tree as seen in Figure 8-7.
The slot inside the shadow tree is nothing more than a placeholder. The content of the slot is in a sibling node of the shadow-root, and not in the shadow-tree itself. It is therefore not encapsulated like the rest of the component and can be styled like any other element on the page. Because it is a child of the host, however, styles assigned to the shadow host will cascade normally to those elements.
When creating an application that uses components, it is easy to start thinking only in terms of small reusable items and to lose sight of the greater picture. Consistency of typeface, colors, button styling, and so on across the application is something I think we can all agree is a good thing. However, if we are rewriting those styles in every component, we are setting ourselves up for discrepancies and a maintainability nightmare. How tightly encapsulated the components are affects the approach.
Once these two files are set up, components should only need to worry about layout and exceptions, things that are specific to that component and nothing else. A great gauge of a style or set of styles belonging in one of these files would be if you find yourself copying and pasting the same thing over and over again. If this is the case, it is time to consider whether these styles need to be imported or set as defaults somewhere.
If the components are tightly encapsulated and a theme file that cascades styling throughout the application is not possible, variables that can be imported become really critical. Importing an entire theme file into every component will just bloat the application because even though maintained in one place, it will essentially be copied in each component. Possibilities here include the use of preprocessors in order to create mixins, or breaking the theme file up into smaller chunks, buttons, tables, links, and so on so that only the needed portions get imported.
In this chapter we covered the common interaction mechanisms between CSS and JS to show how JS interacts with (and sometimes interferes with) our style sheets. We also looked at how the architecture of the libraries we use can affect how we structure our code. In the next chapter we will dive into various architectural best practices along with specific CSS architectural patterns, showing their strengths and weaknesses.
In order to produce an architectural approach for your CSS, it’s important to clearly establish your goals and then settle on a general methodology. In this chapter we propose goals, guidelines, and methodologies for your consideration.
When working on web development, it’s important to build a consistent approach that is sensitive to external factors and business demands. There are also considerations for our development team to be sure we can rapidly produce the desired results, and that the web site continues to look and work as expected as content is added and changed over time.
Since both element and component are technical words with a specific meaning in the context of the Web, we will use the word widget to refer to reusable building blocks made up of HTML+CSS.
While many frameworks will implement a widget with either a native or framework-based web component, this is neither a requirement nor a goal of good CSS architecture.
We should be able to design a modularized widget with HTML and CSS that stands on its own without depending on external CSS.
When we drop a widget into our web site, we want it to take on the overall look and feel of our site and brand.
It shouldn’t be necessary to artificially boost specificity or precedence (such as by adding an ID selector or !important annotation) to style a widget as intended.
General content edits in an HTML document (inserting images, adding paragraphs, adjusting rows/columns in a table, adding items to a list, etc.) should not necessitate updates to the style sheets.
It should be easy for someone new to join our team and figure out how to make adjustments to the design of our pages and widgets.
Our styles should be easy to read, understand, and troubleshoot.
The design of our web sites should be easy to re-theme without impacting the layout and functionality of the site.
It should be straightforward to make design changes (such as colors and typography) without needing to use search-and-replace on the CSS properties across our style sheets.
We should be able to adjust the styles of Widget-A and Widget-B independently, without worrying about changes to one impacting the other unintentionally.
User’s wishes with regard to style should be respected to improve their experience and overall accessibility.
In his 2012 blog post about CSS architecture,1 Philip Walton issued an important reminder: “stripping [sic] all presentational code from your HTML doesn’t fulfill the goal if your CSS requires an intimate knowledge of your HTML structure in order to work.”
Avoid class names that mirror the name of the tag they are on, attribute values, or pseudo classes (e.g., no more <button type="button" class="button"> or <input type="password" class="password">).
Avoid generic class names such as content, container, wrapper, right, and left. These names don’t provide any meaningful understanding as to their usage or intent, and they are subject to naming collisions with other style sheets on our project or with third-party libraries.
Generally, avoid using IDs as selectors.
Prefer selector specificities between [0 1 0] and [0 2 2]. Any lower and you run the risk of leaking styles into the rest of your site. Any higher and you risk overwriting inheritance or making a value unintentionally sticky. Higher specificities also risk being overly verbose and either brittle or difficult to understand. Use higher specificity selectors with purpose and care.
Avoid using !important in your style sheets and inline styles in your HTML.
Optimize your CSS architectural decisions for the developer experience, and the ease and accuracy of ongoing changes, rather than becoming overly concerned with performance of the style sheets.
Choose a consistent naming strategy.
Separate concerns.
Use child and sibling selectors only when necessary (ideally just within a widget), but avoid the use of descendant selectors as they have an unknown amount of impact.
Year | Creator | Methodology: Official Web Site |
|---|---|---|
2008 | Nicole Sullivan | |
2009 | Yandex | |
2012 | Jonathan Snook | SMACSS: http://smacss.com/ |
2015 | Harry Roberts | ITCSS3: https://itcss.io/ |
One thing the following methodologies have in common is the heavy use of classes in both the HTML and CSS. They all agree on one specific point – the only use classes serve in the HTML is for CSS or JavaScript binding.
It is important to point out that many CSS professionals mix and match their favorite parts of each of these methodologies. As always, choose what works for you and your team, but be consistent about it.
Originally presented by Nicole Sullivan at Web Directions North, Object-Oriented CSS (OOCSS) borrows concepts from Object-Oriented Design in order to provide structure to CSS. The Object in OOCSS is what we have been calling a widget and is defined by Sullivan as “a repeating visual pattern, that can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. That object can then be reused throughout a site.”4
Separate structure from skin
Separate container from content
Structure (or layout) refers to the location of elements on the page, or the function and interaction of those elements. Layout properties include those items that impact size and position of elements, such as height, width, margin, padding, and overflow.
Skin (or theme) refers to the visual aspect of the elements. Theme properties include color, border, box-shadow, font, and opacity, among others.
This approach allows theming to be applied to a wide range of elements and maintained in just a single location. In order to implement OOCSS as intended, it will be necessary to add classes to the HTML to avoid relying solely on the semantics of the HTML.
Buttons look the same regardless of location, unless the HTML specifies something else via class.
All elements with the call-to-action class will have the same look, regardless of tag or location.
Looking at the HTML for the button, I can easily find its ruleset.
Avoids artificially inflating the specificity allowing styles to be predictably overridden when necessary.
While these are general guidelines without providing too many specifics, there are no apparent problems with following these recommendations.
However, do keep in mind that CSS requires every property to have a value, so any values not provided will use defaults. Also different elements (say, <a> and <button>) have different default values for these properties and a different default appearance. So when using an approach like OOCSS and leaning on class selectors without an accompanying type selector, consider what might happen when this class is applied to a new element with different defaults.
BEM stands for Block, Element, Modifier , which summarizes the naming convention used and overall approach to organization. The “Block” in BEM refers to our concept of the reusable widget. The use of “Block” and “Element” in BEM is unfortunate as the naming overlaps similar, but not identical, HTML concepts with those same names. (To avoid confusion we’ll use the BEM concepts in italics within this chapter.)
One common question in BEM is how to decide if a given item should be a block or an element. The general guideline is that if the section of code cannot be used separately from its parent container, then it’s an element. If it can be reused independently, then it’s a block .5
One goal of BEM is to flatten the structure inside of a block so that the related CSS doesn’t necessarily have to change if the nesting of the HTML changes. In the example from Listing 9-4, we could change from using <ul> and <li> tags to using <div> tags for the nav__item and the CSS would not need to change. As this illustrates, BEM discourages using HTML elements as CSS selectors .
Another goal of BEM is predictable and consistent naming and simple maintainability. For instance, it should be very easy to perform a text search on a project to find everywhere a given class name is used, making it possible to remove unused rules from our style sheets with confidence.
One benefit of BEM is that the style sheet can be very easily broken into multiple files with a high degree of confidence, based upon block.
The use of modifiers in BEM contradicts the recommendation of OOCSS to create general-purpose and reusable styles that represent the skin or theme. This can be at least partially overcome using SCSS mixins, but is a difference worth noting. While it makes BEM very predictable, it greatly reduces the ability to compose reusable styles in favor of being very explicit .
Base – The base rules establish the defaults. Each rule should generally apply to just one element. This is a great place for normalization and base font size.
Layout – This is where we put the dimensional and positioning declarations, along with any glue we may need for our widgets to work together.
Module – These are where we find the reusable widgets in SMACSS. These modular design units such as callouts, sidebars, and login forms are defined here.
State – Most web applications reveal a large amount of state visually within the design components, which belong here. This may include such values as visible/hidden, active/inactive, hover, focus, and expanded/collapsed.
Theme – Declarations that impact the look, feel, and brand (but not the layout or functionality) are generally thematic. This is similar to the skin concept from OOCSS.
The purpose of categorizing things is not to create artificial barriers, but to better codify repeating patterns within the design.8 SMACSS does not penalize exceptions to the categorization guidelines, but simply recommends that exceptions be justified as advantageous.
Improve semantics – This means making it easier for developers to understand how and why widgets and elements are being used.
Increase orthogonality – In the SMACSS book, Snook states the goal as “Decrease reliance on specific HTML,”9 which simply means restructuring of HTML should have minimal impact on our CSS.
Trigger layout-specific changes based upon a layout class, not a page name.
Use module (widget) class names instead of HTML elements in selectors.
Avoid setting thematic defaults on common elements such as input fields, buttons, and tables.
As you’ve probably noticed, these recommendations closely mirror those we established at the beginning of the chapter, which were derived from our exploration of software architecture in Chapter 1.

ITCSS Diagram
Settings – Intended for preprocessors, this layer includes variable definitions and site-wide settings such as font and colors. Should generally not output any CSS.
Tools – Global mixins and functions for further reuse. This is primarily intended for preprocessors and should generally avoid outputting CSS.
Generic – Normalization and reset of styles, CSS variables, and box-sizing settings. From this layout on should produce CSS. This is similar to the base rules category in SMACSS.
Elements – Default styles for standard HTML elements.
Objects – Class-based selectors which define reusable framework objects such as the OOCSS Media Object.
Components – This is where our widgets come into play and the majority of styles will be in the Objects or Components layers.
Utilities – Helper classes that may need to be able to override everything earlier in the triangle. For example, show/hide classes.
Rather than defining naming or design practices, ITCSS recommends a specific structure and organization of the style rules themselves, without providing too much detail around what sorts of rules are permitted.11 As such ITCSS is fully compatible with OOCSS, SMACSS, and BEM.
One of the important roles a software architect often fills is to help establish good processes and practices for their team. It’s important that your processes fit into the larger framework of web site publishing and delivery.
Imagine you are building a web site that pulls content from a Content Management System (CMS) which is managed by the company’s marketing team. While it may be possible to modify the output produced by the CMS or to train the marketing team to add classes to certain elements, this may put too much pressure on the content team. It may be more reliable to simply write CSS that achieves the desired results with the content you receive. In this example the web development team has control over some of the HTML, but not all of it.
Most style sheet authors choose to cascade font default from the body tag as shown in Listing 9-9 rather than using the universal selector; however, both are technically possible. Because the universal selector has a specificity of zero, any rule will override it; however, since it’s set on every single element, simply resetting the font on a the <p> element will not change the font for any child elements such as anchor tags, definitions, buttons, labels, and so on. To achieve this behavior (which is the default when depending on inheritance from body), we need to either apply the change to each subelement individually or use the universal selector again.
Class names only provide meaning to page authors and developers. Your choice of class names should reflect this. Your class names do not convey any meaning whatsoever to user agents or systems and are never displayed to the human visitors to your pages.12
How big is your project/how big will it get (widgets, files, etc.)?
How big is your team/how big do you expect to grow?
Do you need to support interchangeable themes?
Do your developers have more control over the HTML or the CSS (many applications involve pulling content, data, or styles from external sources)?
One way to help ensure cross-browser compatibility, accessibility, and generally good code is via linting. Linters are tools that check syntax and formatting for errors. Depending on the tool used, some will also check for inefficiencies or problematic patterns. The W3 has a validation service13 but many prefer to validate code as they go. Tools such as CSS Lint can be run in the browser14 via command line15 or even as editor extensions.16
Precompiled CSS, such as Sass or Less, will not compile if not valid, so linting is much less of an issue. However, linters do exist for precompiled CSS languages and can prove very helpful in debugging and in flagging potentially problematic rule combinations and redundancy.
Like most code, CSS can be tested. There are a number of libraries available to do just that. There are two main ways that CSS can be tested: via unit tests or snapshots.
Unit testing CSS involves checking that styles that are expected to be applied are in fact set on the element. Libraries such as Quixote17 test that what is being rendered in the browser is what the author intends. The author can select a specific element by class or ID and verify any CSS property.
When using precompilers, mixins can also be tested. Similar to functions, mixins have inputs and outputs; therefore, outputs can be tested based on input passed. Libraries such as Barista18 or True19 can make sure that mixin outputs match the author’s intent.
The other way to test CSS is through visual regression testing. Libraries such as Cypress20 or Jest21 provide a framework to take and compare snapshots. By checking current snapshots against the reference ones, developers can be notified of unintended side effects when CSS is changed. Any nonmatching snapshots will raise a flag for the author to check on.
Consistency in implementation
That architectural decisions are being followed
Minimal code repetition
Furthermore, code reviews are an opportunity to have open discussions about architectural decisions and specific consequences of those decisions. Code reviews should never be used as a way to judge other developers, but as a forum for sharing knowledge, learning from each other, and ensuring the long-term success of your team and project.
Goals and guidelines that can help assess various options
The CSS methodologies OOCSS, BEM, SMACSS, and ITCSS
Processes you can use to evaluate your decisions to stay on track
Now you’re ready to select and implement a CSS architecture methodology on your own projects! Best of luck, and happy coding!