Chapter 3: Custom Properties

For years, variables were one of the most commonly requested CSS features. Variables make it easier to manage colors, fonts, size, and animation values, and to ensure their consistency across a codebase.

It took years to work through the details of the syntax and decide how variables would fit into existing rules governing the cascade and inheritance. Now they’re available to developers in the form of CSS custom properties.

In this chapter, we’ll discuss the syntax of CSS custom properties. We’ll look at:

  • how to define properties and set default values for those properties
  • how the rules of the cascade and inheritance work with custom properties
  • how to use custom properties with media queries

By the end, you should have a good grasp of how to use custom properties in your projects.

Broswer Support

Browser support for custom properties is robust, existing in the latest versions of every major browser. Support is not, however, available in older yet recently released browser versions that may still be widely used by your site’s audience. Versions of Microsoft Edge prior to 15 and Safari prior to version 9.1 lack support entirely. The same is true for any version of Internet Explorer. Microsoft Edge 15 has support, but also has a few documented bugs.

Defining a Custom Property

To define a custom property, select a name and prefix it with two hyphens. Any alphanumeric character can be part of the name. Hyphen (-) and underscore (_) characters are also allowed. A broad range of unicode characters can be part of a custom property name, including emojis. For the sake of clarity and readability, stick to alphanumeric names.

Here’s an example:

--primarycolor: #0ad0f9ff; /* Using #rrggbbaa color notation */

The -- indicates to the CSS parser that this is a custom property. The value of the property will replace the property wherever it’s used as a variable.

Custom property names are case-sensitive. In other words, --primaryColor and --primarycolor are considered two distinct property names. That’s a departure from traditional CSS, in which property and value case doesn’t matter. It is, however, consistent with the way ECMAScript treats variables.

As with other properties, such as display or font, CSS custom properties must be defined within a declaration block. One common pattern is to define custom properties within a ruleset that uses the :root psuedo-element as a selector:

:root {
    --primarycolor: #0ad0f9ff;
}

:root is a pseudo-element that refers to the root element of the document. For HTML documents, that’s the html element. For SVG documents, it’s the svg element. By using :root, properties are immediately available throughout the document.

Using Custom Properties

To use a custom property value as a variable, we need to use the var() function. For instance, if we wanted to use our --primarycolor custom property as a background color, we’d use the following:

body {
    background-color: var(--primarycolor);
}

Our custom property’s value will become the computed value of the background-color property.

To date, custom properties can only be used as variables to set values for standard CSS properties. You can’t, for example, store a property name as a variable and then reuse it. The following CSS won’t work:

:root {
    --top-border: border-top; /* Can't set a property as custom property's value */
    var(--top-border): 10px solid #bc84d8 /* Can't use a variable as a property */
}

You also can’t store a property–value pair as a variable and reuse it. The following example is also invalid:

:root {
   --text-color: 'color: orange'; /* Invalid property value */
}
body {
  var(--text-color); /* Invalid use of a property */
}

Lastly, you also can’t concatenate a variable as part of a value string:

:root {
    --base-font-size: 10;
}
body {
    font: var(--base-font-size)px / 1.25 sans-serif; /* Invalid CSS syntax. */
}

Custom properties were designed to be used as properties that are parsed according to the CSS specification. Should the CSS Extensions specification be adopted by browser vendors, we could someday use custom properties to create custom selector groups, or custom at-rules. For now, however, we’re limited to using them as variables to set standard property values.

Setting a Fallback Value

The var() function actually accepts up to two arguments. The first argument should be a custom property name. The second argument is optional, but should be a declaration value. This declaration value functions as a kind of fallback value if the custom property value hasn’t been defined.

Let’s take the following CSS:

.btn__call-to-action {
    background: var(--accent-color, salmon);
}

If --accent-color is defined—let’s say its value is #f30—then the fill color for any path with a .btn__call-to-action class attribute will have a red-orange fill. If it’s not defined, the fill will be salmon.

Declaration values can also be nested. In other words, you can use a variable as the fallback value for the var function:

body {
    background-color: var(--books-bg, var(--arts-bg));
}

In the CSS above, if --books-bg is defined, the background color will be set to the value of the --books-bg property. If not, the background color will instead be whatever value was assigned to --arts-bg. If neither of those are defined, the background color will be the initial value for the property—in this case transparent.

Something similar happens when a custom property is given a value that’s invalid for the property it’s used with. Consider the following CSS:

:root {
    --footer-link-hover: #0cg; /* Not a valid color value. */
}
a:link {
     color: blue;
}
a:hover {
    color: red;
}
footer a:hover {
    color: var(--footer-link-hover);
}

In this case, the value of the --footer-link-hover property is not a valid color. In Microsoft Edge, the hover state color for footer links will be inherited from the a:hover selector. In most other browsers, the hover state color will be inherited from the text color of the body element.

Custom Properties and the Cascade

Custom properties also adhere to the rules of the cascade. Their values can be overridden by subsequent rules:

:root {
    --text-color: #190736; /* navy */
}
body {
   --text-color: #333;  /* Dark gray */
}
body {
  color: var(--text-color);
}

In the example above, our body text would be dark gray. We can also reset values on a per-selector basis. Let’s add a couple more rules to this CSS:

:root {
    --text-color: #190736; /* navy */
}
body {
   --text-color: #333;  /* Dark gray */
}
p {
  --text-color: #f60; /* Orange */
}
body {
  color: var(--text-color);
}
p {
  color: var(--text-color)
}

In this case, any text that’s wrapped in p element tags would be orange. But text within div or other elements would still be dark gray.

It’s also possible to set the value of a custom property using the style attribute—for example, style="--brand-color: #9a09af"—which can be useful in a component-based, front-end architecture.

Using Custom Properties with JavaScript

Again: custom properties are CSS properties, and we can interact with them as such. For example, we can use the CSS.supports() API to test whether a browser supports custom properties:

const supportsCustomProps = CSS.supports('--primary-text: #000');

// Logs true to the console in browsers that support it
console.log(supportsCustomProps);

You can learn more about the CSS.supports() API, as well as the @supports CSS rule, in Chapter 8, Applying CSS Conditionally.

We can also use the setProperty() method to set a custom property value:

document.body.style.setProperty('--bg-home', 'whitesmoke');

Using removeProperty() works similarly. Just pass the custom property name as the argument:

document.body.style.removeProperty('--bg-home');

Alas, you can’t set custom properties using square-bracket syntax or camel-cased properties of the style object. In other words, neither document.body.style.--bg-home nor document.body.style[--bg-home] will work.

Custom Properties and Components

Components are an emerging pattern in front-end development. JavaScript frameworks like React, Vue, and Angular let developers use JavaScript to create reusable, sharable blocks of HTML, often with CSS that’s defined at the component level.

Here’s an example of a React component, written in JSX. JSX is a syntax extension for JavaScript. It resembles XML, and gets compiled into HTML or XML. It’s a common way of building React components:

import React from 'react';

/* Importing the associated CSS into this component */
import '../css/field-button.css';

class FieldButtonGroup extends React.Component {
    render() {
        return (
            <div className="field__button__group">
                <label htmlFor={this.props.id}>{this.props.labelText}</label>
                <div>
                    <input type={this.props.type} name={this.props.name} id={this.props.id}
                    onChange={this.props.onChangeHandler} />
                    <button type="submit">{this.props.buttonText}</button>
                   </div>
               </div>
        );
    }
}

export default FieldButtonGroup;

More on JavaScript Frameworks

SitePoint.com’s React and Angular hubs are chock-full of resources for getting you up to speed with those frameworks. To get started with Vue.js, check out “Getting up and Running with the Vue.js 2.0 Framework”, by Jack Franklin.

Our React component imports CSS into a JavaScript file. When compiled, the contents of field-button.css will be loaded inline. Here’s one possible way we might use this with custom properties:

.field__button__group label {
    display: block;
}
.field__button__group button {
    flex: 0 1 10rem;
    background-color: var(--button-bg-color, rgb(103, 58, 183));
    color: #fff;
    border: none;
}

In this example, we’ve used a custom property—--button-bg-color—for the button’s background color, along with a default color in case --button-bg-color never gets defined. From here we can set a value of --button-bg-color, either in a global stylesheet or locally via the style attribute.

Let’s set the value as a React “prop.” React props (short for properties) mimic element attributes. They’re a way to pass data into a React component. In this case, we’ll add a prop named buttonBgColor:

import FieldButtonGroup from '../FieldButtonGroup';

class NewsletterSignup extends React.Component {
    render() {
        // For brevity, we've left out the onChangeHandler prop.
        return (
            <FieldButtonGroup type="email" name="newsletter" id="newsletter"
            labelText="E-mail address" buttonText="Subscribe"
            buttonBgColor="rgb(75, 97, 108)"
        );
    }
}

export default NewsletterSignup;

We do need to update our FieldButtonGroup to support this change:

class FieldButtonGroup extends React.Component {
    render() {
        /*
        In React, the style attribute value must be set using a JavaScript object in which the 
        object keys are CSS properties. Properties should either be camelCased (e.g. 
        backgroundColor) or enclosed in quotes.
        */

        const buttonStyle = {
            '--button-bg-color': this.props.buttonBgColor
        };

        return (
            <div className="field__button__group">
                <label htmlFor={this.props.id}>{this.props.labelText}</label>
                <div>
                    <input type={this.props.type} name={this.props.name} id={this.props.id}
                    onChange={this.props.onChangeHandler} />
                    <button type="submit" style={buttonStyle}>{this.props.buttonText}</button>
                   </div>
               </div>
        );
    }
}

In the code above, we’ve added a buttonStyle object that holds the name of our custom property and sets its value to that of our buttonBgColor prop, and a style attribute to our button.

Using the style attribute probably runs counter to everything you’ve been taught about writing CSS. A selling point of CSS is that we can define one set of styles for use across multiple HTML and XML documents. The style attribute, on the other hand, limits the scope of that CSS to the element it’s applied to. We can’t reuse it. And we can’t take advantage of the cascade.

But in a component-based, front-end architecture, one component may be used in multiple contexts, by multiple teams, or may even be shared across client projects. In those cases, you may want to combine the “global scope” of the cascade with the narrow “local scope” provided by the style attribute.

Setting the custom property value with the style attribute limits the effect to this particular instance of the FieldButtonGroup component. But because we’ve used a custom property instead of a standard CSS property, we still have the option of defining --button-bg-color in a linked stylesheet instead of a component prop.

Using Custom Properties and Media Queries

We can also use custom properties with media queries. We'll take a deeper look at media queries later, in Chapter 8. For example, you could set a different padding size for button elements when the user’s pointer device has a coarse-grained accuracy (such as a finger):

:root {
    --button-padding: .5rem 1rem;
}
@media screen and (pointer: coarse) {
    :root {
        --button-padding: 1rem 2rem;
    }
}
button {
    padding: var(--button-padding);
}

In this example, buttons displayed on devices with a coarse pointer input mechanism will have a larger amount of padding than those without.

Similarly, we can use custom properties to change the base font size for screen versus print:

:root {
    --base-font-size: 10px;
}
@media print {
    :root {
        --base-font-size: 10pt;
    }
}
html {
    font: var(--base-font-size) / 1.5 sans-serif;
}
body {
    font-size: 1.6rem;
}

In this case, we’re using media-appropriate units for print and screen. For both media, we’ll use a base font size of 10 units—pixels for screen, points for print—and use the value of --base-font-size: to set a starting size for our root element (html). We can then use rem units to size our typography relative to the base font size.

Rem Values

As defined in the CSS Values and Units Module Level 3 specification, a rem unit is always “equal to the computed value of font-size on the root element.” If the root element’s computed value of font-size is 10px, 1.6rem will create a computed value of 16px.

As you can see, custom properties can often lead to simpler, more maintainable CSS.

Conclusion

Custom properties take one of the best features of pre-processors—variables—and make them native to CSS. With custom properties, we can:

  • create reusable, themed components
  • easily adjust padding, margins, and typography for a range of viewport sizes and media
  • improve the consistency of color values in our CSS

Variables have a range of applications, and are particularly useful in component-based design systems.

Now that you have an understanding of custom properties, we’ll discuss some ways to work with text using CSS. The next chapter will discuss writing modes and text orientation, and the impact they have on CSS layout.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.202.187