Skip to content

Add transition utilities #1273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 8, 2020
Merged

Add transition utilities #1273

merged 2 commits into from
Jan 8, 2020

Conversation

adamwathan
Copy link
Member

This PR adds support for transition-property, transition-duration, and transition-timing-function utilities.

Here are the utilities generated by default:

.transition-none {
  transition-property: none;
}

.transition-all {
  transition-property: all;
}

.transition {
  transition-property: background-color, border-color, color, opacity, transform;
}

.transition-colors {
  transition-property: background-color, border-color, color;
}

.transition-opacity {
  transition-property: opacity;
}

.transition-transform {
  transition-property: transform;
}

.ease-linear {
  transition-timing-function: linear;
}

.ease-in {
  transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}

.ease-out {
  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}

.ease-in-out {
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.transition-fastest {
  transition-duration: 75ms;
}

.transition-faster {
  transition-duration: 100ms;
}

.transition-fast {
  transition-duration: 150ms;
}

.transition-medium {
  transition-duration: 200ms;
}

.transition-slow {
  transition-duration: 300ms;
}

.transition-slower {
  transition-duration: 500ms;
}

.transition-slowest {
  transition-duration: 700ms;
}

transition-property

By default we include utilities for transition, transition-none, transition-all, transition-colors, transition-opacity, and transition-transform.

The base transition utility enables transitions for colors, opacity, and transform all at once. I think this is the most popular/useful combination and makes sense as the default, since the alternative would be something horrific like transition-colors-opacity-transform. Transitioning color properties is not quite as performant as opacity and transform (it happens during the paint phase rather than the composite phase) but is still very fast compared to stuff like padding, and basically every website on the internet uses color transitions so I think it's fine.

I considered trying to come up with a CSS Custom Properties based solution to make transition properties composable but there's not really a great way to do it.

Instead, if you wanted to transition say opacity and transform but not colors, I would recommend two approaches:

  1. Add a new key like opacity-transform to your transformProperty theme config.
  2. Just use an inline style, style="transition-property: opacity, transform" — there's no magic numbers or anything to worry about here so an inline style introduces no maintainability issues other than potentially vendor prefixing.

transition-timing-function

By default I've included utilities for ease-in, ease-out, ease-in-out, and ease-linear. These can be customized under the transitionTimingFunction key in your theme config.

I agonized for a long time over what values to include here, and originally was planning to use familiar values from easings.net.

After a lot of time studying animation and reading different resources, the Material Design Motion Guidelines stood out to me as making the most sense and having the best justification for their chosen easing curves, so I've decided to use those three curves as the defaults for Tailwind.

I may add a couple more in the future that have a more exaggerated entrance/exit curve for a more cinematic look but lets see how far we can get with just these three. I did a call with Jonas Naimark who does motion design at Google and in his opinion these three curves are pretty sufficient, and he can achieve everything he needs by just tweaking the duration usually.

transition-duration

By default I've included utilities for transition-fastest, transition-faster, transition-fast, transition-medium, transition-slow, transition-slower, and transition-slowest. These can be customized under the transitionDuration key in your theme config.

These are loosely inspired by values used throughout the Material motion guidelines.

What's not included

  • No transition-delay utilities — we can add these later if people need them, I've personally never needed them though and I was having a hard time deciding on a class name so I've just punted on it for now.
  • No will-change utilities — I never use this and don't really understand it, if someone can make a compelling case with a good demo that proves it's important we can definitely add.

Try it out

Here's a quick and dirty demo (just using pre-compiled CSS):

https://tailwind.run/PfhdtG/1

Any feedback appreciated 👍🏻

@innocenzi
Copy link
Contributor

innocenzi commented Dec 24, 2019

Here are documents about the will-change property:

All content will be re-rastered when its transform scale changes, if it does not have the will-change: transform CSS property. In other words, will-change: transform means “please animate it fast”.

This only applies to transforms scales that happen via script manipulation, and does not apply to CSS animations or Web Animations.

They included this example.


I can see it added but disabled by default. I personally never needed to use it.

@codytooker
Copy link
Contributor

For transition-duration I vote for numbers. Currently this seems like pre 1.0 version of the color scale. Numbers seem even better here because it can be a direct translation

.transition-100 {
  transition-duration: 100ms;
}

@FrankyFrankFrank
Copy link

The way you handle the transition properties into groups here is really good. Is ‘transition-colors’ plural because it has multiple properties?

@adamwathan
Copy link
Member Author

@codytooker The only issue with numbers in this case is that it forces you to memorize which ones exist, like transition-75, transition-200, etc. If you are currently using transition-200 but you want it to be a bit slower, you have to know that 300 is the next one rather than 250.

With colors it's not so bad because it's always steps of 100.

That said I'm not vehemently opposed to it, there's definitely a higher chance of regretting transition-slow than duration-500 or whatever if we ever want to add more values. Just a question of if that regret risk is worth the hit in initial developer experience, because fast/slow is a lot more guessable especially when you want to change to the next fastest or slowest value.

@adamwathan
Copy link
Member Author

Is ‘transition-colors’ plural because it has multiple properties?

@FrankyFrankFrank Yeah exactly 👍 transition-color I think runs the risk of implying it's only transitioning the actual color property.

@FrankyFrankFrank
Copy link

My only comment to Cody’s number suggestion is that it would become slightly more “gotta check the docs to see if this exists”. It’s also true that the difference between 100ms and 200ms is a wide gulf. Even the difference between 100ms and 150ms is negotiable in between sometimes. You would need to include a lot of values.

@adamwathan
Copy link
Member Author

This only applies to transforms scales that happen via script manipulation, and does not apply to CSS animations or Web Animations.

@hawezo Thanks for this, super useful to know. I think if that property is only useful for people doing JS animations that it's better to let them manage that property using the same JS instead of with Tailwind.

@jonaskuske
Copy link

Looks super useful, and love the research you did!

Also concerned about the naming for the duration classes however. As others have pointed out already, with duration times you might need a lot of different values to get the feeling of your transitions just right. Now if I need to add 125ms and 175ms duration classes, I need to consider "Which name expresses faster than fast, but not quite faster? Which name expresses slower than fast, but not medium yet? Or do I just throw away the entire default scale and use numeric values? Or mix expressive names with numeric names?". Avoiding such naming problems as much as possible is one of the greatest strengths of tailwind imo.

I see how it's easier to memorize fast, faster and so on over arbitrary numbers, but:

a) looking up stuff in the tailwind or design system documentation for some (surprisingly short amount of) time until you know it by heart is part of the tailwind learning curve already anyway

b) the VSCode extension makes that way less of an issue by autocompleting the class names


On another note, are the easing function class names so generic on purpose? Could lead to confusion if tailwind adds support for CSS animations at some point in the future, if we then have

/* Why do I need a type prefix for animations but not for transitions? */
.animation-ease-linear {
  animation-timing-function: ease;
}
.ease-linear {
  transition-timing-function: ease;
}

Or is your plan to make them truly generic and use the same class for animations and transitions? :)

.ease-linear {
  transition-timing-function: ease;
  animation-timing-function: ease;
}

@brandonpittman
Copy link

@adamwathan I think delay utilities that match the duration ones would be great for staggered animations. That’s the main reason for delays usually.

@innocenzi
Copy link
Contributor

innocenzi commented Dec 24, 2019

You could just bake it into each transition utility too.

@brandonpittman From my understanding, you shouldn't use will-change in a preventive way, but only as a last resort.

These kinds of optimizations can increase the responsiveness of a page by doing potentially expensive work before they are actually required.

According to this, if you use will-change everywhere, there will be a performance cost for no special reason.


EDIT - Apparently, two comments from Brandon were deleted.

@tomhermans
Copy link

Good stuff. Regarding numbers for duration, I'd keep it at named values. In this case it's simpler to get started quick, and builds upon known conventions, so better DX.
People who take more time to get animation just right in their project will probably tweak the values, add or remove. They can still rename the classnames too, but for most this default will get less in the way.

@codytooker
Copy link
Contributor

@adamwathan I think that does make some sense. The default scale being more of a named scale does make sense for new comers, It's easily editable to a custom scale so, fast, faster, fastest, should work. I'm just always hesitant on that because people inevitably will try and keep the scale but add in their own values in between or after so the naming get's a little weird, but again if someone is editing the scale they can come up with their own names at that point anyways.

Copy link

@lubber-de lubber-de left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should try to avoid all of the !important

@adamwathan
Copy link
Member Author

You should try to avoid all of the !important

That's only when the important option is set to true.

@AlexVipond
Copy link
Contributor

On another note, are the easing function class names so generic on purpose? Could lead to confusion if tailwind adds support for CSS animations at some point in the future, if we then have

Good thought @jonaskuske.

My two cents: I think it probably makes sense to prefix all ease classes with transition- so that they are consistent with the rest of the transition classes, and to explicitly separate transition easings from animation easings (whether those are user-added with plugins or added to Tailwind in the future).

@romansndlr
Copy link

Looks really awesome! Regarding the duration utilities, i agree that using numbers has a trade-off on initial productivity but i believe that almost any meaningful project will be required to add more durations between the default values and having the named scale will make that more difficult.

@adamwathan adamwathan mentioned this pull request Dec 25, 2019
4 tasks
@adamwathan adamwathan changed the base branch from v2 to master December 25, 2019 18:54
@mrcsmcln
Copy link

This is great, thanks so much!

I'm wondering if it makes sense to extract transitionDuration and transitionTimingFunction to standalone theme properties for reuse, much like spacing. This could open the door to more consistency with transition-delay and animation-*. For example, in tailwind.config.js:

{
  durations: {
    fastest: '75ms',
    faster: '100ms',
    fast: '150ms',
    medium: '200ms',
    slow: '300ms',
    slower: '500ms',
    slowest: '700ms',
  },
  easing: {
    linear: 'linear',
    in: 'cubic-bezier(0.4, 0, 1, 1)',
    out: 'cubic-bezier(0, 0, 0.2, 1)',
    'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
  },
  transitionDelay: theme => theme('durations'),
  transitionDuration: theme => theme('durations'),
  transitionTimingFunction: theme => theme('easing'),
}

Regarding easing class names, I think I'm in agreement with @jonaskuske. These names seem a bit too generic and future support for animation could prove troublesome. In #378, I mentioned the possibility of adding a generic .transition and/or .animation class to indicate that an easing or duration is intended for a transition and/or animation, vaguely similar to how .group works for group-hover. This could be a middle ground between the current generic names and something super-declarative like .transition-duration-fast.transition-timing-function-ease-in-out, the latter of which just seems insane.

This middle-ground approach would look something like this:

<div class="transition duration-fast ease-in-out">foo</div>

And the relevant generated CSS would look like this:

.transition.duration-fast {
  transition-duration: 150ms;
}

.transition.ease-in-out {
  transition-timing-function: ease-in-out;
}

I've been using this approach for production sites for a couple of months and I really don't have any complaints. And being that I work at a web design studio that uses tons of transitions, I use these utilities a lot.

@AlexVipond
Copy link
Contributor

@jonaskuske @tomhermans @codytooker @romansndlr I just finished opening a new issue for some more general work on Tailwind class names—feel free to chime in #1277

@adamwathan
Copy link
Member Author

Bunch of thoughts on some of the things that have been mentioned so far...

Adding delays

I've been thinking about the real-world use-cases for adding transition-delay utilities and I'm not 100% convinced they will be that useful as CSS classes.

Very often you use delays for staggered animations, so transitioning x elements offset by 50ms each. In these situations you are very often generating these elements in some sort of loop and doing some basic ${i * 50}ms math to determine the delay values for each item.

That means this sort of thing is often better handled by an inline style, or by whatever JS you are probably already writing to manage the whole thing.

Similarly you often want something to stagger in, but not stagger out, which means the delay values need to be in place for the "enter" phase of a transition but not be in place for the "exit" phase, so since they need to be managed conditionally that basically necessitates managing it with JS already.

I'm also not convinced that even if we did at delays that they should match the durations, again because in a stagger transition you are using some consistent offset (like 50ms) which would lead to delay values like 50ms, 100ms, 150ms, 200ms, 250ms, 300ms, 350ms, etc., whereas for durations it's not as important that we have values that are only 50ms apart at the higher end of the scale.

More on this next.

Default duration values

We could just include a more comprehensive scale that jumped in 50ms increments and use it for both duration and delay, but in my opinion that is lazy and conflicts with the philosophy we've applied to come up with all of Tailwind's other default values, which has been to agonize over the defaults and make it as easy as possible for people to fall into the pit of success with their designs by making it as hard as possible to pick the "wrong" value for something. Offering a 600ms duration and a 650ms duration encourages people to nitpick and will slow them down instead of speeding them up.

For this reason I think it's important that the default duration values get progressively longer, like the defaults I've used in this PR. I do worry that people may want a value higher than 700ms though (1s is not uncommon in sites I audited), so it's worth exploring how we might solve that.

Naming the transition-duration utilities

Right now this PR includes these utilities for setting the duration:

  • transition-fastest → 75ms
  • transition-faster → 100ms
  • transition-fast → 150ms
  • transition-medium → 200ms
  • transition-slow → 300ms
  • transition-slower → 500ms
  • transition-slowest → 700ms

This naming convention has a few benefits:

  • The transition- prefix means it could nicely co-exist with animation- utilities down the road
  • The fast/slow scale is easy to use and very guessable — if fast isn't fast enough, switching to faster requires no real knowledge of the scale
  • It's pretty short — I really hate using three words in a utility name and avoid it as much possible. I'd rather introduce potential collisions over really long class names, which is why we have font-sans and font-bold instead of font-family-sans and font-weight-bold. Tailwind causes class attributes to get really long as it is, so I try to save characters wherever I can.

It has one considerably downsides of course as well which is that if you wanted to add a 1s value but keep the default scale, you'd have to come up with a name that deviates from the naming convention, since a fastest/slowest scale is limited to 7 entries by the English language.

This is not an insignificant limitation and is a very strong argument for considering a different scale.

Option 1: transition-{ms}

One option is to just switch fast/slow to the actual millisecond value, like transition-75, transition-100, etc.

Pros:

  • Easy to extend
  • No collisions with potential animation utilities
  • Short

Cons:

  • Hard to guess
  • I just hate the utility names at a superficial/aesthetic level

Option 2: duration-{ms}

Instead of transition-fast, we could use names like duration-150.

Pros:

  • Looks nice
  • Easy to extend
  • Short

Cons:

  • Hard to guess
  • Bad collision with animation utilities — we'd be forced to use animation-duration-150 if we added animation utilities

One way to avoid the collision is to require end users to combine this utility with another utility like transition and use compound selectors like .transition.duration-150 but nothing else in Tailwind works this way and I would rather not set that precedent. Simple, separate utilities seems much less likely to be regretted long-term.

Option 3: transition-duration-{ms}

This is too long for something that I think will be commonly used so not really willing to entertain this one.

Looking at all of these options I'm still not sure what the best approach is. transition-150 is likely the "safest" but I can't stand looking at it, and the fact duration-150 would force us to use animation-duration-150 in the future is a hard pill to swallow.

Overall I am leaning to sticking with transition-fast/slow, because if necessary users can customize it to transition-150/300/500 without any changes to Tailwind itself, so since transition-150 is the "safest" option anyways I think I'm okay with this approach.

Naming the transition-timing-function utilities

I chose ease-in/out mostly because I don't want to force people to use long utility names like transition-ease-in. This has the same issue as using duration-{ms} unfortunately, which is that it collides directly with any potential animation utilities.

Maybe three words is okay in this case? Tough one because I really do love how something like transition transition-fast ease-in reads.

One alternative is to rename the easings based on their use case, so maybe ease-in becomes transition-leave since ease-in should only be used for things leaving the screen, ease-out becomes transition-enter for similar reasons, and ease-in-out becomes transition-move. There are some situations where an ease-out looks better even for things that are changing places on screen rather than strictly entering the screen though, so naming things at this level of abstraction could discourage people from using transition-enter in those situations just because it looks "wrong".

Admittedly if I do decide to keep these named like ease-in, I should probably rename transition-fast as well because it doesn't make sense to use transition-fast to avoid collisions with animation-duration when using ease-in forces the same situation, leading to inconsistency.

If I were to change the duration utilities with no consideration for the animation collision, it'd be a toss up between duration-short/long and duration-150/300.


Overall I don't have any clear decisions here, but just laying out the opinions I do have.

One thing to remember is that for the most part, making these names "perfect" isn't that important — people will learn the names and once they learn they won't care. It's the same reason it wasn't worth renaming rounded-md to radius-md even though I like radius better — it just doesn't matter, you learn the utilities and once you learn them you stop really reading them as English and start reading them as "Tailwind".

Another thing worth mentioning is that I think ambiguity in general is relatively okay for Tailwind, and it's a trade-off we make fairly often.

For example self-center could be align-self: center or justify-self: center. We chose to use self-center instead of align-self-center because align-self is 100x more commonly used than justify-self (which only works in CSS Grid and is something I've literally never needed in practice), and were comfortable with the fact that if we did add justify-self support we'd have to call it justify-self-center. I'd rather use the two-word self-center class for the more common case than pay the three-word penalty on both utilities.

So with that in mind maybe it's fine to optimize for transitions since they are (I believe anyways) significantly more common than animations? duration-short for transitions, animation-duration-short for animations? I don't think that would bother me that much.

Again, still don't know what I want to do here, but that's my brain dump.

@allenjd3
Copy link

A lot of IDEs will autocomplete making the difficulty of guessing not as big of an issue- on the other hand, I don't think having to deviate from the naming convention to extend would be too bad either since you could just prefix your changes.

@AlexVipond
Copy link
Contributor

For duration utilities, I really dig the simple transition- prefix over duration or transition-duration. I agree that transition-duration- is too wordy and duration-'s naming collision with animation-duration is off-putting.

I also am in favor of -fastest and other semantic suffixes, and I'm pretty strongly opposed to anything ending in -{ms} because it forces the user to remember specific underlying values from the design system.

More on that: ease of configuration is amazing in Tailwind, and -{ms} class names are easier to configure and customize, but I've always gotten the impression that Tailwind's highest priority is to provide a user-friendly abstraction for an extremely thorough and useful design system, and configuration/customize is a close second place.

And semantically named classes aren't impossible to add values to—they can have names like transition-fastest.5, mixing semantic and numeric naming. It's funky, but possible, and I'm sure other people have or will come up with other similar ideas.

Take my opinion with a grain of salt though, because I'll definitely be applying a custom linear numeric naming convention to transition duration utilities 😅

@Chadh13
Copy link

Chadh13 commented Dec 28, 2019

Naming the transition-duration utilities

I'm personally a fan of the proposed naming (ie: transition-slowest -> transition-fastest) for the following reasons:

  1. Named transitions reinforce the usage of common standards through a comprehensive design system, rather than venturing off into the land of specialization.
  • If you're a developer with no designer, this gives you superpowers without having to get technical.
  • If you're a developer on a team, this adds one more barrier to prompt you and your dev/design team to have a discussion for necessity before veering off from an already agreed-upon and cohesive design system.
  • In the case that you as the "brave hero" dev decides to add a custom transition because the ones from your design system don't fit, then you end up with some level of arcane custom CSS and JS which can act as a code smell to the rest of the team -- and prevent them from further perpetuating usage of this arcane class in other contexts.
  • Convention over configuration. I would argue that the practical cases where you are likely to need custom transitions that cannot replace your design system (tailwind config) defaults are largely driven by specialized marketing pages. In those cases, the customized animation and transitions are very unlikely to be needed to reuse across the rest of your web properties.
  1. They follow the same convention as the typography utilities (size, leading, tracking) which hide the complexity and technical details of implementing your design system. I believe that this is a pro for any team.
  2. Extending your design system through the tailwind config is very easy.

Naming the transition-timing-function utilities

Going along with the idea of naming these functions based on their use case, here are some potential options:

  • transition-in transition-fastest
  • transition-out transition-fastest
  • transition-once transition-fastest (for running a transition one full time in-and-out)
  • transition-continuous transition-fastest (for repeating transitions)
    &&
  • transition-in (ease by default) transition-fastest
  • transition-in transition-linear transition-fastest

where ease is the default and transition-linear is an additional flag for overriding that default.

I tend to like these much better than ease-in / ease-out / ease-in-out because again, they abstract the need for knowing technical implementation details about animation until you need to get in there and customize, progressive disclosure style.

There is a pretty obvious risk that this becomes unwieldy as the number of transition timing functions grows, however that seems unlikely based on your current research and discussions. The assumption being that the vast majority of animations on the web today are implementing, making use of, and capable of building with the ease functions.

@mrcsmcln
Copy link

mrcsmcln commented Dec 28, 2019

What if we did this by default?

<div class="duration-fast delay-short ease-in"></div>
.duration-fast {
  animation-duration: theme(animationDuration.fast);
  transition-duration: theme(transitionDuration.fast);
}

/* .delay-short .ease-in, etc. */

And if you want to override, say, the easing function for animations only, you can use a variant:

<div class="duration-fast delay-short ease-in animation:ease-out"></div>
.animation\:ease-out {
  animation-timing-function: theme(animationTimingFunction.ease-out)
}

Benefits:

  • Intuitive
  • Concise
  • Flexible
  • Scalable
  • Pretty

Disadvantages:

  • Sets another precedent?

Alternatively, we could approach this like padding et al., where .p-* refers to the shorthand and there are derivatives such as .px-* designed for override use:

<div class="duration-fast delay-short ease-in a-ease-out"></div>

(or similar). Basically, .ease-* is a sort of shorthand and .a-ease-* (or .animation-ease-*, .ease-*-a, whatever) is the animation override.

@adamwathan adamwathan force-pushed the transition-utilities branch from a9d1e0b to d5199e4 Compare January 8, 2020 15:12
@adamwathan adamwathan merged commit eb8900c into master Jan 8, 2020
@adamwathan adamwathan deleted the transition-utilities branch January 8, 2020 15:13
@terryupton
Copy link

One issue I have come across today is that iOS chrome seems to have issues with transforms, in that it doesn't transform them, it just jumps from one to the other. I don't think this is exclusive to Tailwind, but thought I would mention it. I am not sure of the solution (if there is one) yet, but I will try and do some research and post back here...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.