Fluent UI Insights: Styling
Aug 15, 2023
Fluent UI Insights: Styling
Fluent UI Insights is a series that describes the design and decisions behind the Fluent UI design system. In the second episode, engineers from the Fluent UI team explain issues related to styling UI controls by sharing the requirements and also describing problems with existing solutions. 🔗 Links: - https://github.com/microsoft/fluentui - https://react.fluentui.dev/ 📚 Chapters: 00:00 Introduction 00:30 CSS? 01:44 CSS specificity 06:47 BEM, CSS, CSS-in-JS 12:30 State of things 13:32 Not the end… 💡 References: - #1 An article about CSS specifity https://developer.mozilla.org/en-US/d … - #2 CodeSandbox that shows a dependency on order of imports https://codesandbox.io/s/white-paper- … - #3 Semantic UI homepage https://semantic-ui.com - #4 BEM homepage https://getbem.com - #5 CSS modules specification https://github.com/css-modules/css-mo … - #6 Emotion.js homepage https://emotion.sh/ - #7 An issue in Emotion.js about performance hit when styles are used defined inside a component https://github.com/emotion-js/emotion … - #8 Fela.js homepage https://fela.js.org/ #fluentui #webdev #reactjs
Content
6.64 -> Hello world! My name is Oleksandr.
9.158 -> I work on Fluent UI React
10.758 -> at Microsoft Development Center Prague.
12.915 -> In the previous episode my colleagues and I
16.229 -> talked about positioning in Fluent UI React v9.
18.815 -> In this episode we will talk about
21.014 -> how styling works, our journey with CSS,
24.172 -> different approaches we took, and why we have
26.458 -> our own CSS-in-JS engine, called Griffel.
34.657 -> CSS is a key part of web development that's used
38.028 -> to style, and layout web pages and it's
40.741 -> required to style our React components for web.
44.114 -> However with CSS there are common problems that
47.871 -> increase maintenance cost
50.015 -> for large applications and libraries.
52.643 -> And as a UI library our goal
55.595 -> is to simplify usage of components
58.115 -> while reducing complexity and maintenance cost.
62.67 -> Unfortunately there is nothing built into CSS
66.441 -> for easier maintenance and management of styles.
70.16 -> At the same time the approach that will be chosen
73.658 -> for CSS management defines how performant,
76.701 -> scalable and maintainable components will be.
80.111 -> And we have learned this from our past experience
83.723 -> with previous versions of Fluent UI.
86.484 -> Different approaches and technologies exist
89.358 -> to solve CSS problems.
91.001 -> We will cover differences between them a bit later
95.158 -> for now let's talk about CSS specificity
98.721 -> and why this is a problem that
101.601 -> every application deals with.
110.64 -> CSS specificity determines which
113.307 -> CSS property values are the most relevant
116.485 -> for an element if there is a conflict.
119.229 -> It's a hierarchy of different CSS selectors
122.192 -> which are ranked by increasing specificity.
125.161 -> The common misconception is that
127.474 -> order of appearance in a document
130.069 -> is part of CSS specificity. But it's not.
133.044 -> It's another criterion of Cascade Sorting Order.
136.691 -> We will not explain the entire topic in this
140.12 -> video, but I recommend reading an article on MDN
143.586 -> that explains CSS specificity
145.801 -> much better than I could.
147.604 -> While Fluent UI React components are reusable
150.681 -> customers still need to apply some styling tweaks
154.125 -> to them and they should be able to do this.
156.795 -> This brings us back to the importance of
159.472 -> CSS specificity because customer style overrides
162.958 -> should be more specific than CSS rules
165.444 -> in the library. Otherwise their styles
168.774 -> will not be properly applied even if they are
172.08 -> defined and present on the page.
174.409 -> Normally, there are no issues with order of appearance.
178.051 -> Modern bundlers handle most cases properly
181.728 -> based on the order of imports
184.319 -> even inside JavaScript modules.
186.815 -> However there are still issues when the order
190.158 -> of styles might change based on
193.315 -> JavaScript module loading.
195.23 -> A good example for this case is lazy loading.
198.289 -> At the same time applying styles based on
201.037 -> the imports order is not a good idea
204.13 -> especially for large scale applications
207.011 -> that import CSS from many modules. For example,
211.055 -> during refactor, someone can simply move imports
215.012 -> and the application appearance will be broken.
218.369 -> We have a slightly bigger problem
220.43 -> when using CSS selectors.
222.2 -> Let's take a moment to
223.441 -> reference Semantic UI as an example.
226.466 -> Semantic UI offers a nice concept for
229.508 -> CSS definitions: they use semantic language.
232.887 -> It's easy to write "ui vertical menu" and
235.615 -> understand how it looks based on the HTML markup.
239.915 -> Despite the fact that the concept is
243.058 -> understandable it leads to complex CSS selectors
246.837 -> that create CSS specificity problems.
249.498 -> On the snippet you can see that the selector uses
252.75 -> 3 classes. In order to override the "margin",
255.647 -> we should use a selector with at least 3 classes
259.3 -> or more specific, like IDs.
261.517 -> You will not believe
262.812 -> how many issues related to this problem exist
265.97 -> in Semantic UI repository. This pattern leads to
268.844 -> confusion and frustration for customers because
272.1 -> even for simple overrides they need to know about
275.601 -> CSS specificity in this specific case.
278.74 -> The previous example is the simplest case and
282.192 -> at the same time Semantic UI
284.172 -> is full of similar problems.
286.286 -> However even without CSS frameworks the same
289.644 -> problem will appear sooner or later.
292.837 -> Let's look at a case with a button.
295.261 -> For now we assume that the basic styles for a
297.895 -> button in the library include some properties
301.472 -> like "margin", "display" and "padding".
304.915 -> Perfect! We have our button and it works.
308.23 -> Someday our design team decided that a button
312.03 -> can also have "primary" state. Easy-peasy.
315.815 -> New CSS rule and we are done.
318.789 -> A week later our design team realized that
322.341 -> the button needs to have a "circular" shape variation.
326.913 -> No problem, it's doable. Everyone is happy.
330.332 -> Our application is shipped to production.
332.961 -> Another week later, our design team realized that
336.114 -> the combination of "primary" state and "circular" shape
339.444 -> should have different box shadows.
342.201 -> Again, it's fixed.
344.105 -> Okay! Let's look at the snippet.
347.1 -> It's growing and becoming more complex.
349.901 -> Let's also compare it with what we discussed in Semantic UI.
354.772 -> Now imagine what happens once you receive
357.73 -> a request to add "disabled" state.
360.246 -> I'm getting déjà vu.
361.544 -> What about you?
362.844 -> Yeah, it's the same problem!
365.258 -> In order to override the "box-shadow" property
368.444 -> for "primary" + "circular" combo, customers will
371.898 -> need to deal with the selector specificity.
374.8 -> Unfortunately in the reality it's even worse.
377.63 -> Because styles could come from multiple sources:
380.48 -> definitions from CSS files, inline definitions
383.696 -> via a "style" attribute and
385.93 -> the party king "important".
388.23 -> All this goes through specificity calculation,
391.801 -> inheritance and defaulting. This process might be
395.501 -> straightforward for machines but not for humans.
398.646 -> We will talk about solving specificity issues a bit later.
402.63 -> Now it's time to talk about ways to manage
405.558 -> and structure CSS rules.
414.814 -> One of the problems is that
416.572 -> CSS lacks built-in namespaces.
418.6 -> It's one of the reasons why developers
421.001 -> have issues scaling its usage.
422.931 -> Historically, the global namespace was designed as
425.803 -> a core feature of CSS to enable
428.451 -> portability and cascading.
430.465 -> This is the DNA of CSS.
433.571 -> How can CSS meet the incompatible demands for
436.827 -> simplicity from developers,
439.143 -> flexibility from designers
441.169 -> and responsiveness from users?
443.33 -> CSS can't by itself.
445.1 -> It needs its help from more abstract high-level tools
448.544 -> and techniques such as CSS-in-JS or preprocessors
452.084 -> to make it closer to a programming language.
455.043 -> Let's go through existing approaches and
456.972 -> methodologies, to check what problems they solve.
460.03 -> Our first guest is BEM or Block-Element-Modifier.
464.77 -> The idea behind BEM is to divide
467.344 -> user interface into independent blocks.
470.701 -> BEM is a set of rules for naming CSS classes,
474.043 -> where everything is a single class
476.3 -> and nothing is nested.
478.051 -> It's recommended to avoid combined selectors.
480.769 -> And as a design system,
482.187 -> we can follow this rule.
483.914 -> The most important thing about any approach
486.715 -> is to ensure that developers will follow it.
489.715 -> But can it be enforced in a giant corporation?
492.787 -> Probably, yes. Linters for CSS exist.
496 -> There is a lot of rules for them and
498.458 -> we can always write our own.
500.672 -> But it's too optimistic to say that
502.944 -> all teams across the organization will follow
505.458 -> the same approach.
507.079 -> BEM naming convention allows
509.001 -> to downgrade specificity of most selectors to 1.
512.63 -> This approach reduces a rule priority management
516.239 -> to just managing the order of appearance.
518.987 -> But all similar approaches will come
521.383 -> crashing down on rocks of the human factor.
524.209 -> There is no guarantee that a developer from
526.915 -> a Team A won't use the same name as a Team B.
530.03 -> The naming problem can only be solved by
533.187 -> assigning random name to the CSS class.
535.83 -> Meet our next guest, CSS modules.
538.6 -> CSS modules are CSS files in which
542.365 -> all class names and animation names
544.844 -> are scoped locally by default.
547.371 -> CSS modules perfectly solve the naming problem
551.144 -> since all classes are now random.
553.608 -> Sounds like a solution for
555.387 -> the global space problem, right?
557.33 -> But do CSS modules actually solve
559.814 -> the specificity problem?
561.344 -> Unfortunately no. CSS modules will hash classes
565.685 -> to avoid collisions with the global namespace,
569.121 -> but the specificity problem is still there.
571.971 -> But what if we combine BEM and CSS modules?
574.957 -> That's a quite common practice.
577.043 -> Since class names are encapsulated to
579.487 -> module boundaries there is no reason to use
581.701 -> the base class like ".button" in selectors.
584.23 -> In fact we can just remove all ".button"
586.644 -> class selectors in the example.
589.271 -> That gets us back to
590.614 -> managing just the rule order in most cases.
593.544 -> The fair note is that this works for
595.914 -> simple cases, for more complex ones it does not.
599.214 -> As selectors could be combined.
601.329 -> It looks like a never-ending circle.
603.414 -> This is where CSS-in-JS libraries enter the game
606.271 -> to offer solutions for specificity problems.
609.63 -> Well at least a few of them.
612.605 -> Emotion is blazing fast and very close
615.558 -> to plain CSS in terms of performance.
618.6 -> So why cannot we use it?
620.538 -> There are few reasons.
622.416 -> While its performance is great,
624.601 -> it still does a significant amount of runtime work.
627.772 -> It also outputs monolithic classes.
630.215 -> For example, you have base component styles
633.09 -> and style overrides. To merge them later
636.13 -> Emotion concatenates CSS inputs and generates
639.404 -> a new class for the result.
641.201 -> This work can only be done during runtime.
644.087 -> While we would like to reduce runtime to zero or near zero.
647.715 -> Especially considering that the trend of
650.372 -> zero runtime CSS-in-JS started to grow
652.986 -> in JavaScript community.
654.572 -> Emotion also allows us to define styles inside
658.087 -> React components.
659.571 -> At a glance there is nothing wrong with that,
662.729 -> but once the input values change frequently,
665.558 -> there will be a significant performance hit.
668.372 -> There is nothing wrong with Emotion.
670.801 -> The same is applicable to Fela and other libraries.
674.23 -> It's how developers use the API
676.529 -> and no one said that it will always be optimal.
679.943 -> Once style computations happen inside
682.729 -> the component and rely on its state,
685.044 -> a lot of runtime work is required
687.186 -> for each render of any component.
689.78 -> The opposite approach to monolithic classes is Atomic CSS.
694.087 -> In Atomic CSS, every property value pair is
697.715 -> written as a single CSS rule.
700.415 -> Our next guest, Fela.js relies on it.
703.844 -> Specificity issues are solved since
707.143 -> we cannot have duplicate properties in output
710.358 -> and every definition has single class specificity.
713.658 -> Nice and clean approach.
715.229 -> Why don't we use Fela then?
717.301 -> Well, we have used it in Fluent UI Northstar and
720.515 -> honestly the main problem we have had with this
723.55 -> library has been its performance.
725.814 -> The API design does not fit well into
728.215 -> React component lifecycle,
729.901 -> where everything renders frequently
732.03 -> and with every render
733.344 -> the styles are recomputed again and again.
736.386 -> We added a caching layer to the library
738.772 -> to achieve some reasonable numbers,
741.101 -> but in terms of performance it's still far from plain CSS.
745.172 -> Especially during the first render
747.287 -> when styles are not cached.
756.743 -> Both Office UI Fabric React and Stardust UI
759.515 -> use custom solutions for styling via CSS-in-JS.
763.13 -> By the way.
763.989 -> If you are confused by the naming
765.83 -> I suggest checking the first episode of Fluent UI Insights,
769.301 -> which covers that.
770.287 -> It's very confusing, even for us!
772.829 -> From one perspective it's bad that there were
775.858 -> two styling solutions doing the same thing.
778.786 -> From other perspective it's good,
781.387 -> because we learned different things,
783.786 -> gained experience and collected
785.714 -> a lot of feedback from our partners.
787.8 -> The funny thing is that in both libraries
790.332 -> we encountered the same problems.
792.743 -> Each library had performance issues related
796.029 -> to its styling layer.
797.73 -> Once the convergence of the libraries started
800.586 -> we came to an interesting conclusion:
803.214 -> we cannot make the existing solutions faster
806.487 -> and closer to plain CSS
808.316 -> because too much work was required at runtime.
816.56 -> Unfortunately this episode is going to end
819.258 -> on a bit of a cliffhanger.
821.23 -> We covered common problems with CSS
823.463 -> and the existing tools to solve them.
826.115 -> In the next episode I will talk in detail
828.872 -> about our CSS-in-JS solution called Griffel.
832.144 -> You will be able to learn some of the
834.815 -> key implementation details and
836.615 -> the context behind them.
838.386 -> See you next time!
844.08 -> There are no issues with the JavaScript
846.957 -> that's true.
848.444 -> The opposite approach to Atomic CSS is
851.715 -> Atomic CSS....
853.086 -> You know, you are celebrating that you finally
856.144 -> managed to pronounce "specificity" and
858.272 -> failed to pronounce "are"... (laughing)
Source: https://www.youtube.com/watch?v=a8TFywbXBt0