RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconShows IconOpen Source Software IconSpeaking IconTools IconShuffle IconNext IconPrevious IconCalendar IconCalendar Edit IconNotebook IconObservable Notebooks Icon
Text emphasized alt text example
A series of images of an avatar doing a bunch of skateboard tricks.25 min read

6 CSS Snippets Every Front-End Developer Should Know In 2025

css

2025; I think every front-end developer should know how to enable page transitions, transition a <dialog>, popover, and <details>, animate light n' dark gradient text, type safe their CSS system, and add springy easing to animation.

AI is not going to give you this CSS.

This post is a theme continuation; checkout previous years 2023 and 2024 where I shared snippets for those years.

This year, the snippets are bigger, more powerful, and leverage progressive enhancement a bit more; to help us step up to the vast UI/UX requirements of 2025.

Springy easing with linear() #

Sprinkle life into animations with natural looking spring and bounce easings using linear().

Using a series of linear lines to make "curves", you can create surprisingly realistic visual physics. A small amount can go a long way to adding interest and intrigue to the user experience.

In the following video, the top animation uses ease-out and the bottom uses linear(), and I think the results are quite different, the bottom being more desirable.

Here's some typical linear() easing CSS 😅:

.springy {
  transition: transform 1s
    linear(
      0,
      0.009,
      0.035 2.1%,
      0.141,
      0.281 6.7%,
      0.723 12.9%,
      0.938 16.7%,
      1.017,
      1.077,
      1.121,
      1.149 24.3%,
      1.159,
      1.163,
      1.161,
      1.154 29.9%,
      1.129 32.8%,
      1.051 39.6%,
      1.017 43.1%,
      0.991,
      0.977 51%,
      0.974 53.8%,
      0.975 57.1%,
      0.997 69.8%,
      1.003 76.9%,
      1.004 83.8%,
      1
    );
}

Yes… that's what your formatter will do to it, as if it's helpful in some way lol.

The linear() code above is not very human readable, but the machines love it. No frets, there's a few generators out there:

Tip!
Expect longer durations when using linear(). When things run long, it can be nice to make them seamlessly interruptible, making linear() a great fit for transitions and potentially troublesome as keyframes.

You could alternatively use premade CSS variables from a library like Open Props:

@import "https://unpkg.com/open-props/easings.min.css";

.springy {
  @media (prefers-reduced-motion: no-preference) {
    transition: transform 1s var(--ease-spring-3);
  }
}

Easy CSS to read, comes with 5 strengths for common effects:

Try it

Try the Open Props Springs notebook!

Incrementally adopt #

This one is super easy to toss in today.

Easiest way, use the cascade (if you're into that):

@media (prefers-reduced-motion: no-preference) {
  /* just repeat the shorthand with adjusted easing */
  .thingy {
    transition: transform 0.3s ease;
    transition: transform 0.3s linear(…);
  }

  /* or, target a specific property */
  .thingy {
    animation-timing-function: var(--ease-1);
    animation-timing-function: var(--ease-spring-2);
  }
}

If it knows, it knows.

Or, test for it first and scalpel apply the upgrade:

.thingy {
  transition: transform 0.3s ease;

  @supports (transition-timing-function: linear(0, 0.1, 1)) {
    transition-timing-function: var(--ease-spring-2);
  }
}

Typed custom properties #

Similar to JS variables defined with var, CSS variables defined with -- are global, loose, dynamic and flexible. This is great.

But… there are times, like when building a system, that you want to limit what goes into variables so a system can run with a reasonable amount of reliability.

Try it

In the above video, a variable is set to an invalid color. At first, this breaks the system. But, @property is added, the system remained functioning with the latest known valid color value.

Create a typed <color> CSS variable like this:

@property --color-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: rebeccapurple;
}

In addition to type safety, @property defined variables can also be animated because the browser can infer the steps needed to interpolate the value change based on the assigned type.

Before @property, the browser couldn’t derive a type and discover interpolation steps, it was too complicated. Now, you give the browser a hint, and it's simple.

In 2025, y'all front-end devs should be getting familiar with defining variables with @property because it:

  1. Formalizes CSS system interfaces
  2. Protects CSS systems
  3. Enables new animation powers
  4. Can perform better when using inherits: false
Resources

View transitions for page navigation #

Y'all should know how to crossfade pages when links are clicked with this tiny view transitions snippet:

@view-transition {
  navigation: auto;
}

Try it

This is the easiest snippet to add to your site, with no downsides.

It signals your website would like to use page transitions when links are clicked, and the default transition is a crossfade.

If the browser doesn't support it, it continues as it always has; but if it does support it then you dab the page with some special sauce.

There's plenty more customization you can do, like full page animations. But the gist of this section is just to share that easy snippet and the way the feature can be progressively enhanced.

Incrementally adopt #

There's many more opportunities to add additional animations with the page transition.

A great place to start enhancing this page transition experience is to identify elements commonly found across pages, and give them a name:

.nav {
  view-transition-name: --nav;
}

.sidenav {
  view-transition-name: --sidenav;
}

This includes elements in the page transition.

They can even be different elements.

a, h1 {
  view-transition-name: --morphy;
}

By giving an <a> and an <h1> the same view-transition-name on two different pages, the browser will move and resize the page 1 element to the location and size of the page 2 element, making it look like a morph. You can of course morph between the same elements also.

Try it

There is so much more. Continue giving elements names and studying the rad examples by Bramus, and you can create experiences with motion like this:

Try it

I love the DevTools for Animations, scrubbing that full page view transition is very satisfying, and excellent for really inspecting and improving the little details.

Resources

Transition animation for <dialog> and [popover] #

In 2025, knowing your way around a <dialog> and a [popover] are table stakes. Otherwise, everyone else will be on top of you, and your wack z-index attempts will be defeated with a puny value of 1.

These are common UI elements, with no JavaScript to download, and accessibility built in. Use em and know the differences.

These two elements are projected into a layer above all the other UI called the top layer. The browser projects the elements from anywhere in the document, to the top when shown.

To transition this, there’s a few new CSS properties, for the full interruptible CSS transition user experience — transition-behavior, the @starting-style rule, and overlay.

Combining these can feel like an incantation, but that makes it great copy and paste. So here! Use the following snippet to enable cross fade transitions for both <dialog> and popover: Try it

/* enable transitions, allow-discrete, define timing */
[popover], dialog, ::backdrop {
  transition: display 1s allow-discrete, overlay 1s allow-discrete, opacity 1s;
  opacity: 0;
}

/* ON STAGE */
:popover-open,
:popover-open::backdrop,
[open],
[open]::backdrop {
  opacity: 1;
}

/* OFF STAGE */
/* starting-style for pre-positioning (enter stage from here) */
@starting-style {
  :popover-open,
  :popover-open::backdrop,
  [open],
  [open]::backdrop {
    opacity: 0;
  }
}

While this code is effective and terse, it's often not enough customization if you want to present and dismiss dialogs differently than you do popovers. Or make the entry animation different then the exit.

Transition a dialog #

Here's a <dialog> element with this barebones snippet applied. They look pretty terrible out of the box, but you can do amazing things with them.

Try it

To get started, a <dialog> element needs to be in the HTML. A <dialog> should be shown and hidden with buttons, one to show it can be in the page and one to close it should be inside the dialog.

<button onclick="demo.showModal()">…</button>

<dialog id="demo">
  <header>
    <button title="Close" onclick="demo.close()">close</button>
  </header>
</dialog>

You can enable light dismiss on a dialog and skip the close button like <dialog closedby="any">

To animate the dialog transition:

  1. Two parts need animation: the <dialog> itself and its ::backdrop.
  2. When a dialog is shown, display: none is changed to display: block and transition-behavior enables timing this change with our animation.
  3. When a dialog is shown, it is cloned into the top layer, this also needs to be timed with our animation.
  4. The [open] attribute is used to know when the dialog is open or closed. @starting-style is used during the first render as starting styles.
dialog {
  /* Exit Stage To */
  transform: translateY(-20px);

  &, &::backdrop {
    transition:
      display 1s allow-discrete,
      overlay 1s allow-discrete,
      opacity 1s ease,
      transform 1s ease;

    /* Exit Stage To */
    opacity: 0;
  }

  /* On Stage */
  &[open] {
    opacity: 1;
    transform: translateY(0px);

    &::backdrop {
      opacity: 0.8;
    }
  }

  /* Enter Stage From */
  @starting-style {
    &[open],
    &[open]::backdrop {
      opacity: 0;
    }

    &[open] {
      transform: translateY(20px);
    }
  }
}

Try it

With this snippet as a starting point, you can find three popular dialog experiences for you to take code or inspiration from /have-a-dialog.

The following video shows the excellent keyboard experience. It also demonstrates the interruptible nature of a CSS transition, so a user can close it anytime they want and always see a smooth interface.

nerdy.dev/have-a-dialog

Resources

Transition a popover #

Like a <dialog> element, a popover appears over everything else in the top layer. Light dismiss is the default, and keyboard / focus management is all done for you.

https://codepen.io/argyleink/pen/mdZXzxW

Let's bulid it.

There's an HTML aspect to implementing the UX:

<button popovertarget="pop">?</button>

<p id="pop" popover>An overlay with additional information.</p>

Also, like a <dialog> element, to animate the transition of a popover's display property and insertion into the top layer, you need to combine transition-behavior and @starting-style.

Notice with a popover, the open state isn't an attribute, it's a css pseudo-class :popover-open.

[popover] {
  &, &::backdrop {
    transition:
      display 0.5s allow-discrete,
      overlay 0.5s allow-discrete,
      opacity 0.5s,
      transform 0.5s;

    /* Exit Stage To */
    opacity: 0;
  }

  /* On Stage */
  &:popover-open {
    opacity: 1;

    &::backdrop {
      opacity: 0.5;
    }
  }

  /* Enter Stage From */
  @starting-style {
    &:popover-open,
    &:popover-open::backdrop {
      opacity: 0;
    }

    &:popover-open {
      transform: translateY(10px);
    }
  }
}

Try it

Resources

Transition animation for <details> #

Found on the CSS Wrapped 2024 website in the desktop layout.

The disclosure element (<details>) has been waiting for CSS primitives to unlock its animation potential for many years.

<details>
  <summary>Show disclosed content</summary>
  <p>…</p>
</details>

The details element needs to transition to height: auto from height: 0px and a way to target the slotted content it internally uses for the disclosure. The new interpolate-size feature can be used for the height animation and ::details-content for the selector.

details {
  inline-size: 50ch;

  @media (prefers-reduced-motion: no-preference) {
    interpolate-size: allow-keywords;
  }

  &::details-content {
    opacity: 0;
    block-size: 0;
    overflow-y: clip;
    transition: content-visibility 1s allow-discrete, opacity 1s, block-size 1s;
  }

  &[open]::details-content {
    opacity: 1;
    block-size: auto;
  }
}

Try it

Resources

Bonus attribute #

If you want to connect two or more details elements and have them close each other respectively, you can accomplish this with a shared name attribute on each detail element you want to be connected. Very much like a radio group.

https://developer.chrome.com/blog/styling-details

<details name="linked-disclosure">
  <summary>Show disclosed content</summary>
  <p>…</p>
</details>

<!-- name="linked-disclosure" connects these together -->

<details name="linked-disclosure>
  <summary>Show disclosed content</summary>
  <p>…</p>
</details>

Animated adaptive gradient text #

A bold headline in a design is often complimented with a gradient, helping draw the eye with intrigue and vividness.

Since 2015 the web has been able to create gradient text effects, and in the past 10 years, there have been some updates and enhancements: animation, user preferences and interpolation.

  1. Adapting the gradient text effect to light and dark themes is easy with the prefers-color-scheme query
  2. New animation updates enable gradient effects to go beyond spinning or moving gradient images around, but to change colors over time
  3. New interpolation updates allow those mixes to be more vivid, rich, and interesting

https://codepen.io/argyleink/pen/vEBmZNw

@property --color-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

@property --color-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

@keyframes color-change {
  to {
    --color-1: var(--_color-1-to);
    --color-2: var(--_color-2-to);
  }
}

.gradient-text {
  --_space: ;

  /* light mode */
  --_color-1-from: yellow;
  --_color-1-to: orange;
  --_color-2-from: purple;
  --_color-2-to: hotpink;

  /* dark mode */
  @media (prefers-color-scheme: dark) {
    --_color-1-from: lime;
    --_color-1-to: cyan;
    --_color-2-from: cyan;
    --_color-2-to: deeppink;
  }

  --color-1: var(--_color-1-from);
  --color-2: var(--_color-2-from);

  animation: color-change 2s linear infinite alternate;

  background: linear-gradient(
    to right var(--_space),
    var(--color-1),
    var(--color-2)
  );

  /* old browser support */
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;

  /* modern browser version */
  background-clip: text;
  color: transparent;

  @supports (background: linear-gradient(in oklch, #fff, #fff)) {
    --_space: in oklch;
  }
}

That's quite a snippet 😅 How did it get to that?

Most developers making a gradient text effect are starting here:

.gradient-text {
  background: linear-gradient(to right, hotpink, cyan);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

remove the prefixes #

The first update or enhancement is to remove the prefixes. Although, so older browsers continue to support the effect, add the unprefixed values after:

.gradient-text {
  background: linear-gradient(to right, hotpink, cyan);

  /* old browser support */
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;

  /* modern browser version */
  background-clip: text;
  color: transparent;
}

Use updated gradient interpolation spaces #

Next, improve the quality of the gradient by progressively enhancing the in interpolation syntax with CSS variables and @supports.

You could alternatively repeat the gradient definition and include in oklch in it, which would also work great and support older browsers.

.gradient-text {
  --_space: ;

  background: linear-gradient(to right var(--_space), hotpink, cyan);

  /* old browser support */
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;

  /* modern browser version */
  background-clip: text;
  color: transparent;

  @supports (background: linear-gradient(in oklch, #fff, #fff)) {
    --_space: in oklch;
  }
}

Create typed <color> properties #

For the gradient color animation use @property, like described in snippet #4. The typed color values can be animated inside of a gradient, like a gradient used with text.

@property --color-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

@property --color-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

Now --color-1 can be animated like transition: –color-1 .3s ease or used in keyframes. These values that can animate, can be used anywhere color is allowed, like in a gradient text effect.

@keyframes color-change {
  to {
    --color-1: lime --color-2: orange;
  }
}

.gradient-text {
  animation: color-change 2s linear infinite alternate;
}

Make a few props, Swap em' in a dark MQ #

To keep things declarative, I've also defined color variables to hold the colors for animation.

.gradient-text {
  --_color-1-from: yellow;
  --_color-1-to: orange;
  --_color-2-from: purple;
  --_color-2-to: hotpink;

  @media (prefers-color-scheme: dark) {
    --_color-1-from: lime;
    --_color-1-to: cyan;
    --_color-2-from: cyan;
    --_color-2-to: deeppink;
  }

  /* set our typed variables to the "from" values */
  --color-1: var(--_color-1-from);
  --color-2: var(--_color-2-from);
}

Put all those moments and reasons together, and we have arrived at the final snippet:

@property --color-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

@property --color-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

@keyframes color-change {
  to {
    --color-1: var(--_color-1-to);
    --color-2: var(--_color-2-to);
  }
}

.gradient-text {
  --_space: ;

  /* light mode */
  --_color-1-from: yellow;
  --_color-1-to: orange;
  --_color-2-from: purple;
  --_color-2-to: hotpink;

  /* dark mode */
  @media (prefers-color-scheme: dark) {
    --_color-1-from: lime;
    --_color-1-to: cyan;
    --_color-2-from: cyan;
    --_color-2-to: deeppink;
  }

  --color-1: var(--_color-1-from);
  --color-2: var(--_color-2-from);

  animation: color-change 2s linear infinite alternate;

  background: linear-gradient(
    to right var(--_space),
    var(--color-1),
    var(--color-2)
  );

  /* old browser support */
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;

  /* modern browser version */
  background-clip: text;
  color: transparent;

  @supports (background: linear-gradient(in oklch, #fff, #fff)) {
    --_space: in oklch;
  }
}
🤓

Some years these snippets are short and sweet, but not this year. Watch out for next year's article, who knows what you'll need to know!

Mentions #

Join the conversation on

136 likes
41 reposts
  • Bramus
  • Marcos de Miguel
  • Chris Alley
  • Davide Di Pumpo
  • kopilot
  • rafaFERIAH
  • Amir R Muntasser
  • Jeremy Leroux
  • Jon Henshaw
  • Rafa Romero Dios
  • GENKI
  • Kristof Zerbe
  • Jan Slusarczyk
  • Mario
  • Josh W. Comeau
  • Mike E
  • Charlie
  • Andy P
  • Leopold Kristjansson
  • Necrite
  • Alvaro Montoro
  • Phil Nelson
  • Candido Sales
  • Arunan
  • Sebastian Zelonka
  • Rio
  • Josh Humble
  • Antonio Giroz
  • Jesse Sutherland
  • Alfonso Martínez de Lizarrondo
  • technerd
  • jiceb
  • Garry Aylott
  • mantish
  • Morbi
  • Alan
  • René (Elevenfour) :verified:
  • kaiserkiwi :kiwibird:
  • Felix
  • Carlos E.
????
SamSam
Silly widget, trix are for kids
Adam ArgyleAdam Argyle
Maybe it's a Schrödinger's feature. ????
SamSam
is there a plan for “allowed values” for css custom properties? imo that’s the last negative for using them in design systems. they can be set to literally any value. would be great if there was an allow list for them so if an unrecognized value was set, the default was used instead.
Michael WarrenMichael Warren
A syntax type "allow list" of custom words can be done ????
Adam ArgyleAdam Argyle
????
Oh hm, not a limited set of numbers no. CSS functions could maybe help there
Adam ArgyleAdam Argyle
is it just me or is this article hard to read on mobile? ive tried brave and firefox, theme button isnt working
tannerr ????????tannerr ????????

Crawl the CSS Webring