I have a passion for modular design. I've long been interested in separating the Web into components, not pages, and dynamically merging those components into the interface. This approach is flexible, efficient and easy to maintain.
But I don't want my design to seem to be made up of irrelevant things. I'm creating an interface, not a surreal picture.
Fortunately, there is already a technology called CSS that is deliberately designed to solve this problem. With CSS, I can pass styles around HTML components to ensure consistent design at minimal cost. This is thanks in large part to the two CSS features:
While these features allow us to add styles to Web documents in a DRY and efficient way, it is also why CSS exists, but it is clear that they are no longer favored. In some CSS methodologies, such as BEM and Atomic CSS, many of the methods that programmatically encapsulate CSS modules are trying to circumvent or suppress these features. This also gives developers more opportunities to control their CSS, but this is only a special control based on frequent intervention.
I'm going to take the respect for the modular interface design here to revisit inheritance, cascading, and scopes. What I want to tell you is how to use these features to make your CSS code more concise, better adaptive, and to improve the extensibility of the page.
Inheritance and font-family
While many people are complaining about why CSS doesn't just provide a global scope, if it does, then there are a lot of repeating styles. Conversely, CSS has global scope and local scope. Just like in JavaScript, local scopes have access to the parent and global scopes, whereas in CSS, local scopes help inheritance.
For example, if you define an font-family attribute for the root (also: global) HTML element, you can be sure that the rule applies to all ancestor elements in the document (there are some exceptions that will be discussed in the next section).
HTML { Font-family:sans-serif;} /*this rule is not needed↷p { font-family:sans-serif;} */
As in JavaScript, if I define some rules in the local scope, they are not valid globally, or in any ancestor scope, but only in their own sub-scopes (as in the P element in the code above).) In the next example, 1.5 of line-height are not used by HTML elements. However, the A element in P uses the Line-height value.
HTML { font-family:sans-serif; } p { line-height:1.5; } /* This rule was not Needed↷ p a { line-height:1.5; } */
The biggest benefit of inheritance is that you can build a foundation for consistent visual design with a very small amount of code. And these styles will even work on HTML that you haven't written yet. We're talking about code that won't go out of date!
Alternative methods
Of course there is another way to provide a common style. For example, I can create a. Sans-serif class ...
. sans-serif { font-family:sans-serif; }
... and apply it to whatever I want it to have this style of element up:
<p class= "Sans-serif" >lorem ipsum.</p>
This approach provides some control over the right: I can choose exactly which elements to apply this style to and which elements are not.
Any opportunity to control is attractive, but there are some obvious problems. Not only do I need to manually add the class name to the element that needs to apply the style (which also means that I want to first determine what the style class is), and in this case it has effectively discarded the possibility of supporting dynamic content: Neither the Rich Text editor nor the Markdown parser can provide any P element Sans-serif class.
Class= "Sans-serif" and style= "Font-family:sans-serif" are almost all-except the former means adding code to both the stylesheet and the HTML. With inheritance, we can write a little bit less and the other one doesn't have to write anymore. Instead of writing a class for each font style, we can add the desired rule to the HTML element in only one declaration.
HTML { font-size:125%; Font-family:sans-serif; line-height:1.5; Color: #222; }
Inherit keywords
Some types of properties are not inherited by default, and some elements do not inherit certain properties. In some cases, however, you can use [property name]: Inherit to enforce inheritance.
For example, the input element does not inherit any font attributes in the previous example, and textarea does not inherit. To ensure that all elements can inherit these properties from the global scope, you can use the wildcard selector and the Inherit keyword. In this way, inheritance can be used to the fullest extent possible.
* { font-family:inherit; Line-height:inherit; Color:inherit; } HTML { font-size:125%; Font-family:sans-serif; line-height:1.5; Color: #222; }
Notice that I overlooked the font-size. The reason I don't want to inherit font-size directly is that it overrides the default user-agent style of the heading element (translator Note: H1), small element, and other elements. By doing this I can save one line of code and let user-agent decide what style to want.
Another property I don't want to inherit is Font-style: I don't want to reset the italic of EM and add it again. This will be useless work and will generate extra code.
Now, all font styles, whether inherited or enforced, are what I expected. We've spent a lot of time using only two declaration blocks to deliver a consistent idea and scope. From now on, except for some exceptions, no one will need to consider font-family, line-height, or color when constructing the component. This is the origin of the cascade.
Exception-based styles
I might want the main heading elements (H1) to take the same font-family, color, and line-height. Using inheritance is a good solution, but I want it to be font-size different. Because the default user-agent style has provided a large font-size for the H1 element (but it will be overwritten by the style I set for the relative base font size of 125%), I don't need to overwrite it here if possible.
However, do I need to adjust the font size of all the elements? I then took advantage of the global scope to adjust only where I needed to adjust in the local scope.
* { font-family:inherit; Line-height:inherit; Color:inherit; } HTML { font-size:125%; Font-family:sans-serif; line-height:1.5; Color: #222; } h1 { font-size:3rem; }
If the CSS element's style is encapsulated by default, then the following is not possible: you need to explicitly add all the font styles to H1. Instead, I can split the style into several separate style classes, and then add a style to the H1 with a space divider:
Either way, more work is needed, and the ultimate goal is a style H1. Using cascading, I have given the desired style to most of the elements, and have made H1 an exception in only one way. Cascading as a filter means that styles change only when new style overrides are added.
Element style
We have a good head, but to really master the Cascade, we also need to add as many styles as possible to the public elements. Why? Because our hybrid components are made up of separate HTML elements, a screen reader-friendly interface makes full use of the semantic structure tags.
In other words, the "atoms" style that makes your interface "molecular" (using the atomic design terminology) should be largely anchored and use element selectors. Element selectors have a low priority, so they don't overwrite class-based styles that you might add later.
The first thing you should do is to add a style to all the elements you will need to use:
A {...} p {...} H1, H2, h3 {...} Input, textarea {...} /* etc */
If you want to have a consistent interface without redundancy, then the next step is very important: whenever you create a new component, if it takes some new elements, use the element selector to add a style to them. Now is not the time to use a restrictive, high-priority selector, or to write a style class. The semantic element uses itself.
For example, if I haven't added a style to the button element (just like the previous example), and the new component has added a BUTTON element, then this is a good chance to add a style to the button element for the entire interface.
button { padding:0.75em; Background: #008; Color: #fff; } Button:focus { outline:0.25em solid #dd0; }
Now, when you want to write a new component and join the button again, there's one less thing to worry about. Under different namespaces, do not rewrite the same CSS, and there is no class name to remember or write. CSS should always be dedicated to making things simple and efficient-it's designed for that.
There are three main advantages to using element selectors:
The resulting HTML is more concise (there are no redundant style classes).
The resulting style sheet is more concise (styles are shared between components and do not need to be overridden in each component).
The generated interface for adding a good style is based on semantic HTML.
Using classes to specialize in providing styles is often defined as "separation of concerns." This is a misunderstanding of the principle of separation of concerns of the site. Its purpose is to use HTML and CSS styles to describe the entire structure. Because classes are specifically designed for style purposes and are present in structural tags, no matter where they are used, technically they break apart, and you have to change the physical structure to get the style.
No matter where you are, don't rely on the structural markup of the surface (style class, inline style), your CSS should be compatible with common structure and semantic conventions. This allows you to simply extend the content and functionality without requiring it to become a style task. Also in projects with different traditional semantic constructs, you can make your CSS more reusable (but the "methodology" of CSS may be different).
Special cases
Before I was accused of being overly simplistic, I realized that not all the buttons on the interface did the same thing, and I realized that the buttons for doing different things might seem to be not the same in some way.
This is not to say that we need to work with style classes, inheritance, or cascading. Having a button on an interface that looks completely different is confusing to your users. For accessibility and consistency, most buttons appear to be visually differentiated by labels only.
<button>create</button> <button>edit</button> <button>delete</button >
Remember that styles are not the only way to differentiate visually. The content can also be visually differentiated and, to a certain extent, more explicit, because you are telling the user different things in words.
In most cases, it is not necessary or correct to use styles individually to differentiate between content. Typically, a style distinction should be an additional condition, such as a red background or a text label with an icon. Text labels have specific effects on software that uses sound activation: When you say "red button" or "button with the cross icon", it does not cause the software to be recognized.
I'll explore the topic of adding nuances to seemingly similar elements in the "Tool classes" section.
Label Properties
Semantic HTML is not just about elements. Label properties define types, style properties, and states. These are also important for accessibility, so they need to be written in the right place in HTML. And because they're all in HTML, they also offer the opportunity to do style hooks.
For example, the INPUT element has a type attribute, so you should want to take advantage of it, as well as the Aria-invalid attribute is used to describe the state.
Input, textarea { border:2px solid; Padding:0.5rem; } [Aria-invalid] { border-color: #c00; Padding-right:1.5rem; Background:url (images/cross.svg) no-repeat center 0.5em; }
Here are a few things to look out for:
I don't need to set color, font-family, or line-height here, because these are inherited from the HTML, thanks to the Inherit keyword used above. If I want to change the font-family on the entire application level, just edit one of the declarations in the HTML block.
The color of the border is associated to color, so it is also inherited from the global color. I just need to declare the width and style of the border.
There is no limit to the [Aria-invalid] property selector. This means that it has a better application (it can work at both the input and textarea selectors) and the lowest priority. Simple attribute selectors and class selectors have the same precedence. Unrestricted use of them means that any style classes that are written under a cascade can overwrite them.
The BEM methodology solves this problem by using a modifier class, such as Input--invalid. However, given that the invalid state should only be used at the time of communication, Input--invalid is still a certain amount of redundancy. In other words, the Aria-invalid attribute has to be written there, so where is the purpose of this style class?
Write-only HTML
In the cascade aspect about most element and attribute selectors I absolutely like the thing is that the structure of the component becomes less aware of the naming conventions of the company or organization, and pays more attention to HTML. Any developer who is proficient in writing decent HTML will benefit from the inherited style that has been written in place when assigned to the project. These styles significantly reduce the need to read documents and write new CSS. In most cases, they can write only some (meta) languages that should be known by rote memorization. Tim Baxter also wrote a case for this in meaningful css:style it like you Mean it.
Layout
So far, we haven't written any CSS for the specified component, but that doesn't mean we haven't added any related styles yet. All components are a combination of HTML elements. The formation of more complex components is mainly by the combination of these elements of the order and arrangement.
This leads us to the concept of layout.
Basically we need to deal with the flow layout-the spacing between successive block elements. You may have noticed that I haven't set any margin for any elements so far. That's because the margin should not be considered as an attribute of an element, but rather as an attribute of the element context. In other words, they should only work when they encounter elements.
Fortunately, the direct neighbor selector can accurately describe the relationship. With cascading, we can use a single selector with a uniform default across all successive block-level elements, with only a few exceptions.
* { margin:0; } * + * { margin-top:1.5em; } Body, BR, Li, dt, DD, TH, TD, option { margin-top:0; }
Using a very low-priority Owl Selector ensures that any element (except for those common exceptions) is separated by a single line. This means that in all cases there will be a default white interval, and all developers who write the component flow content have a reasonable starting point.
In most cases, margins only care about themselves. However, because of the low priority, it is easy to overwrite the base line interval when needed. For example, I might want to remove the interval between a tag and its associated elements, so that they are a pair. In the following example, any element that follows the label (input, textarea, select, and so on) has no spacing.
Label { Display:block } label + * { margin-top:0.5rem; }
Again, using cascading means that you just have to write some specific styles when you need them, while other elements conform to a reasonable benchmark.
It is important to note that because margins only occur between elements, they do not overlap with the padding that may be included within the container. This is also a matter that does not need to be feared or prevented.
Also note that whether or not you decide to introduce packaging elements has been the same interval. That is, you can do it as follows and implement the same layout-the margin appears much better between p than between the label and the input box.
<form> <p> <label for= "One" >label one</label> <input id= "one" name= "one" Type= "text" > </p> <p> <label for= "a" >label two</label> <input id= "Name=" type= "text" > </p> <button type= "Submit" >Submit</button> </ Form>
The same effect can be achieved with a method like atomic CSS, by combining various margin-related style classes and adding them manually in various situations, including the exception of First-child, which is implicitly controlled by * + *:
<form class= "Mtop (1POINT5)" > <p class= "mtop (0)" > <label for= "one" class= "mtop (0)" >label one</label> <input id= "one" name= "one" type= "text" class= "Mtop (0point75)" > </p> < P class= "Mtop (1POINT5)" > <label for= "both" class= "mtop (0)" >label two</label> <input id= " "Name=" "type=" "Text" class= "Mtop (0point75)" > </p> <button type= "Submit" class= "Mtop" ( 1POINT5) ">Submit</button> </form>
Remember that if you persist in using atomic CSS, writing like above will only cover the top margin. You must also establish separate style classes for color, Background-color, and other properties, because atomic CSS does not control inheritance or element selectors.
<form class= "Mtop (1POINT5) BDC (#ccc) P (1POINT5)" > <p class= "mtop (0)" > <label for= "one" class= "Mtop (0) C (brandcolor) Fs (bold)" >label one</label> <input id= "one" name= "one" type= "text" class= "Mtop (0point75) C (brandcolor) BDC (#fff) B (2) P (1) "> </p> <p class=" Mtop (1POINT5) "> <label For= "class=" mtop (0) C (brandcolor) Fs (bold) ">label two</label> <input id=" "A" and "name=" Text "class=" Mtop (0point75) C (brandcolor) BDC (#fff) B (2) P (1) "> </p> <button type=" Submit "class= "Mtop (1POINT5) C (#fff) BDC (blue) P (1)" >Submit</button> </form>
Atomic CSS allows developers to control styles directly without using inline styles, and inline styles are not reusable like style classes. By providing style classes for various independent properties, the duplicate declarations in the style sheet are reduced.
However, it needs to be directly involved in labeling for these purposes. This requires learning and investing in its lengthy API, as well as writing a lot of extra HTML code.
Conversely, the CSS "methodology" is largely deprecated if it is used only to design arbitrary HTML elements and their spatial relationships. Systems with consistent design have great advantages, and are easier to consider and manage separately than an overlay-style HTML system.
In any case, here are the features that our CSS architecture and streaming layout content solutions should have:
Global (HTML) style and enforce inheritance,
Streaming layout methods and some exceptions (using the owl selector),
Element and attribute styles.
We haven't written a specific component or conceived a CSS style class, but most of our styles have already been written, provided that we can write the style class reasonably and re-use it.
Tool class
About style classes They have a global scope: they are all used anywhere in the HTML, and they are affected by the associated CSS. For most people, this is considered a disadvantage, because two independent developers may write a style class with the same name, thus affecting each other's work.
CSS modules has recently been used to solve this situation by generating unique style class names in programs that are bound to their local or component scopes.
<!--My module ' s button-- <button class= "button_dysuhe027653" >press me</button> <!-- Their module ' s button-- <button class= "button_hydsth971283" >hit me</button>
Ignoring the ugliness of the generated code, you should be able to see the differences between the two independent components and easily put together: unique identifiers are used to differentiate between similar styles. With so much better effort and redundant code, the result interface will be either inconsistent or consistent.
There is no reason to differentiate the public elements from uniqueness. You should add a style to an element type, not an element instance. Keep in mind that "class" means "some kind of something that may be a lot of things." In other words, all style classes should be tool classes: globally reusable.
Of course, in this example, in short. The button class is redundant: we can replace it with the button element selector. But what if there is a special type of button? For example, we might write a. Danger class to indicate that this button is a risky operation, such as deleting data:
. Danger { background: #c00; Color: #fff; }
Because class selectors have a higher precedence than element selectors, and the property selector has the same precedence, the style rule added after the style sheet overrides the previous element and the rule of the property selector. Therefore, the danger button will appear with white text on a red background, but its other properties, such as the padding, the focus contour, and the margin will be added by the previous flow layout method, and remain unchanged.
<button class= "Danger" >delete</button>
If many developers work on the same code for a long time, a naming conflict can occur occasionally. But there are several ways to avoid this, like, oh, I don't know, but for the name you want to take, I suggest a text search to see if it's already there. Because you don't know, someone may have solved the problem you are positioning.
Various tool classes for local scopes
For the tool class, my favorite thing to do is to set them on the container and use this hook to influence the layout of the inner child elements. For example, I can quickly set an equal-interval, responsive, and centered layout on any element.
. centered { text-align:center; Margin-bottom: -1rem; /* Adjusts for leftover bottom margin of children */ } . centered > * { display:inline-block; margin:0 0.5rem 1rem; }
Using this method, I can center all the elements, such as list items, buttons, button combinations, and links. Used by > * In this scope, it means that the closest child elements under the element with the. Centered style will take these styles, and also inherit the styles of the global and parent elements.
And I adjusted the margin so that the elements were free to wrap, without destroying the vertical setting using the * + * selector. This small amount of code provides a general-purpose, responsive layout solution by setting a local scope for different elements.
One of my small (compressed 93B) based Flexbox Grid layout systems is a tool class similar to this approach. It is highly reusable, and because it uses flex-basis, it does not require a breakpoint intervention. I just used the Flexbox layout method.
. fukol-grid { Display:flex; Flex-wrap:wrap; margin: -0.5em; /* Adjusting for gutters * /}. Fukol-grid > * { flex:1 0 5em;/* The 5em part is the basis (ideal width) * / margin:0.5em;/* Half the Gutter value */ }
Using the BEM method, you will be encouraged to place an explicit "element" style class on each grid item:
<p class= "Fukol" > <!--the outer container, needed for vertical rhythm-- <ul class= "Fukol-grid" > <li class= "Fukol-grid__item" ></li> <li class= "Fukol-grid__item" ></li> <li class= "Fukol-grid__item" ></li> <li class= "Fukol-grid__item" ></li> </ul > </p>
But that's not necessary. An identifier is required to instantiate the local scope. The list items here are no longer protected by external influences and should not be affected by > * Compared to the list items in my version. The only difference is that there is a lot of markup for style classes.
So, now that we've started merging style classes, we're merging only on commonality, just like they expect. We still don't have to add styles to complex components independently. Instead, we are addressing some of the systemic issues in a reusable way. Of course, you will need to write down in the comments how these style classes are used.
Tool classes like these take advantage of CSS's global scope, local scope, inheritance, and cascading. These style classes can be used in various places, and they instantiate local scopes to affect only their child elements, and they inherit styles that are not set on themselves from the parent or global scope, and we are not over-using element or class selectors.
Here's what our stack looks like now:
Global (HTML) style and mandatory inheritance,
Streaming layout methods and some exceptions (using the owl selector),
Element and attribute styles,
Generic Tool class.
Of course, it may not be necessary to write all of these sample tool classes. The point is that if there is a need to use the component, the solution should be valid for all components. Always stand at the system level to think.
Specific component Styles
We've added styles to components from the beginning, and we've learned how to combine styles with components, so a lot of people might miss out on this part. It is worth noting, however, that any components that are not created from other components, including even a single HTML element, are necessary. They are components that use the ID selector and are at risk of becoming a system problem.
In fact, a good practice is to use IDs only to identify complex components ("molecules", "organisms"), and not to use them in CSS. For example, you can write a #login on a login form component, and you should not use the #login in CSS with elements, attributes, or streaming layout methods, even though you might find yourself creating one or two generic tool classes that can be used in other form components.
If you do use a #login, it will only affect that component. It is important to note that if you do this, you have deviated from developing a design system direction and moving forward with lengthy code that has only a constant tangle of pixels.
Conclusion
When I tell people that I'm not using a methodology like BEM or a CSS module such as this, most would think that I would write a CSS like this:
Header Nav ul li { display:inline-block; } Header Nav ul Li a { background: #008; }
I didn't do that. A clear statement is already here, and the things we need to be careful to avoid are also explained. Just to show that BEM (and Oocss, Smacss, atomic CSS, etc.) is not the only way to avoid complex, impossible-to-manage CSS.
To solve the priority problem, many methodologies have chosen to use class selectors. The problem is that this creates a lot of style classes: The Magic code that makes HTML markup bloated, and the loss of attention to the document, which can make new developers confused about the system they're in.
By using a lot of style classes, you also need to manage a style system, and the system is largely decoupled from the HTML system. This inappropriate so-called "separation of concerns" can cause redundancy, or even worse, to make it inaccessible: it is possible to affect a visual style in an accessible state:
<input id= "My-text" aria-invalid= "false" class= "Text-input--invalid"/>
To replace a lot of writing and various style classes, I found some other methods:
To set a precondition for consistency mastery inheritance;
Full use of element and attribute selectors to support transparency and standard-based combination styles;
Easy-to-use flow-layout system;
Combine some highly versatile tool classes to solve common layout problems that affect multiple elements.
All of these methods are designed to create a design system that makes it easier to write a new component, and reduces the reliance on adding new CSS code when the project matures. And this is not to benefit from strict naming and merging, but because they are missing.
You may not have a cold with the special tips I recommend here, but I hope this article will at least allow you to rethink what the components are. They are not something you create on your own. Sometimes, in the case of standard HTML elements, they are not even what you create. The more things your component gets from other components, the more accessible and visually consistent the interface will be, and eventually it will be implemented with less CSS.
CSS doesn't have much of a fault. In fact, it's very nice to have you do a lot of things, we just don't use them.