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:
Inherited
Cascade (C,cascade in CSS)
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 give the root (also: global)
Html
Element defines a
Font-family
property, 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 isn't Needed↷
p {
Font-family:sans-serif;
}
*/
As in JavaScript, if I define some rules in a local scope, they are not valid globally, or in any ancestor scope, but only in their own sub-scopes (as in the code above), as in
P
Element). In the next example,
1.5
Of
Line-height
And not be
Html
element is used. But
P
In the
A
The elements are used in the
Line-height
The value.
HTML {
Font-family:sans-serif;
}
p {
line-height:1.5;
}
/*
This rule isn't 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"
The same usage-except that it means adding code to both the style sheet and HTML. With inheritance, we can write a little bit less and the other one doesn't have to write anymore. Rather than writing a class for each font style, we can just give
Html
element to add the desired rule.
HTML {
font-size:125%;
Font-family:sans-serif;
line-height:1.5;
Color: #222;
}
Inherit
Key words
Some types of properties are not inherited by default, and some elements do not inherit certain properties. However, in some cases, you can use the
[Property name]: Inherit
To enforce inheritance.
To give an example,
Input
element does not inherit the attributes of any font in the previous example,
TextArea
Will not inherit as well. To ensure that all elements can inherit these properties from the global scope, you can use the wildcard selection and
Inherit
Key word. 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 ignored the
Font-size
。 I don't want to inherit directly.
Font-size
The reason is that it will heading element (translator Note: such as
H1
)、
Small
The default user-agent style for elements and some other elements is overridden. By doing this I can save one line of code and let user-agent decide what style to want.
The other property I don't want to inherit is
Font-style
: I don't want to reset
Em
Italic, and then 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 is going to have to think about it when it comes to building components.
Font-family
、
Line-height
Or
Color
The This is the origin of the cascade.
Exception-based styles
I might want the main heading element (
H1
) using the same
Font-family
、
Color
And
Line-height
。 Using inheritance is a good solution, but I want it again.
Font-size
Not the same. Because the default user-agent style has been given
H1
The element provides a large
Font-size
(But then it will be overwritten by the style I set for the relative base font size of 125%), and I don't need to overwrite it 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 give
H1
Add all font styles. Instead, I can split the style into several separate style classes, and then use a space divider to give
H1
To add a style:
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 only in one aspect make
H1
Become an exception. 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.
As an example, if I haven't given
button
Element (just like the previous example), and the new component adds a
button
Element, then this is one for the entire interface
button
An opportunity to add a style to an element.
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 is 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.
To give an example,
Input
Element has a
Type
property, then you should want to take advantage of it, and there are like
Aria-invalid
property 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:
Here I don't need to set
Color
、
Font-family
Or
Line-height
, because these are all from
Html
, thanks to the use of the above
Inherit
Key word. If I want to change the whole application level,
Font-family
, you only need to
Html
That piece of one of the statements to be edited on it.
The color of the border is associated to
Color
, so it's also from the global
Color
Inherit from. I just need to declare the width and style of the border.
[Aria-invalid]
There is no limit to the property selector. This means that it has a better application (it can also act on
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
。 But considering that the invalid state should only function when it is possible to communicate,
Input--invalid
is still a certain amount of redundancy. Other words
Aria-invalid
Properties have 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 streaming layouts – 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) there are no gaps.
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 the same as below and implement the same layout – the margins are
P
is much better than appearing between labels and input boxes.
<form>
<p>
<label for= "one" >label one</label>
<input id= "One" name= "one" type= "text" >
</p>
<p>
<label for= ">label" two</label>
<input id= "", "name=", "type=", "Text" >
</p>
<button type= "Submit" >Submit</button>
</form>
The same effect can be achieved using methods like atomic CSS, by combining various margin-related style classes and manually adding them in various situations, including being
* + *
Implicit control of
First-child
This exception is the case:
<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= "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 have to do it for
Color
、
Background-color
and other properties to establish a separate style class, 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= "B" class= "Mtop (0) C (brandcolor) Fs (bold)" >label two</label>
<input id= "2" name= "" "type=" text "class=" Mtop (0point75) C (brandcolor) BDC (#fff) B () 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
. button
The class is redundant: we can use
button
Element selector instead. 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. All by
> *
In this scope, it means the use of a
. centered
The closest child elements under the elements of the style will take these styles, and also inherit the styles of the global and parent elements.
And I adjusted the margins so that the elements could be wrapped freely and without destroying the use
* + *
The vertical setting of the selector setting. 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
, so no breakpoint intervention is required. 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-griditem" ></li>
<li class= "Fukol-griditem" ></li>
<li class= "Fukol-griditem" ></li>
<li class= "Fukol-griditem" ></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
> *
The impact. 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 form component on a
#login
, then you should not use the style of elements, attributes, or streaming layout methods in CSS.
#login
, even though you may find that you are creating a generic tool class or two that can be used in other form components.
If you do use the
#login
, then 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.