CSS Custom properties are a feature that allows you to define variables that you can use in CSS. Custom properties are native in the browser, unlike variables in CSS preprocessors like SASS.
Custom properties will empower your projects, save you time and make your CSS more flexible and powerful.
- Describe CSS Custom Properties their features and use cases
- Use CSS Custom properties in real-world applications
- Use Math operations in CSS with calc()
- Use Reset CSS to remove browser inconsistencies
The examples presented will apply custom properties to the design of buttons. Before we start styling buttons we need to answer these questions:
- What's a button?
- When do we use them?
- What do they look like?
Before you start to design your buttons it's good to take a look at what other people are doing. Take a look at these:
- https://getbootstrap.com/docs/4.5/components/buttons/
- https://get.foundation/sites/docs/button.html
- https://material.io/components/buttons
Now answer these questions:
- What properties do you see here?
- What different styles are offered?
CSS Custom Properties let you define variables in CSS. Unlike variables in other programming languages custom properties act like properties in CSS, and just like properties if the value of a custom property changes, the page updates to reflect that change.
Properties names must begin with --
, rest of the name can be anything that would normally work in CSS (think: kabob-case).
CSS Custom properties must be defined in a block.
--color_primary: rgba(123, 37, 44, 1.0); /* BAD! */
body {
--color_primary: rgba(123, 37, 44, 1.0); /* Good! */
}
The block where a custom property is defined determines the scope of that property.
body {
/* Accessible to body and it's descendants */
--color-primary: rgba(123, 37, 44, 1.0);
--font-size: 18px;
}
h1 {
/* Available to all h1 and their descendants */
--font-size: 2em;
}
Assigning a value is like setting the value of a property in CSS.:root
is a special selector that represents the root of your CSS scope. All other elements are descendants :root
. Defining variables here make them accessible to all other elements. Think of this as global scope.
:root {
--color: red;
--primary-color: rgba(123, 37, 44, 0.7);
--size: 121px;
--base-font-size: 16px;
--large-font-size: 1.85em;
--number-of-columns: 4;
--golden-ratio: 1.618;
}
Using custom properties like this is like defining global constants.
Any value that would work in CSS can be assigned to a property.
:root {
/* Define custom properties on :root */
--golden-ratio: 1.618; /* number no unit */
--base-font-size: 16px; /* number with a unit */
--bg-color: #333; /* Color */
--base-font: Helvetica; /* Name or string */
}
:root
is a pseudo-element that matches the root element of the document tree. This is identical to the <html>
element. :root
has a higher specificity.
By declaring custom properties on :root
they become global and available everywhere.
Otherwise declaring a custom property on an element makes it available to that element and inherited by the element's descendants, but unavailable to it's ancestors.
To access the value of a custom property use the var()
function.
.some-class {
width: var(--size);
color: var(--color);
background-color: var(--bg-color);
}
For example, you might define some values in :root
then use those values throughout the rest of your stylesheet.
:root {
--golden-ratio: 1.618;
--base-font-size: 16px;
--large-font-size: 1.85em;
...
}
.main {
--number-of-columns: 4;
...
grid-template-columns: repeat(var(--number-of-columns), 1fr);
}
.alert {
--color: red;
--size: 121px;
...
color: var(--color);
width: var(--size);
...
}
The var function can also take a fallback value that is used if the custom property is not found. Here is an example:
:root {
--color: var(--undefined-var, tomato);
}
In the example above --color
is given the value tomato
assuming that that the custom property --undefined-var
is not defined.
The default button style is not very interesting and the text is too small. Giving the button some color and making it a little larger will make it easier to use.
button {
padding: 0.5em 0.75em;
background-color: #fff;
border: 1px solid;
color: cornflowerblue;
border-radius: 0.5em;
font-size: 1em;
transition: 300ms;
}
button:hover {
background-color: cornflowerblue;
color: #fff;
}
Here you have a button with a base style and a simple hover.
<button>Hello World</button>
Some custom properties will make this easier to manage. The button uses the same color for the background and foreground but switches these on hover.
button {
--bg-color: #fff;
--fg-color: cornflowerblue;
padding: 0.5em 0.75em;
background-color: var(--bg-color);
border: 1px solid;
color: var(--fg-color);
border-radius: 0.5em;
font-size: 1em;
transition: 300ms;
&:hover {
background-color: var(--fg-color);
color: var(--bg-color);
}
}
Now the colors can be edited in one location.
You'll often want buttons with different colors for different purposes. Look at the button styles in Bootstrap. Your goal is to emulate some of these.
To change the colors and other styles of buttons using custom properties become very flexible.
Using a class name:
button.warning {
--fg-color: tomato; /* Red button */
}
button.action {
--fg-color: yellowgreen; /* Green button */
}
<button>Login</button>
<button class="warning">Delete</button>
<button class="action">Buy Now!</button>
In your button example create classes for:
- Success
- Alert
- Dark
What if you want to have an inverted style for your buttons? Using custom properties you can add a class that switches where the colors are applied.
Here the .invert
class just sets the color
and background-color
properties but swaps which color is used where. These rules override the other since a selector with the class is more specific.
button.invert {
color: var(--bg-color);
background-color: var(--fg-color);
}
button.invert:hover {
background-color:var(--bg-color);
color: var(--fg-color);
}
<button class="warning invert">Delete</button>
Add an inverted style to your button styles
What if you want to customize the color of the button? You could write another class or you could set color properties inline.
<button style="--bg-color: violet; --fg-color: #fff">Use Locaton</button>
If your code defines colors as a theme you can use those inside your buttons by assigning their value to the properties used by the button.
:root {
--primary-color: cornflowerblue;
--foreground-color: #fff;
}
button {
--bg-color: var(--foreground-color);
--fg-color: var(--primary-color);
...
}
Now you can change the colors of all buttons and other elements that use the --primary-color
, or change the color of any button by changing it's --bg-color
.
For all of the challenges here add an example and sample code to your CSS framework sample.
Take a look at the buttons defined in Bootstrap. They have 9 styles:
- Primary
- Secondary
- Success
- Danger
- Warning
- Info
- Light
- Dark
- Link (this is not a button)
Your goal is to define a style for all of these using custom properties. You'll need to define colors for each and think of a style. Don't overthink this. It's more important to get this done than it is to create new and innovative button styles. If you're having trouble thinking of styles copy Bootstrap!
Your buttons should have the following features:
- hover
- transition
- Uses custom properties
- Has an inverted or outline class/style
Stretch Challenge
Take a look at the Bootstrap button it has a large and small size.
https://getbootstrap.com/docs/4.0/components/buttons/#sizes
Implement large and small button sizes in your CSS framework.
Sometimes buttons are disabled. Bootstrap does this.
https://getbootstrap.com/docs/4.0/components/buttons/#disabled-state
Implement a disabled state in your framework.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<style>
:root {
/* Custom property names must begin with --
Defining custom proeprties in :root makes them available
everywhere like global constants
Anything can be a value for a cusom property */
--color-action: tomato;
--font-size: 20px;
--number: 2;
--size: 200px;
--easing: cubic-bezier(0.075, 0.82, 0.165, 1);
--font-stack: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--color-primary: cadetblue;
--color-light: white;
}
body {
/* Access the value of a custom property with var() */
font-family: var(--font-stack);
font-size: var(--font-size);
}
a {
color: var(--color-primary);
}
</style>
<p>Custom <a href="">properties</a>.</p>
<style>
button {
background-color: var(--color-action);
width: var(--size);
font-size: var(--font-size);
}
</style>
<button>Button 1</button>
<p>This button uses the class "blue" which changes cascades a new
value for --color-action. The button uses the color cornflowerblue instead.</p>
<style>
.blue {
--color-action: cornflowerblue;
}
</style>
<button class="blue">Blue Button</button>
<p>If a property might not be defined you can supply a fallback value.</p>
<style>
h1 {
/* color: var(--not-defined, gold); */
}
</style>
<h1>Uses undefined var</h1>
<style>
.button {
/* Create a localy scoped var from some global vars */
/* Notice these have fallbacks to set a default */
--fg-color: var(--button-fg-color, var(--color-primary));
--bg-color: var(--button-bg-color, var(--color-light));
display: inline-block;
color: var(--fg-color);
background-color: var(--bg-color);
border:1px solid;
border-radius: 0.5rem;
padding: 1rem;
text-decoration: none;
}
.button:hover {
/* Here we use the local vars again */
background-color: var(--fg-color);
color: var(--bg-color);
}
</style>
<a class="button" href="#">Anchor tag</a>
<p>You can override the default colors in a few ways.</p>
<style>
.warning {
/* Use a class to set the fg and bg colors */
--button-fg-color: gold;
--button-bg-color: rgb(32, 154, 142);
}
.danger {
--button-fg-color: white;
--button-bg-color: tomato;
}
</style>
<a class="button warning" href="#">Anchor tag</a>
<a class="button danger" href="#">Anchor tag</a>
<!-- Use an inline style -->
<a
class="button"
style="--button-fg-color: purple; --button-bg-color: yellowgreen"
href="#">Anchor tag</a>
<!-- It is also possible to override the default colors in other
ways using the rules of selectors. -->
<style>
.card {
--fg-color: var(--some-color, #333);
--bg: var(--some-bg, #eee);
color: var(--fg-color);
background: var(--bg);
padding: 2rem;
margin: 1rem;
display: inline-block;
}
.dark {
--fg-color: #eee;
--bg: #333;
}
.grad {
--fg-color:#333;
--bg: linear-gradient(#eee, #ccc);
}
.grad.dark { /* 0 0 2 0 */
--fg-color:#eee;
--bg: linear-gradient(#333, #666);
}
</style>
<div class="card">
<h1>Hello</h1>
<p>This is an example card</p>
</div>
<div class="card dark">
<h1>World</h1>
<p>This is an example card</p>
</div>
<div class="card grad">
<h1>Foo</h1>
<p>This is an example card</p>
</div>
<div class="card grad dark">
<h1>Foo</h1>
<p>This is an example card</p>
</div>
</body>
</html>
CSS supports basic math through the calc()
method. You can use +
, -
, *
, and /
.
.heading {
font-size: calc(16px * 1.85);
}
.input[type=text] {
padding: calc(1em * 1.5);
}
.alert {
margin: calc(1em + 5px);
}
The beauty of calc()
is the ability to mix units! It may not seem like much but ask yourself what you would need to do to make this calculation:
calc(1em * 1.5px + 2%)
This is more complicated than it looks. Before you could solve the math above you need to convert all values to a common unit! With calc()
this happens automatically, like magic!
Custom properties work with calc()
.
.heading {
--size: 16px;
font-size: calc(var(--size) * 1.85);
}
.input[type=text] {
--scale: 1.5;
padding: calc(1em * var(--scale));
}
Pro tip! If you have a value that is just a number without a unit you can convert it to a number using calc()
like this:
.box {
--size: 100;
height: calc(var(--size) * 1px);
}
The code sample above changes the value of --size
from a unitless value of 100
into 100px
.
CSS now has more math functions. See the list here. This includes: min()
, max()
, clamp()
, round()
, mod()
, rem()
, sin()
, cos()
, tan()
, atan()
, asin()
, acos()
, atan2()
, pow()
, sqrt()
, hypot()
, log()
, exp()
, abs()
, and sign()
.
With JS you can set any CSS property on an element.
const box = document.getElementById('box')
box.style.width = '100px'
All CSS properties work here. Names are converted to camelcase.
box.style.borderWidth = '1em' // border-wdth
box.style.borderColor = 'red' // border-color
An alternate method is to use: element.setProperty()
const box = document.getElementById('box')
box.setProperty('width', '100px')
box.setProperty('border-width', '1em')
box.setProeprty('border-color', 'red')
...
Custom properties don't exist in the standard JS world and even if they did you couldn't use the --
in the name. So element.style.--size
is not going to work. Use element.setProperty()
for setting custom properties.
const box = document.getElementById('box')
box.setProperty('--primary-color', '#ff8844')
box.setProperty('--size', '230px')
box.setProeprty('--font-family', 'Helvetica')
You can also get the value of a custom property, or any property via JS.
const box = document.getElementById('box')
const size = box.getProperty('--size') // get a custom property
const color = box.getProperty('color') // get a standard property
The reason HTML that you write looks the way it does is because the browser applies default CSS styles to elements.
These styles are not always consistent across browsers. While the W3C defines the standards for the web it's browser makers that implement those standards. There are a lot of moving pieces and things are not always consistent across browsers and platforms.
This can be a problem for you as a web developer. You want your products to look consistent across browsers where quirks and inconsistencies create small but sometimes important differences.
A good strategy to eliminate some problems is to eliminate some or all of the default styles applied by the browser.
Take a look at the default styles used by browsers:
https://bitsofco.de/a-look-at-css-resets-in-2018/
Read the article above and discuss it with your pair.
Q: Why use a reset style sheet? Q: How would you apply this to your work? Q: What are the differences between these different resets?
Choose one of the stylesheets and apply it to the example code.
Take your CSS logo and move all of the values into CSS custom properties. Anything that is a value can be a property. Move these to a location in your code where it makes them easier to access.
Is this an improvement?
Take it a step further. Look at the cade and find values that are calculated from other values. Use calc()
and custom properties to figure these values.
Discussion:
Q: What happened here? Q: Was it useful? Q: Does this make a better CSS code?
The goal of this assignment is to start your CSS Framework. This assignment will continue through the next few classes. You'll be adding features with each new assignment until it is complete.
Start your Framework: Fonts Clarify what you are doing by looking at what other people are doing who are doing the same thing. Check out this awesome list of CSS Frameworks.