Tag: Defining

Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library

Theming UI refers to the ability to perform a change in visual styles in a consistent manner that defines the “look and feel” of a site. Swapping color palettes, à la dark mode or some other means, is a good example. From the user’s perspective, theming involves changing visual styles, whether it’s with UI for selecting a theme style, or the site automatically respecting the user’s color theme preference at the OS-level. From the developer’s perspective, tools used for theming should be easy-to-use and define themes at develop-time, before applying them at runtime.

This article describes how to approach theming with Mimcss, a CSS-in-JS library, using class inheritance—a method that should be intuitive for most developer as theming is usually about overriding CSS property values, and inheritance is perfect for those overrides.

Full discloser: I am the author of Mimcss. If you consider this a shameless promotion, you are not far from the truth. Nevertheless, I really do believe that the theming technique we’re covering in this article is unique, intuitive and worth exploring.

General theming considerations

Styling in web UI is implemented by having HTML elements reference CSS entities (classes, IDs, etc.). Since both HTML and CSS are dynamic in nature, changing visual representation can be achieved by one of the following methods:

  1. Changing the CSS selector of an HTML element, such as a different class name or ID.
  2. Changing actual CSS styling for that HTML element while preserving the selector.

Depending on the context, one method can be more efficient than another. Themes are usually defined by a limited number of style entities. Yes, themes are more than just a collection of colors and fonts—they can define paddings, margins, layouts, animations and so on . However, it seems that the number of CSS entities defined by a theme might be less than a number of HTML elements referencing these entities, especially if we are talking about heavy widgets such as tables, trees or code editors. With this assumption, when we want to change a theme, we’d rather replace style definitions than go over the HTML elements and (most likely) change the values of their class attributes.

Theming in plain CSS

In regular CSS, one way theming is supported is by using alternate stylesheets. This allows developers to link up multiple CSS files in the HTML <head>:

<link href="default.css" rel="stylesheet" type="text/css" title="Default Style"> <link href="fancy.css" rel="alternate stylesheet" type="text/css" title="Fancy"> <link href="basic.css" rel="alternate stylesheet" type="text/css" title="Basic">

Only one of the above stylesheets can be active at any given time and browsers are expected to provide the UI through which the user chooses a theme name taken from the values of the <link> element’s title attribute. The CSS rule names (i.e. class names) within the alternative stylesheets are expected to be identical, like:

/* default.css */ .element {   color: #fff; }  /* basic.css */ .element {   color: #333; }

This way, when the browser activates a different stylesheet, no HTML changes are required. The browser just recalculates styles (and layout) and repaints the page based on the “winning” values, as determined by The Cascade.

Alternate stylesheets, unfortunately, are not well-supported by mainstream browsers and, in some of them, work only with special extensions. As we will see later, Mimcss builds upon the idea of alternate stylesheets, but leverages it in a pure TypeScript framework.

Theming in CSS-in-JS

There are too many CSS-in-JS libraries out there, and there is no way we can completely cover how theming works in CSS-in-JS in a single post to do it justice. As far as CSS-in-JS libraries that are tightly integrated with React (e.g. Styled Components), theming is implemented on the ThemeProvider component and the Context API, or on the withTheme higher-order component. In both cases, changing a theme leads to re-rendering. As far as CSS-in-JS libraries that are framework-agnostic, theming is achieved via proprietary mechanisms, if theming is even supported at all.

The majority of the CSS-in-JS libraries—both React-specific and framework-agnostic—are focused on “scoping” style rules to components and thus are mostly concerned with creating unique names for CSS entities (e.g. CSS classes). In such environments, changing a theme necessarily means changing the HTML. This goes against the alternative stylesheets approach described above, in which theming is achieved by just changing the styles.

Here is where Mimcss library is different. It tries to combine the best of both theming worlds. On one hand, Mimcss follows the alternate stylesheets approach by defining multiple variants of stylesheets with identically named CSS entities. On the other hand, it offers the object-oriented approach and powerful TypeScript typing system with all the advantages of CSS-in-JS dynamic programming and type safety.

Theming in Mimcss

Mimcss is in that latter group of CSS-in-JS libraries in that it’s framework-agnostic. But it’s also created with the primary objective of allowing everything that native CSS allows in a type-safe manner, while leveraging the full power of the TypeScript’s typing system. In particular, Mimcss uses TypeScript classes to mimic the native CSS stylesheet files. Just as CSS files contain rules, the Mimcss Style Definition classes contain rules.

Classes open up the opportunity to use class inheritance to implement theming. The general idea is that a base class declares CSS rules used by the themes while derived classes provide different style property values for these rules. This is very similar to the native alternative stylesheets approach: activate a different theme class and, without any changes to the HTML code, the styles change.

But first, let’s very briefly touch on how styles are defined in Mimcss.

Mimcss basics

Stylesheets in Mimcss are modeled as Style Definition classes, which define CSS rules as their properties. For example:

import * as css from "mimcss"  class MyStyles extends css.StyleDefinition {   significant = this.$  class({     color: "orange",     fontStyle: "italic"   })    critical = this.$  id({     color: "red",     fontWeight: 700   }) }

The Mimcss syntax tries to be as close to regular CSS as possible. It is slightly more verbose, of course; after all, it is pure TypeScript that doesn’t require any plug-ins or pre-processing. But it still follows regular CSS patterns: for every rule, there is the rule name (e.g. significant), what type of rule it is (e.g. $ class), and the style properties the rule contains.

In addition to CSS classes and IDs, style definition properties can define other CSS rules, e.g. tags, keyframes, custom CSS properties, style rules with arbitrary selectors, media, @font-face, counters, and so on. Mimcss also supports nested rules including those with pseudo classes and pseudo-elements.

After a style definition class is defined, the styles should be activated:

let styles = css.activate(MyStyles);

Activating styles creates an instance of the style definition class and writes the CSS rules to the DOM. In order to use the styles, we reference the instance’s properties in our HTML rendering code:

render() {   return <div>     <p className={styles.significant.name}>       This is a significant paragraph.     </p>     <p id={styles.critical.name}>       This is a critical paragraph.     </p>   </div> }

We use styles.significant.name as a CSS class name. Note that the styles.significant property is not a string, but an object that has the name property and the CSS class name. The property itself also provides access to the CSS Object Model rule, which allows direct rule manipulation; this, however, is outside of the scope of this article (although Louis Lazaris has a great article on it).

If the styles are no longer needed, they can be deactivated which removes them from the DOM:

css.deactivate(styles);

The CSS class and ID names are uniquely generated by Mimcss. The generation mechanism is different in development and production versions of the library. For example, for the significant CSS class, the name is generated as MyStyles_significant in the development version, and as something like n2 in the production version. The names are generated when the style definition class is activated for the first time and they remain the same no matter how many times the class is activated and deactivated. How the names are generated depends on in what class they were first declared and this becomes very important when we start inheriting style definitions.

Style definition inheritance

Let’s look at a simple example and see what Mimcss does in the presence of inheritance:

class Base extends css.StyleDefinition {   pad4 = this.$  class({ padding: 4 }) } class Derived extends Base {   pad8 = this.$  class({ padding: 8 }) } let derived = css.activate(Derived);

Nothing surprising happens when we activate the Derived class: the derived variable provides access to both the pad4 and the pad8 CSS classes. Mimcss generates a unique CSS class name for each of these properties. The names of the classes are Base_pad4 and Derived_pad8 in the development version of the library.

Interesting things start happening when the Derived class overrides a property from the base class:

class Base extends css.StyleDefinition {   pad = this.$  class({ padding: 4 }) } class Derived extends Base {   pad = this.$  class({ padding: 8 }) } let derived = css.activate(Derived);

There is a single name generated for the derived.pad.name variable. The name is Base_pad; however, the style is { padding: 8px }. That is, the name is generated using the name of the base class, while the style is taken from the derived class.

Let’s try another style definition class that derives from the same Base class:

class AnotherDerived extends Base {   pad = this.$  class({ padding: 16 }) } let anotherDerived = css.activate(AnotherDerived);

As expected, the anotherDerived.pad.name has the value of Base_pad and the style is { padding: 16px }. Thus, no matter how many different derived classes we may have, they all use the same name for the inherited properties, but different styles are assigned to them. This is the key Mimcss feature that allows us to use style definition inheritance for theming.

Creating themes in Mimcss

The main idea of theming in Mimcss is to have a theme declaration class that declares several CSS rules, and to have multiple implementation classes that are derived from the declaration while overriding these rules by providing actual styles values. When we need CSS class names, as well as other named CSS entities in our code, we can use the properties from the theme declaration class. Then we can activate either this or that implementation class and, voilà, we can completely change the styling of our application with very little code.

Let’s consider a very simple example that nicely demonstrates the overall approach to theming in Mimcss.: a theme simply defines the shape and style of an element’s border.

First, we need to create the theme declaration class. Theme declarations are classes that derive from the ThemeDefinition class, which itself derives from the StyleDefinition class (there is an explanation why we need the ThemeDefinition class and why themes should not derive directly from the StyleDefinition class, but this is a topic for another day).

class BorderTheme extends css.ThemeDefinition {   borderShape = this.$  class() }

The BorderTheme class defines a single CSS class, borderShape. Note that we haven’t specified any styles for it. We are using this class only to define the borderShape property type, and let Mimcss create a unique name for it. In a sense, it is a lot like a method declaration in an interface—it declares its signature, which should be implemented by the derived classes.

Now let’s define two actual themes—using SquareBorderTheme and RoundBorderTheme classes—that derive from the BorderTheme class and override the borderShape property by specifying different style parameters.

class SquareBorderTheme extends BorderTheme {   borderShape = this.$  class({     border: ["thin", "solid", "green"],     borderInlineStartWidth: "thick"   }) }  class RoundBorderTheme extends BorderTheme {   borderShape = this.$  class({     border: ["medium", "solid", "blue"],     borderRadius: 8 // Mimcss will convert 8 to 8px   }) }

TypeScript ensures that the derived classes can only override a property using the same type that was declared in the base class which, in our case, is an internal Mimcss type used for defining CSS classes. That means that developers cannot use the borderShape property to mistakenly declare a different CSS rule because it leads to a compilation error.

We can now activate one of the themes as the default theme:

let theme: BorderTheme = css.activate(SquareBorderTheme);

When Mimcss first activates a style definition class, it generates unique names for all of CSS entities defined in the class. As we have seen before, the name generated for the borderShape property is generated once and will be reused when other classes deriving from the BorderTheme class are activated.

The activate function returns an instance of the activated class, which we store in the theme variable of type BorderTheme. Having this variable tells the TypeScript compiler that it has access to all the properties from the BorderTheme. This allows us to write the following rendering code for a fictional component:

render() {   return <div>     <input type="text" className={theme.borderShape.name} />   </div> }

All that is left to write is the code that allows the user to choose one of the two themes and activate it.

onToggleTheme() {   if (theme instanceof SquareBorderTheme)     theme = css.activate(RoundBorderTheme);   else     theme = css.activate(SquareBorderTheme); }

Note that we didn’t have to deactivate the old theme. One of the features of the ThemeDefinition class (as opposed to the StyleDefintion class) is that for every theme declaration class, it allows only a single theme to be active at the same time. That is, in our case, either RoundBorderTheme or SquareBorderTheme can be active, but never both. Of course, for multiple theme hierarchies, multiple themes can be simultaneously active. That is, if we have another hierarchy with the ColorTheme declaration class and the derived DarkTheme and LightTheme classes, a single ColorTheme-derived class can be co-active with a single BorderTheme-derived class. However, DarkTheme and LightTheme cannot be active at the same time.

Referencing Mimcss themes

In the example we just looked at, we used a theme object directly but themes frequently define elements like colors, sizes, and fonts that can be referenced by other style definitions. This is especially useful for separating the code that defines themes from the code that defines styles for a component that only wants to use the elements defined by the currently active theme.

CSS custom properties are perfect for declaring elements from which styles can be built. So, let’s define two custom properties in our themes: one for the foreground color, and one for the background color. We can also create a simple component and define a separate style definition class for it. Here is how we define the theme declaration class:

class ColorTheme extends css.ThemeDefinition {   bgColor = this.$  var( "color")   frColor = this.$  var( "color") }

The $ var method defines a CSS custom property. The first parameter specifies the name of the CSS style property, which determines acceptable property values. Note that we don’t specify the actual values here; in the declaration class, we only want Mimcss to create unique names for the custom CSS properties (e.g. --n13) while the values are specified in the theme implementation classes, which we do next.

class LightTheme extends ColorTheme {   bgColor = this.$  var( "color", "white")   frColor = this.$  var( "color", "black") }  class DarkTheme extendsBorderTheme {   bgColor = this.$  var( "color", "black")   frColor = this.$  var( "color", "white") }

Thanks to the Mimcss (and of course TypeScript’s) typing system, developers cannot mistakenly reuse, say, the bgColor property with a different type; nor they can specify values that are not acceptable for a color type. Doing so would immediately produce a compilation error, which may save developers quite a few cycles (one of the declared goals of Mimcss).

Let’s define styles for our component by referencing the theme’s custom CSS properties:

class MyStyles extends css.StyleDefinition {   theme = this.$  use(ColorTheme)    container = this.$  class({     color: this.theme.fgColor,     backgroundColor: this.theme.bgColor,   }) }

The MyStyles style definition class references the ColorTheme class by calling the Mimcss $ use method. This returns an instance of the ColorTheme class through which all its properties can be accessed and used to assign values to CSS properties.

We don’t need to write the var() function invocation because it’s already done by Mimcss when the $ var property is referenced. In effect, the CSS class for the container property creates the following CSS rule (with uniquely generated names, of course):

.container {   color: var(--fgColor);   backgroundColor: var(--bgColor); }

Now we can define our component (in pseudo-React style):

class MyComponent extends Component {   private styles = css.activate(MyStyles);    componentWillUnmount()   {     css.deactivate(this.styles);   }    render()   {     return <div className={this.styles.container.name}>       This area will change colors depending on a selected theme.     </div>   } }

Note one important thing in the above code: our component is completely decoupled from the classes that implement actual themes. The only class our component needs to know about is the theme declaration class ColorTheme. This opens a door to easily “externalize” creation of themes—they can be created by third-party vendors and delivered as regular JavaScript packages. As long as they derive from the ColorTheme class, they can be activated and our component reflects their values.

Imagine creating a theme declaration class for, say, Material Design styles along with multiple theme classes that derive from this class. The only caveat is that since we are using an existing system, the actual names of the CSS properties cannot be generated by Mimcss—they must be the exact names that the Material Design system uses (e.g. --mdc-theme--primary). Thankfully, for all named CSS entities, Mimcss provides a way to override its internal name generation mechanism and use an explicitly provided name. Here is how it can be done with Material Design CSS properties:

class MaterialDesignThemeBase extends css.ThemeDefinition {   primary = this.$  var( "color", undefined, "mdc-theme--primary")   onPrimary = this.$  var( "color", undefined, "mdc-theme--on-primary")   // ... }

The third parameter in the $ var call is the name, which is given to the CSS custom property. The second parameter is set to undefined meaning we aren’t providing any value for the property since this is a theme declaration, and not a concrete theme implementation.

The implementation classes do not need to worry about specifying the correct names because all name assignments are based on the theme declaration class:

class MyMaterialDesignTheme extends MaterialDesignThemeBase {   primary = this.$  var( "color", "lightslategray")   onPrimary = this.$  var( "color", "navy")   // ... }

Multiple themes on one page

As mentioned earlier, only a single theme implementation can be active from among the themes derived from the same theme declaration class. The reason is that different theme implementations define different values for the CSS rules with the same names. Thus, if multiple theme implementations were allowed to be active at the same time, we would have multiple definitions of identically-named CSS rules. This is, of course, a recipe for disaster.

Normally, having a single theme active at a time is not a problem at all—it is likely what we want in most cases. Themes usually define the overall look and feel of the entire page and there is no need to have different page sections to use different themes. What if, however, we are in that rare situation where we do need to apply different themes to different parts of our page? For example, what if before a user chooses a light or dark theme, we want to allow them to compare the two modes side-by-side?

The solution is based on the fact that custom CSS properties can be redefined under CSS rules. Since theme definition classes usually contain a lot of custom CSS properties, Mimcss provides an easy way to use their values from different themes under different CSS rules.

Let’s consider an example where we need to display two elements using two different themes on the same page. The idea is to create a style definition class for our component so that we could write the following rendering code:

public render() {   return <div>     <div className={this.styles.top.name}>       This should be black text on white background     </div>     <div className={this.styles.bottom.name}>       This should be white text on black background     </div>   </div> }

We need to define the CSS top and bottom classes so that we redefine the custom properties under each of them taking values from different themes. We essentially want to have the following CSS:

.block {   backgroundColor: var(--bgColor);   color: var(--fgColor); }  .block.top {   --bgColor: while;   --fgColor: black; }  .block.bottom {   --bgColor: black;   --fgColor: white; }

We use the block class for optimization purposes and to showcase how Mimcss handles inheriting CSS styles, but it is optional.

Here is how this is done in Mimcss:

class MyStyles extends css.StyleDefinition {   theme = this.$  use(ColorTheme)    block = this.$  class({     backgroundColor: this.theme.bgColor,     color: this.theme.fgColor   })    top = this.$  class({     "++": this.block,     "--": [LightTheme],   })    bottom = this.$  class({     "++": this.block,     "--": [DarkTheme],   }) }

Just as we did previously, we reference our ColorTheme declaration class. Then we define a helper block CSS class, which sets the foreground and background colors using the custom CSS properties from the theme. Then we define the top and bottom classes and use the "++" property to indicate that they inherit from the block class. Mimcss supports several methods of style inheritance; the "++" property simply appends the name of the referenced class to our class name. That is, the value returned by the styles.top.name is "top block" where we’re combining the two CSS classes (the actual names are randomly generated, so it would be something like "n153 n459").

Then we use the "--" property to set values of the custom CSS variables. Mimcss supports several methods of redefining custom CSS properties in a ruleset; in our case, we just reference a corresponding theme definition class. This causes Mimcss to redefine all custom CSS properties found in the theme class with their corresponding values.

What do you think?

Theming in Mimcss is intentionally based on style definition inheritance. We looked at exactly how this works, where we get the best of both theming worlds: the ability to use alternate stylesheets alongside the ability to swap out CSS property values using an object-oriented approach.

At runtime, Mimcss applies a theme without changing the HTML whatsoever. At build-time, Mimcss leverages the well-tried and easy-to-use class inheritance technique. Please check out the Mimcss documentation for a much deeper dive on the things we covered here. You can also visit the Mimcss Playground where you can explore a number of examples and easily try your own code.

And, of course, tell me what you think of this approach! This has been my go-to solution for theming and I’d like to continue making it stronger based on feedback from developers like yourself.


The post Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,

Defining “View Source”

Last time there was a little flurry of activity around the concept of “View Source,” I did get the sense that not everyone was on the same page about what that even means. Jim Nielsen:

First, when we talk about “View Source” what precisely are we talking about? I think this is an important point to clarify, as it sometimes goes unsaid and therefore a lot of assumptions sneak into the conversation and we might realize we’re not all talking about the same thing.

There are three things that people might be talking about:

  1. View source code (the code that generates the HTML delivered over the network)
  2. View page source (the HTML delivered over the network)
  3. View runtime source (the living HTML, a.k.a the DOM)

I’ll assign what I think are the values of each are, as slices of a pie chart:

  1. 10%
  2. 5%
  3. 85%

Every major browser ships with built-in DevTools where you can easily peak at the “runtime source.” That’s where the vast bulk of value is to me. If browsers ever talked about removing that, I’m sure we’d all be up in arms. Even for non-developers, the existence of this tool might be the spark that grows baby web developers.

DevTools also provides a way to view the HTML delivered over the network, hence my hardline stance from before:

I literally don’t care at all about View Source and wouldn’t miss it if it was removed from browsers. I live in DevTools, and I’ll bet you do too. It entirely supersedes View Source, as you can quite literally view source inside it if you’d like.

Jim’s post explains the difference between all three types of “viewing source” in great detail. For sites that are built entirely from client-side JavaScript, viewing the HTML over the wire is nearly useless. But if you could see the whole codebase (say if it was open-source on GitHub), there is certainly value there.

Direct Link to ArticlePermalink


The post Defining “View Source” appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Responsive Designs and CSS Custom Properties: Defining Variables and Breakpoints

CSS custom properties (a.k.a. CSS variables) are becoming more and more popular. They finally reached decent browser support and are slowly making their way into various production environments. The popularity of custom properties shouldn’t come as a surprise, because they can be really helpful in numerous use cases, including managing color palettes, customizing components, and theming. But CSS variables can also be really helpful when it comes to responsive design.

Article Series:

  1. Defining Variables and Breakpoints (This Post)
  2. Building a Flexible Grid System (Coming Tomorrow!)

Let’s consider an <article> element with a heading and a paragraph inside:

<article class="post"> 	<h2 class="heading">Post's heading</h2> 	<p class="paragraph"> 		Lorem ipsum dolor sit amet, consectetur adipisicing elit. 		Laudantium numquam adipisci recusandae officiis dolore tenetur, 		nisi, beatae praesentium, soluta ullam suscipit quas? 	</p> </article>

It’s a common scenario in such a case to change some sizes and dimensions depending on the viewport’s width. One way to accomplish this is by using media queries:

.post { 	padding: 0.5rem 1rem; 	margin: 0.5rem auto 1rem; }  .heading { 	font-size: 2rem; }  @media (min-width: 576px) { 	.post { 		padding: 1rem 2rem; 		margin: 1rem auto 2rem; 	} 	 	.heading { 		font-size: 3rem; 	} }

See the Pen
#1 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

Such an approach gives us an easy way to control CSS properties on different screen sizes. However, it may be hard to maintain as the complexity of a project grows. When using media queries, keeping code readable and DRY at the same time quite often turns out to be challenging.

The most common challenges when scaling this pattern include:

  • Repeated selectors: Apart from bloating code with multiple declarations, it also makes future refactoring more difficult, e.g. every time a class name changes it requires remembering to update it in multiple places.
  • Repeated properties: Notice that when overwriting CSS rules within media queries, it requires repeating the entire declaration (e.g. font-size: 3rem;) even though it’s just the value (3rem) that actually changes.
  • Repeated media queries: To keep responsive styles contextual, it’s a common practice to include the same media queries in multiple places, close to the styles they override. Unfortunately, it not only makes code heavier, but also might make breakpoints much harder to maintain. On the other hand, keeping all responsive styles in one place, away from their original declarations, may be very confusing: we end up with multiple references to the same elements sitting in completely different places.

We can argue that repeated declarations and queries shouldn’t be such a big deal with proper file compression enabled, at least as long as we’re referring to performance. We can also merge multiple queries and optimize your code with post-processing tools. But wouldn’t it be easier to avoid these issues altogether?

There’s a lot of ways to avoid the issues listed above. One of them, that we will explore in this article, is to use CSS custom properties.

Using CSS variables for property values

There are plenty of amazing articles on the web explaining the concept of CSS custom properties. If you haven’t got chance to get familiar with them yet, I would recommend starting with one of the beginner articles on this topic such as this awesome piece by Serg Hospodarets as we are not going to get into details of the basic usage in this article.

The most common way of utilizing CSS custom properties in responsive design is to use variables to store values that change inside of media queries. To accomplish this, declare a variable that holds a value that is supposed to change, and then reassign it inside of a media query:

:root {   --responsive-padding: 1rem; }  @media (min-width: 576px) {                                :root {     --responsive-padding: 2rem;   } }  .foo { 	padding: var(--responsive-padding); }

Assigning variables to the :root selector is not always a good idea. Same as in JavaScript, having many global variables is considered a bad practice. In real life, try to declare the custom properties in the scope they will actually be used.

This way, we are avoiding multiple rules of the .foo class. We are also separating the logic (changing values) from the actual designs (CSS declarations). Adapting this approach in our example from above gives us the following CSS:

.post { 	--post-vertical-padding: 0.5rem; 	--post-horizontal-padding: 1rem; 	--post-top-margin: 0.5rem; 	--post-bottom-margin: 1rem; 	--heading-font-size: 2rem; }  @media (min-width: 576px) { 	.post { 		--post-vertical-padding: 1rem; 		--post-horizontal-padding: 2rem; 		--post-top-margin: 1rem; 		--post-bottom-margin: 2rem; 		--heading-font-size: 3rem; 	} }  .post { 	padding: var(--post-vertical-padding) var(--post-horizontal-padding); 	margin: var(--post-top-margin) auto  var(--post-bottom-margin); }  .heading { 	font-size: var(--heading-font-size); }

See the Pen
#2 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

Notice that the use of variables in shorthand properties (e.g. padding, margin or font) allow some very interesting repercussions. As custom properties may hold almost any value (more on this later), even an empty string, it’s unclear how the value of a shorthand property will be separated out into longhand properties that are used in the cascade later. For example, the auto used in the margin property above may turn out to be a top-and-bottom margin, a left-and-right margin, a top margin, a right margin, a bottom margin or a left margin — it all depends on the values of the custom properties around.

It’s questionable whether the code looks cleaner than the one from the previous example, but on a larger scale, it’s definitely more maintainable. Let’s try to simplify this code a bit now.

Notice that some values are repeated here. What if we try to merge duplicate variables together? Let’s consider the following alteration:

:root { 	--small-spacing: 0.5rem; 	--large-spacing: 1rem; 	--large-font-size: 2rem; }  @media (min-width: 576px) { 	:root { 		--small-spacing: 1rem; 		--large-spacing: 2rem; 		--large-font-size: 3rem; 	} }  .post { 	padding: var(--small-spacing) var(--large-spacing); 	margin: var(--small-spacing) auto  var(--large-spacing); }  .heading { 	font-size: var(--large-font-size); }

See the Pen
#3 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

It looks cleaner but is it actually better? Not necessarily. For the sake of flexibility and readability, this may not be the right solution in every case. We definitely shouldn’t merge some variables just because they accidentally turned out to hold the same values. Sometimes, as long as we’re doing this as a part of a well thought out system, it may help us simplify things and preserve consistency across the project. However, in other cases, such a manner may quickly prove to be confusing and problematic. Now, let’s take a look at yet another way we can approach this code.

Using CSS variables as multipliers

CSS custom properties are a fairly new feature to the modern web. One of the other awesome features that rolled out in the last years is the calc() function. It lets us perform real math operations in live CSS. In terms of the browser support, it’s supported in all browsers that support CSS custom properties.

calc() tends to play very nicely with CSS variables, making them even more powerful. This means we can both use calc() inside custom properties and custom properties inside calc()!

For example, the following CSS is perfectly valid:

:root { 	--size: 2; } 	 .foo { 	--padding: calc(var(--size) * 1rem); /* 2 × 1rem = 2rem */ 	padding: calc(var(--padding) * 2);   /* 2rem × 2 = 4rem */ }

Why does this matter to us and our responsive designs? It means that we can use a calc() function to alter CSS custom properties inside media queries. Let’s say we have a padding that should have a value of 5px on mobile and 10px on desktop. Instead of declaring this property two times, we can assign a variable to it and multiply it by two on larger screens:

:root { 	--padding: 1rem; 	--foo-padding: var(--padding); }  @media (min-width: 576px) {                              	:root { 		--foo-padding: calc(var(--padding) * 2); 	} }  .foo { 	padding: var(--foo-padding); }

Looks fine, however all the values (--padding, calc(--padding * 2)) are away from their declaration (padding). The syntax may also be pretty confusing with two different padding variables (--padding and --foo-padding) and an unclear relationship between them.

To make things a bit clearer, let’s try to code it the other way around:

:root { 	--multiplier: 1; }  @media (min-width: 576px) {                              	:root { 		--multiplier: 2; 	} }  .foo { 	padding: calc(1rem * var(--multiplier)); }

This way, we accomplished the same computed output with much cleaner code! So, instead of using a variable for an initial value of the property (1rem), a variable was used to store a multiplier (1 on small screens and 2 on larger screens). It also allows us to use the --multiplier variable in other declarations. Let’s apply this technique to paddings and margins in our previous snippet:

:root { 	--multiplier: 1; }  @media (min-width: 576px) { 	:root { 		--multiplier: 2; 	} }  .post { 	padding: calc(.5rem * var(--multiplier)) 						calc(1rem  * var(--multiplier)); 	margin:  calc(.5rem * var(--multiplier)) 						auto 						calc(1rem  * var(--multiplier)); }

Now, let’s try to implement the same approach with typography. First, we’ll add another heading to our designs:

<h1 class="heading-large">My Blog</h1> <article class="post"> 	<h2 class="heading-medium">Post's heading</h2> 	<p class="paragraph"> 		Lorem ipsum dolor sit amet, consectetur adipisicing elit. 		Laudantium numquam adipisci recusandae officiis dolore tenetur, 		nisi, beatae praesentium, soluta ullam suscipit quas? 	</p> </article>

With multiple text styles in place, we can use a variable to control their sizes too:

:root { 	--headings-multiplier: 1; }  @media (min-width: 576px) { 	:root { 		--headings-multiplier: 3 / 2; 	} }  .heading-medium { 	font-size: calc(2rem * var(--headings-multiplier)) }  .heading-large { 	font-size: calc(3rem * var(--headings-multiplier)) }

You may have noticed that 3 / 2 is not a valid CSS value at all. Why does it not cause an error then? The reason is that the syntax for CSS variables is extremely forgiving, which means almost anything can be assigned to a variable, even if it’s not a valid CSS value for any existing CSS property. Declared CSS custom properties are left almost entirely un-evaluated until they are computed by a user agent in certain declarations. So, once a variable is used in a value of some property, this value will turn valid or invalid at the computed-value time.

Oh, and another note about that last note: in case you’re wondering, I used a value of 3 / 2 simply to make a point. In real life, it would make more sense to write 1.5 instead to make the code more readable.

Now, let’s take a look at the finished live example combining everything that we discussed above:

See the Pen
#4 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

Again, I would never advocate for combining calc() with custom properties to make the code more concise as a general rule. But I can definitely imagine scenarios in which it helps to keep code more organized and maintainable. This approach also allows the weight of CSS to be significantly reduced, when it’s used wisely.

In terms of readability, we can consider it more readable once the underlying rule is understood. It helps to explain the logic and relations between values. On the other hand, some may see it as less readable, because it’s tough to instantly read what a property holds as a value without first doing the math. Also, using too many variables and calc() functions at once may unnecessarily obscure code and make it harder to understand, especially for juniors and front-end developers who are not focused on CSS.

Conclusion

Summing up, there’s a lot of ways to use CSS custom properties in responsive design, definitely not limited to the examples shown above. CSS variables can be used simply to separate the values from the designs. They can also be taken a step further and be combined with some math. None of the presented approaches is better nor worse than the others. The sensibility of using them depends on the case and context.

Now that you know how CSS custom properties can be used in responsive design, I hope you will find a way to introduce them in your own workflow. Next up, we’re going to look at approaches for using them in reusable components and modules, so stay tuned for the next post tomorrow!

The post Responsive Designs and CSS Custom Properties: Defining Variables and Breakpoints appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]