Tag: property

First Look At The CSS object-view-box Property

Ahmad Shadeed — doing what he always does so well — provides an early look at the object-view-box property, something he describes as a native way to crop an image in the browser with CSS.

The use case? Well, Ahmad wastes no time showing how to use the property to accomplish what used to require either (1) a wrapping element with hidden overflow around an image that’s sized and positioned inside that element or (2) the background-image route.

But with object-view-box we can essentially draw the image boundaries as we can with an SVG’s viewbox. So, take a plain ol’ <img> and call on object-view-box to trim the edges using an inset function. I’ll simply drop Ahmad’s pen in here:

Only supported in Chrome Canary for now, I’m afraid. But it’s (currently) planned to release in Chrome 104. Elsewhere:

To Shared LinkPermalink on CSS-Tricks


First Look At The CSS object-view-box Property originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , ,

When to Avoid the text-decoration Shorthand Property

In my recent article about CSS underline bugs in Chrome, I discussed text-decoration-thickness and text-underline-offset, two relatively new and widely-supported CSS properties that give us more control over the styling of underlines.

Let me demonstrate the usefulness of text-decoration-thickness on a simple example. The Ubuntu web font has a fairly thick default underline. We can make this underline thinner like so:

:any-link {   text-decoration-thickness: 0.08em; }
Showing two links, a default and one that decreases the text-decoration-thickness.

/explanation Throughout this article, I will use the :any-link selector instead of the a element to match hyperlinks. The problem with the a tag as a selector is that it matches all <a> elements, even the ones that don’t have a href attribute and thus aren’t hyperlinks. The :any-link selector only matches <a> elements that are hyperlinks. Web browsers also use :any-link instead of a in their user agent stylesheets.

Hover underlines

Many websites, including Google Search and Wikipedia, remove underlines from links and only show them when the user hovers a link. Removing underlines from links in body text is not a good idea, but it can make sense in places where links are more spaced apart (navigation, footer, etc.). With that being said, here’s a simple implementation of hover underlines for links in the website’s header:

header :any-link {   text-decoration: none; }  header :any-link:hover {   text-decoration: underline; }

But there’s a problem. If we tested this code in a browser, we’d notice that the underlines in the header have the default thickness, not the thinner style that we declared earlier. Why did text-decoration-thickness stop working after we added hover underlines?

Let’s look at the full CSS code again. Can you think of a reason why the custom thickness doesn’t apply to the hover underline?

:any-link {   text-decoration-thickness: 0.08em; }  header :any-link {   text-decoration: none; }  header :any-link:hover {   text-decoration: underline; }

The reason for this behavior is that text-decoration is a shorthand property and text-decoration-thickness its associated longhand property. Setting text-decoration to none or underline has the side effect of re-initializing the other three text decoration components (thickness, style, and color). This is defined in the CSS Text Decoration module:

The text-decoration property is a shorthand for setting text-decoration-line, text-decoration-thickness, text-decoration-style, and text-decoration-color in one declaration. Omitted values are set to their initial values.

You can confirm this in the browser’s DevTools by selecting one of the hyperlinks in the DOM inspector and then expanding the text-decoration property in the CSS pane.

DevTools screenshot showing text-decoration styles on the :any-link pseudo-selector.

In order to get text-decoration-thickness to work on hover underlines, we’ll have to make a small change to the above CSS code. There are actually multiple ways to achieve this. We could:

  • set text-decoration-thickness after text-decoration,
  • declare the thickness in the text-decoration shorthand, or
  • use text-decoration-line instead of text-decoration.

Choosing the best text-decoration option

Our first thought might be to simply repeat the text-decoration-thickness declaration in the :hover state. It’s a quick and simple fix that indeed works.

/* OPTION A */  header :any-link {   text-decoration: none; }  header :any-link:hover {   text-decoration: underline;   text-decoration-thickness: 0.08em; /* set thickness again */ }

However, since text-decoration is a shorthand and text-decoration-thickness is its associated longhand, there really should be no need to use both at the same time. As a shorthand, text-decoration allows setting both the underline itself and the underline’s thickness, all in one declaration.

/* OPTION B */  header :any-link {   text-decoration: none; }  header :any-link:hover {   text-decoration: underline 0.08em; /* set both line and thickness */ }

If this code looks unfamiliar to you, that could be because the idea of using text-decoration as a shorthand is relatively new. This property was only subsequently turned into a shorthand in the CSS Text Decoration module. In the days of CSS 2, text-decoration was a simple property.

Unfortunately, Safari still hasn’t fully caught up with these changes. In the WebKit browser engine, the shorthand variant of text-decoration remains prefixed (-webkit-text-decoration), and it doesn’t support thickness values yet. See WebKit bug 230083 for more information.

This rules out the text-decoration shorthand syntax. The above code won’t work in Safari, even if we added the -webkit- prefix. Luckily, there’s another way to avoid repeating the text-decoration-thickness declaration.

When text-decoration was turned into a shorthand, a new text-decoration-line longhand was introduced to take over its old job. We can use this property to hide and show the underline without affecting the other three text decoration components.

/* OPTION C */  header :any-link {   text-decoration-line: none; }  header :any-link:hover {   text-decoration-line: underline; }

Since we’re only updating the line component of the text-decoration value, the previously declared thickness remains intact. I think that this is the best way to implement hover underlines.

Be aware of shorthands

Keep in mind that when you set a shorthand property, e.g., text-decoration: underline, any missing parts in the value are re-initialized. This is also why styles such as background-repeat: no-repeat are undone if you set background: url(flower.jpg) afterwards. See the article “Accidental CSS Resets” for more examples of this behavior.


When to Avoid the text-decoration Shorthand Property originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

Multi-Value CSS Properties With Optional Custom Property Values

Imagine you have an element with a multi-value CSS property, such as transform: optional custom property values:

.el {   transform: translate(100px) scale(1.5) skew(5deg); }

Now imagine you don’t always want all the transform values to be applied, so some are optional. You might think of CSS optional custom property values:

.el {   /*         |-- default ---| |-- optional --| */   transform: translate(100px) var(--transform); }

But surprisingly using optional custom property values like this does not work as intended. If the --transform variable is not defined the whole property will not be applied. I’ve got a little “trick” to fix this and it looks like this:

.el {   transform: translate(100px) var(--transform, ); }

Notice the difference? There is a fallback defined in there that is set to an empty value: (, )

That’s the trick, and it’s very useful! Here’s what the specification has to say:

In an exception to the usual comma elision rules, which require commas to be omitted when they’re not separating values, a bare comma, with nothing following it, must be treated as valid in var(), indicating an empty fallback value.

This is somewhat spiritually related to the The CSS Custom Property Toggle Trick that takes advantage of a custom property having the value of an empty space.

Demo

Like I said, this is useful and works for any multi-value CSS property. The following demo shows it using text-shadow, background, and filter in addition to the transform example we just discussed.

See the Pen CSS var – Fallback To Nothing by Yair Even Or (@vsync) on CodePen.

Some properties that accept multiple values, like text-shadow, require special treatment because they only work with a comma delimiter. In those cases, when the CSS custom property is defined, you (as the code author) know it is only to be used in a situation where a value is already defined where the custom property is used. Then a comma should be inserted directly in the custom property before the first value, like this:

--text-shadow: ,0 0 5px black;

This, of course, inhibits the ability to use this variable in places where it’s the only value of some property. That can be solved, though, by creating “layers” of variables for abstraction purposes, i.e. the custom property is pointing to lower level custom properties.

Beware of Sass compiler

While exploring this trick, uncovered a bug in the Sass compiler that strips away the empty value (,) fallback, which goes against the spec. I’ve reported the bug and hope it will be fixed up soon.

As a temporary workaround, a fallback that causes no rendering can be used, such as:

transform: translate(100px) var(--transform, scale(1));

Multi-Value CSS Properties With Optional Custom Property Values originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

The surprising behavior of !important in CSS custom property values

Huh! I did not realize that CSS custom properties had their own resolution behavior for how !important works in their values. Uh, despite writing a guide about them. 😬 But hey it’s now updated.

Stefan Judis documents it clearly. The deal is that !important is not at all used in the final value. So:

div {   --color: red !important;   color: var(--color);   color: yellow; }

It kinda feels like red should win, but because !important is ultimately stripped from the custom property value, yellow wins it out. And it’s not because the color declaration comes last — if color: red !important; was the first declaration, then red would win.

But it’s not like !important is just stripped and ignored; it’s used in a scoped way, affecting which custom property value wins. But then !important is gone when that value is applied. Stefan’s example:

div {   /*     `!important` overrules the     other `--color` definitions   */   --color: red !important;   color: var(--color); }  .class {   --color: blue; }  #id {   --color: yellow; }

This feels weird (to me) as you’d think yellow declared on #id would win because it has the highest specificity. But that’s the deal with the scoped behavior — !important makes red the winner, then is applied to the color as such.

To Shared LinkPermalink on CSS-Tricks

CSS-Tricks

, , , , ,
[Top]

Different Degrees of Custom Property Usage

One way to work with Custom Properties is to think of them as design tokens. Colors, spacings, fonts, and whatnot. You set them at the root of the page and use them throughout your CSS. Very useful, and the classic use case for not only Custom Properties but for preprocessor variables for the last one million years.

Another way to work with Custom Properties, which can be done in addition to the design tokens approach, is to go a lot harder and use them for every major unique styling choice on any given element.

Imagine you have a Card like this (simplified for demonstration sake, of course):

.card {   background: hsl(200deg 15% 73%);   border: 4px solid rgb(255 255 255 / 0.5);   padding: 2rem;   border-radius: 8px; } .card > h2 {   margin: 0 0 1rem 0;   border-bottom: 3px solid rgba(0 0 0 / 0.2); }

Fine.

But then when you inevitably make variations of the card, you’re on your own to override these rulesets. A CSS Custom Property approach can be like:

.card {   --card-background: hsl(200deg 15% 73%);   --card-border: 4px solid rgb(255 255 255 / 0.5);   --card-padding: 2rem;   --card-border-radius: 8px;   --card-title-margin: 0 0 1rem 0;   --card-title-border: 3px solid rgba(0 0 0 / 0.2);    background: var(--card-background);   border: var(--card-border);   padding: var(--card-padding);   border-radius: var(--card-border-radius); } .card > h2 {   margin: var(--card-title-margin);   border-bottom: var(--card-title-border); }

A little more verbose, for now, but look what happens when we want to do a variation:

.card-variation {   --card-background: purple;   --card-padding-block: 2.5rem;   --card-title-margin: 0 0 2rem 0; } 

Here are three clear advantages right off the bat:

  • I’m only changing values that I’ve clearly set up to be changed. My main Card prototype maintains the integrity I want it to keep.
  • I can style children of the variation without having to re-write those selectors correctly.
  • I can now pass in styling overrides from the style attribute in the HTML for quick, one-off variations.

Less verbose with fallbacks

Rather than declaring the Custom Properties at the top and then using them right below, I can do both at the same time like this:

.card {   background: var(--card-background, hsl(200deg 15% 73%));   border: var(--card-border, 4px solid rgb(255 255 255 / 0.5));   padding: var(--card-padding, 2rem);   border-radius: var(--card-border-radius, 8px); } .card > h2 {   margin: var(--card-title-margin, 0 0 1rem 0);   border-bottom: var(--card-title-border, 3px solid rgba(0 0 0 / 0.2)); }

Now if something like --card-background does happen to get set, it will override the fallback value here. I don’t completely love this, because it means elements above .card can override it. That might be what you want, but it’s not exactly the same as declaring the values at the .card level to begin with. No strong opinions here.

Breaking it up even more

An example here is that you might want to individually control padding.

.card {   --card-padding-block: 2rem;   --card-padding-inline: 2rem;   --card-padding: var(--card-padding-block) var(--card-padding-inline);    padding: var(--card-padding); }

Now a variation can control just a part of the padding if I want:

.card-variation {   --card-padding-inline: 3rem; }

You gotta be careful of the big gotcha though. Meaning if you declare all these at the root, this isn’t going to work, because those nested properties have already been resolved. But so long as it’s first declared on .card, you’ll be fine here.

Too far?

Say you wanted super ultimate control over every part of a value. For example:

html {   --color-1-h: 200deg;   --color-1-s: 15%;   --color-1-l: 73%;   --color-1-hsl: var(--color-1-h) var(--color-1-s) var(--color-1-l);   --color-1: hsl(var(--color-1-hsl)); }

That’s kinda neat, but it’s likely too far. Colors are almost certainly going to be declared at the root and left alone, so the great gotcha is going to make overriding the low-level child properties impossible. Besides, if you have a --color-1, you probably have a 2-9 (or more) as well, which is all well and good because there is far more delicate design magic to a color system than simple mathematical manipulations of color parts.

Deliverable design systems?

There is no doubt that Tailwind has enjoyed a lot of popularity. It uses an atomic approach where a slew of HTML classes control one property each. I’d argue some of that popularity is driven by the fact that if you choose from these pre-configured classes, that the design ends up fairly nice. You can’t go off the rails. You’re choosing from a limited selection of values that have been designed to look good.

I wouldn’t go as far as to say that a Custom Properties heavy-based approach to styling is exactly the same. For example, you’ll still need to think of a class name abstraction rather than apply styling directly to the HTML element. But, it might enjoy some of the same constraints/limitations that make Tailwind and other atomic class approaches desirable. If you can only pick from a pre-defined set of --spacing-x values, --color-x values, and --font-x values, you might achieve a more cohesive design than you would have otherwise.

Personally, I’ve found inching toward a design system that is more heavily based on Custom Properties feels good — if nothing else to make variations and overrides more sensible to manage.

What about third-party design systems delivering what they deliver as… nothing but a big ol’ set of Custom Properties to use at your leisure?

Pollen

Third-party deliverables don’t even have to be the entire kitchen sink like this. For example, Adam Argyle’s transition.style provides a “Hackpack” that is nothing but Custom Properties of transition animation helpers.

Understandabilty cost

One pushback I’ve heard against this more all-in approach on Custom Properties is newcomer understandability. If you wrote the system, it probably makes perfect sense to you. But it’s an additional abstraction on top of CSS. CSS knowledge is shared by all, bespoke systems knowledge is only shared by the people actively working on it.

Rolling in fresh to a system heavily using Custom Properties is going to have a heck of a learning curve.

Videos


The post Different Degrees of Custom Property Usage appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

The Story Behind TryShape, a Showcase for the CSS clip-path property

I love shapes, especially colorful ones! Shapes on websites are in the same category of helpfulness as background colors, images, banners, section separators, artwork, and many more: they can help us understand context and inform our actions through affordances.

A few months back, I built an application to engage my 7-year old daughter with mathematics. Apart from basic addition and subtraction, my aim was to present questions using shapes. That’s when I got familiar with the CSS clip-path property, a reliable way to make shapes on the web. Then, I ended up building another app called, TryShape using the power of clip-path.

I’ll walk you through the story behind TryShape and how it helps create, manage, share, and export shapes. We’ll cover a lot about CSS clip-path along the way and how it helped me quickly build the app.

Here are a few important links:

First, the CSS clip-path property and shapes

Imagine you have a plain piece of paper and a pencil to draw a shape (say, a square) on it. How will you proceed? Most likely, you will start from a point, then draw a line to reach another point, then repeat it exact three more times to come back to the initial point. You also have to make sure you have opposite lines parallel and of the same length.

So, the essential ingredients for a shape are points, lines, directions, curves, angles, and lengths, among many others. The CSS clip-path helps specify many of these properties to clip a region of an HTML element to show a specific region. The part that is inside the clipped region is shown, and the rest is hidden. It gives an ocean of opportunities to developers to create various shapes using clip-path property.

Learn more about clipping and how it is different from masking.

The clip-path values for shape creation

The clip-path property accepts the following values for creating shapes:

  • circle()
  • ellipse()
  • inset()
  • polygon()
  • A clip source using url() function
  • path()

We need to understand the basic coordinate system a bit to use these values. When applying the clip-path property on an element to create shapes, we must consider the x-axis, y-axis, and the initial coordinates (0,0) at the element’s top-left corner.

Here is a div element with its x-axis, y-axis, and initial coordinates (0,0).

A blue square with its starting point located at a zero zero position on a chart with x and y axes.
Initial coordinates(0,0) with x-axis and y-axis

Now let’s use the circle() value to create a circular shape. We can specify the position and radius of the circle using this value. For example, to clip a circular shape at the coordinate position (70, 70) with a radius of 70px, we can specify the clip-path property value as:

clip-path: circle(70px at 70px 70px)

So, the center of the circle is placed at the coordinate (70, 70) with a 70px radius. Now, only this circular region is clipped and shown on the element. The rest of the portion of the element is hidden to create the impression of a circle shape.

A blue circle on a grid with an x and y axis. The circle starts at the zero zero position and its center is located at 70 px and 70px. A zoomed in area shows the clipping path, which is also located at 70x and 70px.
The center of the circle is placed at (70, 70) coordinates with a 70px x 70px area clipped. Hence the full circle is shown.

Next, what if we want to specify the position at (0,0)? In this case, the circle’s center is placed at the (0,0) position with a radius of 70px. That makes only a portion of the circle visible inside the element.

The center of a blue circle is placed at the 0,0 coordinates with a 70px by 70px area clipping the bottom-left region of the circle.
The center of the circle is placed at (0, 0) coordinates with a 70px x 70px area clipping the bottom-left region of the circle.

Let’s move on to use the other two essential values, inset() and polygon(). We use an inset to define a rectangular shape. We can specify the gap that each of the four edges may have to clip a region from an element. For example:

clip-path: inset(30px)

The above clip-path value clips a region by leaving out the 30px values from element’s edges. We can see that in the image below. We can also specify a different inset value for each of the edges.

The inset() function allows us to clip and area from the outside edge of a shape.

Next is the polygon() value. We can create a polygonal shape using a set of vertices. Take this example:

clip-path: polygon(10% 10%, 90% 10%, 90% 90%, 10% 80%)

Here we are specifying a set of vertices to create a region for clipping. The image below shows the position of each vertex to create a polygonal shape. We can specify as many vertices as we want.

A square with four points inside of it located at 10% by 10%, 90% by 10%, 10% by 80% and 90% by 90%, creating the clipped area.
The polygon() function allows us to create polygonal shapes using the set of vertices passed to it.

Next, let’s take a look at the ellipse() and the url() values. The ellipse() value helps create shapes by specifying two radii values and a position. In the image below, we see an ellipse at the position where the radii is at (50%,50%) and the shape is 70px wide and 100px tall.

A blue blue against a light blue background. The ellipse is 70 pixels wide and 100 pixels tall and it's radius is centered at 50% 50%.
We need to specify two radii values and a position to create an ellipse.

url() is a CSS function to specify the clip-path element’s ID value to render an SVG shape. Please take a look at the image below. We have defined a SVG shape using clipPath and path elements. You can use the ID value of the clipPath element as an argument to the url() function to render this shape.

Showing the SVG code for a heart-shaped path and the actual heart next to it in blue.
Here, we are creating a heart shape using the url() function

Additionally, we can use the path values directly in the path() function to draw the shape.

Showing a line of CSS code for the clip-path property filled in with the code of an SVG path inside the path function. The result is below the code, a parabolic curve that's filled in blue.
Here we are creating a curvy shape using the path() function.

Alright. I hope you have got an understanding of different clip-path property values. With this understanding, let’s take a loot at some implementations and play around with them. Here is a Pen for you. Please use it to try adding, modifying values to create a new shape.

Let’s talk about TryShape

It’s time to talk about TryShape and its background story. TryShape is an open-source application that helps create, export, share, and use any shapes of your choice. You can create banners, circles, arts, polygons and export them as SVG, PNG, and JPEG files. You can also create a CSS code snippet to copy and use in your application.

TryShape is built using the following framework and libraries (and clip-path, of course):

  • CSS clip-path: We’ve already discussed the power of this awesome CSS property.
  • Next.js: The coolest React-based framework around. It helped me create pages, components, interactions, and APIs to connect to the back-end database.
  • HarperDB: A flexible database to store data and query them using both SQL and No-SQL interactions. TryShape has its schema and tables created in the HarperDB cloud. The Next.js APIs interact with the schema and tables to perform required CRUD operations from the user interface.
  • Firebase: Authentication services from Google. TryShape uses it to get the social login working using Google, GitHub, Twitter, and other accounts.
  • react-icons: One shop for all the icons for a React-based application
  • date-fns: The modern, lightweight library for date formatting
  • axios: Making the API calls easy from the React components
  • styled-components: A structured way to create CSS rules from react components
  • react-clip-path: A homegrown module to handle clip-path property in a React app
  • react-draggable: Make an HTML element draggable in a React app. TryShape uses it to adjust the position of shape vertices.
  • downloadjs: Trigger a download from JavaScript
  • html-to-image: Converts an HTML element to image (including SVG, JPEG, and PNG)
  • Vercel: Best for hosting a Next.js app

Creating shapes in TryShape using CSS clip-path

Let me highlight the source code that helps create a shape using the CSS clip-path property. The code snippet below defines the user interface structure for a container element (Box) that’s 300px square. The Box element has two child elements, Shadow and Component.

<Box    height="300px"    width="300px"    onClick={(e) => props.handleChange(e)}>   {      props.shapeInformation.showShadow &&      <Shadow        backgroundColor={props.shapeInformation.backgroundColor}        id="shapeShadow" />    }   <Component      formula={props.shapeInformation.formula}      backgroundColor={props.shapeInformation.backgroundColor}      id="clippedShape" /> </Box>

The Shadow component defines the area that is hidden by the clip-path clipping. We create it to show a light color background to make this area partially visible to the end user. The Component is to assign the clip-path value to show the clipped area.

See the styled-component definitions of Box, Shadow, and Component below:

// The styled-components code to create the UI components using CSS properties  // The container div const Box = styled.div`   width: $ {props => props.width || '100px'};   height: $ {props => props.height || '100px'};   margin: 0 auto;   position: relative; `;  // Shadow defines the area that is hidden by the `clip-path` clipping // We show a light color background to make this area partially visible. const Shadow = styled.div`   background-color: $ {props => props.backgroundColor || '#00c4ff'};   opacity: 0.25;   position: absolute;   top: 10px;   left: 10px;   right: 10px;   bottom: 10px; `;  // The actual component that takes the `clip-path` value (formula) and set // to the `clip-path` property. const Component = styled.div`   clip-path: $ {props => props.formula}; // the formula is the clip-path value   background-color: $ {props => props.backgroundColor || '#00c4ff'};   position: absolute;   top: 10px;   left: 10px;   right: 10px;   bottom: 10px; `;
An illustration of a blue box with its inset clip points drawing another square that indicates the visible area of the shape in a darker blue.
The components to show a shape(both visible and hidden areas) after the clipping.

Please feel free to look into the entire codebase in the GitHub repo.

The future scope of TryShape

TryShape works well with the creation and management of basic shapes using CSS clip-path in the background. It is helpful to export the shapes and the CSS code snippets to use in your web applications. It has the potential to grow with many more valuable features. The primary one will be the ability to create shapes with curvy edges.

To support the curvy shapes, we need the support of the following values in TryShape:

  • a clip source using url() and
  • path().

With the help of these values, we can create shapes using SVG and then use one of the above values. Here is an example of the url() CSS function to create a shape using the SVG support.

<div class="heart">Heart</div> <svg>   <clipPath id="heart-path" clipPathUnits="objectBoundingBox">     <path d="M0.5,1       C 0.5,1,0,0.7,0,0.3       A 0.25,0.25,1,1,1,0.5,0.3       A 0.25,0.25,1,1,1,1,0.3       C 1,0.7,0.5,1,0.5,1 Z" />   </clipPath> </svg>

Then, the CSS::

.heart {   clip-path: url(#heart-path); }
It produces a heart shape like this.

Now, let’s create a shape using the path() value. The HTML should have an element like a div:

<div class="curve">Curve</div>

In CSS:

.curve {   clip-path: path("M 10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"); }

Before we end…

I hope you enjoyed meeting my TryShape app and learning about the idea that leads to it, the strategies I considered, the technology under the hood, and its future potential. Please consider trying it and looking through the source code. And, of course, feel free to contribute to it with issues, feature requests, and code.

Before we end, I want to leave you with this short video prepared for the Hashnode hackathon where TryShape was an entry and finally in the list of winners. I hope you enjoy it.

Let’s connect. You can @ me on Twitter (@tapasadhikary) with comments, or feel free to follow.


The post The Story Behind TryShape, a Showcase for the CSS clip-path property appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Custom Property Brain Twisters

I am part of that 82% that got it wrong in Lea’s quiz (tweet version).

Here’s the code:

:root {   --accent-color: skyblue; }  div {   --accent-color: revert;    background: var(--accent-color, orange); }

So what background do I expect <div> to have?

My brain goes like this:

  1. Well, --accent-color is declared, so it’s definitely not orange (the fallback).
  2. The value for the background is revert, so it’s essentially background: revert;
  3. The background property doesn’t inherit though, and even if you force it to, it would inherit from the <body>, not the root.
  4. So… transparent.

Nope.

Lea:

[Because the value is revert it] cancels out any author styles, and resets back to whatever value the property would have from the user stylesheet and UA stylesheet. Assuming there is no --accent-color declaration in the user stylesheet, and of course UA stylesheets don’t set custom properties, then that means the property doesn’t have a value.

Since custom properties are inherited properties (unless they are registered with inherits: false, but this one is not), this means the inherited value trickles in, which is — you guessed it — skyblue.

Stephen posted a similar quiz the other day:

Again, my brain does it totally wrong. It goes:

  1. OK, well, --color is declared, so it’s not blue (the fallback).
  2. It’s not red because the second declaration will override that one.
  3. So, it’s essentially like p { color: inherit; }.
  4. The <p> will inherit yellow from the <body>, which it would have done naturally anyway, but whatever, it’s still yellow.

Nope.

Apparently inherit there is actually inheriting from the next place up the tree that sets it, which html does, so green. That actually is now normal inheriting works. It’s just a brain twister because it’s easy to conflate color the property with --color the custom property.

It also might be useful to know that when you actually declare a custom property with @property you can say whether you want it to inherit or not. So that would change the game with these brain twisters!

@property --property-name {   syntax: '<color>';   inherits: false;   initial-value: #c0ffee; }

The post Custom Property Brain Twisters appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Using the `outline` Property as a Collapsable Border

The outline property in CSS draws a line around the outside of an element. This is quite similar to the border property, the main exception being that outline isn’t a part of the box model. It is often used for highlighting elements, for example, the :focus style.

In this article, let’s put a point on it, leaning into what outline is good at:

  1. They can be collapsed with each other (trickery!) because they technically “take up no space.”
  2. Showing and hiding outlines, or changing outline-width, doesn’t trigger layouts (which is good for performant animations and transitions).

Easier faux-table cell borders

Below is an example of a list that is laid out as a grid, making it look a bit like a table layout. Every cell has a minimum width, and will grow/shrink as the container becomes wider/narrower.

We could use border to pull this off, like this:

But in order to make an even border around each cell — never doubling up or missing — it’s a cumbersome process. Above, I used a border on all sides of each “cell” then negative margins to overlap them and prevent doubling. That meant clipping off the border on two sides, so the borders had to be re-applied there on the parent. Too much fiddly work, if you ask me.

Even having to hide the overflow is a big ask, which you have to do because, otherwise, you’ll trigger scrollbars unless you resort to even thicker trickery, like using absolutely-positioned pseudo elements.

Showing a flat table with seven columns and four rows, each cell numbered sequentially, 1 through 28. The table has a white background and block text and the borders are black around each cell with ample padding.

Check out the same result, visually, only using outline instead:

The code here is much cleaner. There is no real trickery at play. Each “cell” just has an outline around it, and that’s it.

Border in animation

Changing border-width will always trigger layout, no matter if it is actually needed.

Showing the paint rendering results from a performance test where a layout change is shown in the middle of the results taking 58.4 milliseconds to complete.

In addition, due to Chrome’s special handling of sub-pixels for border widths, animating the border-width property makes the entire border shake (which I think is strange). Firefox doesn’t have this issue.

Showing another performance test, this time with no layout triggered in the results.

There are pros and cons when it comes to animating borders. Check out Stephen Shaw’s post from a while back for an example of the performance implications.

There are some gotchas

Of course there are. Like most other CSS properties, there are a few “gotchas” or things to know when working with the outline property:

  1. Rounded outlines are only supported in Firefox at the time of writing. I imagine other browsers will eventually support them as well.
  2. An outline always goes around all the sides. That is to say it’s not a shorthand property like, say, border; so no outline-bottom, and so on.

But we can work around these limitations! For example, we can use add a box-shadow with no blur radius as an alternative. But remember: box-shadow has a higher performance cost than using either outline and border.

That’s it!

Will you always be working on something that calls for faking a table with an unordered list? Unlikely. But the fact that we can use outline and its lack of participation in the box model makes it interesting, particularly as a border alternative in some cases.

Maybe something like this tic-tac-toe board Chris put together several years ago could benefit from outline, instead of resorting to individually-crafted cell borders. Challenge accepted, Mr. Coyier? 😉


The post Using the `outline` Property as a Collapsable Border appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

Exploring @property and its Animating Powers

Uh, what’s @property? It’s a new CSS feature! It gives you superpowers. No joke, there is stuff that @property can do that unlocks things in CSS we’ve never been able to do before.

While everything about @property is exciting, perhaps the most interesting thing is that it provides a way to specify a type for custom CSS properties. A type provides more contextual information to the browser, and that results in something cool: We can give the browser the information is needs to transition and animate those properties!

But before we get too giddy about this, it’s worth noting that support isn’t quite there. As it current stands at the time of this writing, @property is supported in Chrome and, by extension, Edge. We need to keep an eye on browser support for when we get to use this in other places, like Firefox and Safari.

First off, we get type checking

@property --spinAngle {   /* An initial value for our custom property */   initial-value: 0deg;   /* Whether it inherits from parent set values or not */   inherits: false;   /* The type. Yes, the type. You thought TypeScript was cool */   syntax: '<angle>'; }  @keyframes spin {   to {     --spinAngle: 360deg;   } }

That’s right! Type checking in CSS. It’s sorta like creating our very own mini CSS specification. And that’s a simple example. Check out all of the various types we have at our disposal:

  • length
  • number
  • percentage
  • length-percentage
  • color
  • image
  • url
  • integer
  • angle
  • time
  • resolution
  • transform-list
  • transform-function
  • custom-ident (a custom identifier string)

Before any of this, we may have relied on using “tricks” for powering animations with custom properties.

What cool stuff can we do then? Let’s take a look to spark our imaginations.

Let’s animate color

How might you animate an element either through a series of colors or between them? I’m a big advocate for the HSL color space which breaks things down into fairly understandable numbers: hue, saturation, and lightness, respectively.

Animating a hue feels like something fun we can do. What’s colorful? A rainbow! There’s a variety of ways we could make a rainbow. Here’s one:

In this example, CSS Custom Properties are set on the different bands of the rainbow using :nth-child() to scope them to individual bands. Each band also has an --index set to help with sizing.

To animate those bands, we might use that --index to set some negative animation delays, but then use the same keyframe animation to cycle through hues.

.rainbow__band {   border-color: hsl(var(--hue, 10), 80%, 50%);   animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear; }  @keyframes rainbow {   0%, 100% {     --hue: 10;   }   14% {     --hue: 35;   }   28% {     --hue: 55;   }   42% {     --hue: 110;   }   56% {     --hue: 200;   }   70% {     --hue: 230;   }   84% {     --hue: 280;   } }

That might work out okay if you want a “stepped” effect. But, those keyframe steps aren’t particularly accurate. I’ve used steps of 14% as a rough jump.

We could animate the border-color and that would get the job done. But, we’d still have a keyframe step calculation issue. And we need to write a lot of CSS to get this done:

@keyframes rainbow {   0%, 100% {     border-color: hsl(10, 80%, 50%);   }   14% {     border-color: hsl(35, 80%, 50%);   }   28% {     border-color: hsl(55, 80%, 50%);   }   42% {     border-color: hsl(110, 80%, 50%);   }   56% {     border-color: hsl(200, 80%, 50%);   }   70% {     border-color: hsl(230, 80%, 50%);   }   84% {     border-color: hsl(280, 80%, 50%);   } }

Enter @property. Let’s start by defining a custom property for hue. This tells the browser our custom property, --hue, is going to be a number (not a string that looks like a number):

@property --hue {   initial-value: 0;   inherits: false;   syntax: '<number>'; }

Hue values in HSL can go from 0 to 360. We start with an initial value of 0. The value isn’t going to inherit. And our value, in this case, is a number. The animation is as straightforward as:

@keyframes rainbow {   to {     --hue: 360;   } }

Yep, that’s the ticket:

To get the starting points accurate, we could play with delays for each band. This gives us some cool flexibility. For example, we can up the animation-duration and we get a slow cycle. Have a play with the speed in this demo.

It may not be the “wildest” of examples, but I think animating color has some fun opportunities when we use color spaces that make logical use of numbers. Animating through the color wheel before required some trickiness. For example, generating keyframes with a preprocessor, like Stylus:

@keyframes party    for $  frame in (0..100)     {$  frame * 1%}       background 'hsl(%s, 65%, 40%)' % ($  frame * 3.6)

We do this purely because this isn’t understood by the browser. It sees going from 0 to 360 on the color wheel as an instant transition because both hsl values show the same color.

@keyframes party {   from {     background: hsl(0, 80%, 50%);    }   to {     background: hsl(360, 80%, 50%);   } }

The keyframes are the same, so the browser assumes the animation stays at the same background value when what we actually want is for the browser to go through the entire hue spectrum, starting at one value and ending at that same value after it goes through the motions.

Think of all the other opportunities we have here. We can:

  • animate the saturation
  • use different easings
  • animate the lightness
  • Try rgb()
  • Try degrees in hsl() and declare our custom property type as <angle>

What’s neat is that we can share that animated value across elements with scoping! Consider this button. The border and shadow animate through the color wheel on hover.

Animating color leads me think… wow!

Straight-up numbering

Because we can define types for numbers—like integer and number—that means we can also animate numbers instead of using those numbers as part of something else. Carter Li actually wrote an article on this right here on CSS-Tricks. The trick is to use an integer in combination with CSS counters. This is similar to how we can work the counter in “Pure CSS” games like this one.

The use of counter and pseudo-elements provides a way to convert a number to a string. Then we can use that string for the content of a pseudo-element. Here are the important bits:

@property --milliseconds {   inherits: false;   initial-value: 0;   syntax: '<integer>'; }  .counter {   counter-reset: ms var(--milliseconds);   animation: count 1s steps(100) infinite; }  .counter:after {   content: counter(ms); }  @keyframes count {   to {     --milliseconds: 100;   } }

Which gives us something like this. Pretty cool.

Take that a little further and you’ve got yourself a working stopwatch made with nothing but CSS and HTML. Click the buttons! The rad thing here is that this actually works as a timer. It won’t suffer from drift. In some ways it may be more accurate than the JavaScript solutions we often reach for such as setInterval. Check out this great video from Google Chrome Developer about JavaScript counters.

What other things could you use animated numbers for? A countdown perhaps?

Animated gradients

You know the ones, linear, radial, and conic. Ever been in a spot where you wanted to transition or animate the color stops? Well, @property can do that!

Consider a gradient where we‘re creating some waves on a beach. Once we’ve layered up some images we could make something like this.

body {   background-image:     linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-four) calc(75% + var(--wave)) 100%),     linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-three) calc(50% + var(--wave)) calc(75% + var(--wave))),     linear-gradient(transparent 0 calc(20% + (var(--wave) * 0.5)), var(--wave-two) calc(35% + var(--wave)) calc(50% + var(--wave))),     linear-gradient(transparent 0 calc(15% + (var(--wave) * 0.5)), var(--wave-one) calc(25% + var(--wave)) calc(35% + var(--wave))), var(--sand); }

There is quite a bit going on there. But, to break it down, we’re creating each color stop with calc(). And in that calculation, we add the value of --wave. The neat trick here is that when we animate that --wave value, all the wave layers move.

This is all the code we needed to make that happen:

body {   animation: waves 5s infinite ease-in-out; } @keyframes waves {   50% {     --wave: 25%;   } }

Without the use of @property, our waves would step between high and low tide. But, with it, we get a nice chilled effect like this.

It’s exciting to think other neat opportunities that we get when manipulating images. Like rotation. Or how about animating the angle of a conic-gradient… but, within a border-image. Bramus Van Damme does a brilliant job covering this concept.

Let’s break it down by creating a charging indicator. We’re going to animate an angle and a hue at the same time. We can start with two custom properties:

@property --angle {   initial-value: 0deg;   inherits: false;   syntax: '<number>'; }  @property --hue {   initial-value: 0;   inherits: false;   syntax: '<angle>'; }

The animation will update the angle and hue with a slight pause on each iteration.

@keyframes load {   0%, 10% {     --angle: 0deg;     --hue: 0;   }   100% {     --angle: 360deg;     --hue: 100;   } }

Now let’s apply it as the border-image of an element.

.loader {   --charge: hsl(var(--hue), 80%, 50%);   border-image: conic-gradient(var(--charge) var(--angle), transparent calc(var(--angle) * 0.5deg)) 30;   animation: load 2s infinite ease-in-out; }

Pretty cool.

Unfortunately, border-image doesn‘t play nice with border-radius. But, we could use a pseudo-element behind it. Combine it with the number animation tricks from before and we’ve got a full charging/loading animation. (Yep, it changes when it gets to 100%.)

Transforms are cool, too

One issue with animating transforms is transitioning between certain parts. It often ends up breaking or not looking how it should. Consider the classic example of a ball being throw. We want it to go from point A to point B while imitating the effect of gravity.

An initial attempt might look like this

@keyframes throw {   0% {     transform: translate(-500%, 0);   }   50% {     transform: translate(0, -250%);   }   100% {     transform: translate(500%, 0);   } }

But, we’ll soon see that it doesn’t look anything like we want.

Before, we may have reached for wrapper elements and animated them in isolation. But, with @property, we can animate the individual values of the transform. And all on one timeline. Let’s flip the way this works by defining custom properties and then setting a transform on the ball.

@property --x {   inherits: false;   initial-value: 0%;   syntax: '<percentage>'; }  @property --y {   inherits: false;   initial-value: 0%;   syntax: '<percentage>'; }  @property --rotate {   inherits: false;   initial-value: 0deg;   syntax: '<angle>'; }  .ball {   animation: throw 1s infinite alternate ease-in-out;   transform: translateX(var(--x)) translateY(var(--y)) rotate(var(--rotate)); }

Now for our animation, we can compose the transform we want against the keyframes:

@keyframes throw {   0% {     --x: -500%;     --rotate: 0deg;   }   50% {     --y: -250%;   }   100% {     --x: 500%;     --rotate: 360deg;   } }

The result? The curved path we had hoped for. And we can make that look different depending on the different timing functions we use. We could split the animation into three ways and use different timing functions. That would give us different results for the way the ball moves.

Consider another example where we have a car that we want to drive around a square with rounded corners.

We can use a similar approach to what we did with the ball:

@property --x {   inherits: false;   initial-value: -22.5;   syntax: '<number>'; }  @property --y {   inherits: false;   initial-value: 0;   syntax: '<number>'; }  @property --r {   inherits: false;   initial-value: 0deg;   syntax: '<angle>'; }

The car’s transform is using calculated with vmin to keep things responsive:

.car {   transform: translate(calc(var(--x) * 1vmin), calc(var(--y) * 1vmin)) rotate(var(--r)); }

Now can write an extremely accurate frame-by-frame journey for the car. We could start with the value of --x.

@keyframes journey {   0%, 100% {     --x: -22.5;   }   25% {     --x: 0;   }   50% {     --x: 22.5;   }   75% {     --x: 0;   } }

The car makes the right journey on the x-axis.

Then we build upon that by adding the travel for the y-axis:

@keyframes journey {   0%, 100% {     --x: -22.5;     --y: 0;   }   25% {     --x: 0;     --y: -22.5;   }   50% {     --x: 22.5;     --y: 0;   }   75% {     --x: 0;     --y: 22.5;   } }

Well, that’s not quite right.

Let’s drop some extra steps into our @keyframes to smooth things out:

@keyframes journey {   0%, 100% {     --x: -22.5;     --y: 0;   }   12.5% {     --x: -22.5;     --y: -22.5;   }   25% {     --x: 0;     --y: -22.5;   }   37.5% {     --y: -22.5;     --x: 22.5;   }   50% {     --x: 22.5;     --y: 0;   }   62.5% {     --x: 22.5;     --y: 22.5;   }   75% {     --x: 0;     --y: 22.5;   }   87.5% {     --x: -22.5;     --y: 22.5;   } }

Ah, much better now:

All that‘s left is the car‘s rotation. We‘re going with a 5% window around the corners. It’s not precise but it definitely shows the potential of what’s possible:

@keyframes journey {   0% {     --x: -22.5;     --y: 0;     --r: 0deg;   }   10% {     --r: 0deg;   }   12.5% {     --x: -22.5;     --y: -22.5;   }   15% {     --r: 90deg;   }   25% {     --x: 0;     --y: -22.5;   }   35% {     --r: 90deg;   }   37.5% {     --y: -22.5;     --x: 22.5;   }   40% {     --r: 180deg;   }   50% {     --x: 22.5;     --y: 0;   }   60% {     --r: 180deg;   }   62.5% {     --x: 22.5;     --y: 22.5;   }   65% {     --r: 270deg;   }   75% {     --x: 0;     --y: 22.5;   }   85% {     --r: 270deg;   }   87.5% {     --x: -22.5;     --y: 22.5;   }   90% {     --r: 360deg;   }   100% {     --x: -22.5;     --y: 0;     --r: 360deg;   } }

And there we have it, a car driving around a curved square! No wrappers, no need for complex Math. And we composed it all with custom properties.

Powering an entire scene with variables

We‘ve seen some pretty neat @property possibilities so far, but putting everything we’ve looked at here together can take things to another level. For example, we can power entire scenes with just a few custom properties.

Consider the following concept for a 404 page. Two registered properties power the different moving parts. We have a moving gradient that’s clipped with -webkit-background-clip. The shadow moves by reading the values of the properties. And we swing another element for the light effect.

That’s it!

It’s exciting to think about what types of things we can do with the ability to define types with @property. By giving the browser additional context about a custom property, we can go nuts in ways we couldn’t before with basic strings.

What ideas do you have for the other types? Time and resolution would make for interesting transitions, though I’ll admit I wasn’t able to make them work that way I was hoping. url could also be neat, like perhaps transitioning between a range of sources the way an image carousel typically does. Just brainstorming here!

I hope this quick look at @property inspires you to go check it out and make your own awesome demos! I look forward to seeing what you make. In fact, please share them with me here in the comments!


The post Exploring @property and its Animating Powers appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

The CSS Custom Property Toggle Trick

Back in July 2020, I got an email from James0x57 (I always try to refer to people by their name, but I think I get the sense they prefer to go by screen name) that says:

The entire world of branching conditional logic and bulk feature toggling for custom CSS properties is possible and only exists because of a tiny footnote in the CSS spec that has gone unnoticed.

That line is:

Note: While <declaration-value> must represent at least one token, that one token may be whitespace.

In other words, --foo: ; is valid.

If you’re like me, this doesn’t read as some massive revelation that unlocks huge doors, but to smarter people, like James0x57, it does! We started working on a draft blog post, but for various reasons it didn’t make it all the way here. One of those reasons is that I just wasn’t getting it. Call me dense, sorry James0x57. One demo they sent me when I asked super dumbed-down example was helpful though, and I think it’s kind of clicked for me. Here’s my interpretation:

Let me attempt to explain:

  • The breakpoint we’ve set up here is a 900px max-width media query. You can see that’s where the variable --mq-sm flops from initial to an empty space value.
  • When the browser window is wider than 900px, that the value of --mq-sm is initial.
    • That makes the variable --padding-when-small contain two values — initial and 2rem —which, I guess is invalid.
    • So when we actually set the padding and call that variable like padding: var(--padding-when-small, var(--padding-when-large)), the second value (the “fallback”) is used because the first value is invalid.
  • When the browser window is narrower than 900px, the --mq-sm value is a space.
    • That makes the variable --padding-when-small value "(space)2rem" which, I guess is valid.
    • That means when we actually set the padding and call that variable like padding: var(--padding-when-small, var(--padding-when-large)), the first value is used.

So, now we can flip the padding between two values by changing a placeholder variable.

That clicks for me.

When see this as simply changing a single value, it’s almost like uh, ok, you’ve found a really complex way to change some padding, but you could have just changed the padding in the media query. But the trick is that now we have this placeholder variable that has changed and we can key into that to change unlimited other values.

We could have a single media query (or set of media queries) in our CSS that only toggles these placeholder variables and we use elsewhere to toggle values. That could be nice and clean compared to sprinkling media queries all over the CSS. It’s a proper toggle in CSS, like a form of IF/THEN logic that we haven’t quite had before.

James0x57 extended that thinking to all the logical possibilities, like AND, OR, XOR, NAND, NOR, and XNOR, but that lost me again. Not really a computer scientist over here. But you can follow their work if you want to see real world usage of this stuff.

This variable stuff is wild and gets very confusing. I noted in a possibly recent (but the byline says 2015?) article from Patrick Brosset that covers some tricky CSS custom properties stuff. For example, fallbacks can be infinitely nested, like:

color: var(--foo, var(--bar, var(--baz, var(--are, var(--you, var(--crazy)))));

Also, valid values for CSS custom properties can have commas in them like this:

content: var(--foo, one, two, three);

Is that really just one fallback with a single one, two, three value? This is rather mind-bending.

Anyway, fast-forward a bunch of months now, and CSS trickery master Lea Verou has set her sights on this whitespace-in-custom-properties stuff:

What if I told you you could use a single property value to turn multiple different values on and off across multiple different properties and even across multiple CSS rules?

It’s the same trick! In Lea’s example, though, she uses this ability to:

  • set variations on a button, and
  • set four different properties rather than one.

This really hones in on why this is the concept is so cool.

Lea points to some downsides:

There is no way to say “the background should be red if --foo is set and white otherwise”. Some such conditionals can be emulated with clever use of appending, but not most.

And of course there’s a certain readability issue: --foo: ; looks like a mistake and --foo: initial looks pretty weird, unless you’re aware of this technique.

We’re certainly entering the next era of how custom properties are used. First, we used them like preprocessor variables. Then we started seeing more cascade and fallback usage. Next, we used it alongside JavaScript more frequently. Now this.

There is even more writing about keeping CSS preprocessor variables around, not so much for the times when you only need what they can do, but for the things that only they can do, like having their color values manipulated.


The post The CSS Custom Property Toggle Trick appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]