Emotion: className interpolation alternative

Created on 21 Jan 2019  路  28Comments  路  Source: emotion-js/emotion

  • emotion version: 10.0.6
  • react version: 16.8.0-alpha.1

Relevant code:

import { css } from 'emotion';

const childClass = css`
  display: none;
`;

const containerClass = css`
  &:hover .${childClass} {
    display: block;
  }
`;

const Home = () => (
  <div className={containerClass}>
    <h1>Hello world</h1>
    <div className={childClass}>Hi there!</div>
  </div>
);

Problem description:

Hello, I'm getting a warning that class interpolation will not be supported in the next major release of Emotion, but I didn't find any migration instructions for it and I'm not sure how to implement the example above without interpolation and without using "regular" class names.

I know its use is problematic when using it with composition, but I also don't know about the alternative in the use cases like the one above. Does the warning mean the feature will be removed or does it mean it will be undocumented in 11 and there will be no guarantees after that (could be removed without notice)?

https://github.com/emotion-js/emotion/blob/86700d21ccd8bcc6fc2740c5d6b381dfe8a9bce1/packages/serialize/src/index.js#L208

question

Most helpful comment

It doesn't seem to be strictly equivalent to class interpolation, is it ?
Using .hidden in your example could yield unexpected results if the hidden class is associated to some non-namespaced rules elsewhere.
My understanding is that with class interpolation it is strictly namespaced.

All 28 comments

The easiest way is to use attributes or class names as you would before.

Class Names

import { css, cx } from 'emotion';

const childClass = css`
  display: none;
`;

const containerClass = css`
  &:hover .hidden {
    display: block;
  }
`;

const Home = () => (
  <div className={containerClass}>
    <h1>Hello world</h1>
    <div className={cx('hidden', childClass)}>Hi there!</div>
  </div>
);

Attributes

import { css } from 'emotion';

const childClass = css`
  display: none;
`;

const containerClass = css`
  &:hover [data-hidden] {
    display: block;
  }
`;

const Home = () => (
  <div className={containerClass}>
    <h1>Hello world</h1>
    <div className={childClass} data-hidden>Hi there!</div>
  </div>
);

Note that this works for any version of emotion. If the className is not recognized by emotion it will simply be appended to the className prop.

It doesn't seem to be strictly equivalent to class interpolation, is it ?
Using .hidden in your example could yield unexpected results if the hidden class is associated to some non-namespaced rules elsewhere.
My understanding is that with class interpolation it is strictly namespaced.

so there is no strict alternative ... 馃様

New emotion user here! I have started the process of moving over from CSS modules to emotion and hit this issue in my first component conversion. The doc seems pretty light on practical examples of moving from CSS to emotion (separate issue, if you know of such examples/guide message me 馃槂), but the solutions here seem to be to subvert my new CSS tool to do a very common CSS thing. Granted I am brand new to the CSS in JS world and the aim is probably not to duplicate all CSS features as some are deemed 'harmful'.

Seeing as this issue is closed, is the current stance that emotion will not provide support for this use case in the future?

This seems like a necessary feature. People coming from Sass, Less, Stylus, JSS, etc, all expect this basic functionality.

You can do styled-components interpolation if you use the Emotion babel preset, or the Emotion macro.

import styled from '@emotion/styled/macro';
const ChildComponent = styled.div`
  display: none;
`;

const Container = css`
  &:hover ${ChildComponent} {
    display: block;
  }
`;

Alternatively, you can use class name interpolation, if you use the emotion package:

import { css } from 'emotion';

const childClass = css`
  display: none;
`;

const containerClass = css`
  &:hover .${childClass} {
    display: block;
  }
`;

const Home = () => (
  <div className={containerClass}>
    <h1>Hello world</h1>
    <div className={childClass}>Hi there!</div>
  </div>
);

Alternatively, you can use class name interpolation, if you use the emotion package:

This is the setup that I am using currently, and IMO seems like the more natural usage. However, this is what brought me here as when you do this you get:

Interpolating a className from css`` is not recommended and will cause problems with composition. Interpolating a className from css`` will be completely unsupported in a future major version of Emotion

So do we have any proper official workaround for this?

The way @nlrowe is doing works when using styled components, and I also expected it to work the same when using css, it looks like a really crucial functionality.

The solution @tkh44 gave is far from optimal since, as already said, could easily cause unexpected behaviour in case there are multiple elements using this "hidden" class for example. The whole point of using interpolation is to take advantage of the namespace.

Also I think this should be reopened as there's clearly not a proper answer or workaround to the problem yet.

I don't have background as to why class name interpolations are being deprecated, other than possible composition issues, but I have been getting around this warning via _nested_ template strings. This prevents the css function from identifying the class name as a cached class. I haven't had any problems with this approach ..._yet_. Though something official would be super helpful without relying on the React based @emotion/core package since I have many non-React use cases.

An example:

const style1 = css`
    color: red;
`

const style2 = css`
    color: green;

    ${`.${style1}`} {
        color: blue;
    }
`

or something more reusable...

const cssName = (name: string): string => `.${name}`

const style1 = css`
    color: red;
`

const style2 = css`
    color: yellow;

    ${cssName(style1)} {
        color: red;
    }
`

I'm going to reopen this since I feel like it needs a proper answer.

This also looks related to #1192.

Emotion (as many other CSS-in-JS libraries) tries to avoid the cascade as much as possible to avoid strong coupling between components and this is what you propose here. We believe that doing prop-based styling (limited to own props) leads to more maintainable styling overall.

We recognize the problem that there are not many practical examples in the documentation that would present solutions for those common patterns in plain CSS and we'll try to provide them in the near future.

Thank you for the info!

Emotion (as many other CSS-in-JS libraries) tries to avoid the cascade as much as possible to avoid strong coupling between components

Completely understand this, as I do the same thing while building components no matter which CSS tool I am using with the emphasis on _between components_.

and this is what you propose here

Just for a little bit of user perspective (albeit maybe an ignorant one) this is not what I would propose. I only use the cascade in styles for the same component, in the same file. And the component is the only thing that imports that file.

For example, I have a custom Radio component that has a container div, input and label. When a user passes the disabled prop, I add disabled styles to all three elements. This is very convenient to do using the cascade as opposed to creating three separate classes and doing three prop checks to apply them.

You could argue that creating the extra classes is better, or maybe even that creating two more components (RadioInput, RadioLabel) would be better. Either way, I personally still think there are valid use cases for the cascade that preserve encapsulation at the boundaries you set as a component designer.

This change will kind of tie your hands in the way you implement (maybe intentionally) as well as have an unfortunate side effect of the original issue from the OP, for which I still think people would be interested in seeing a solution that did not require users to go outside of the tool.

For example, I have a custom Radio component that has a container div, input and label. When a user passes the disabled prop, I add disabled styles to all three elements. This is very convenient to do using the cascade as opposed to creating three separate classes and doing three prop checks to apply them.

I agree 100% with everything said by @nlrowe, especially this part which is an extremely common use case for me in most projects I develop.

As already said, no one is proposing tight coupling between different components by using the cascade, we're coupling internal styles of the component, which are already scoped, for convenience and also for maintainability, because I would argue that it's way easier to write code as in the mentioned example rather than by passing props for every single class.

It's just overcomplicating something that should definitely not be complicated. Cascade doesn't cause problems in such well defined boundaries, it becomes a problem when you have a global stylesheet for your project.

So, how am I supposed to change the style of a child component when the parent is hovered?

Can we open up this issue again? The need to style a child element, of a component, based on whether the parent is hovered, for example, is a valid practice. It is a valid issue people have.

EDIT: Also, it is still possible to do

:hover {
  > htmlElement {
    something
  }
}

so, it seems as a weird limitation to not be able to do the same thing with classes.

100% agree with @antitoxic and others in this thread.

For example, showing an edit button on a list item row when hovered (changing css display or visibility). I should be able to do this by styling the parent. Having to listen to mouseenter and mouseleave events on the parent, storing state, and using that to style the child is not a cleaner option. Even performance wise, I need that hover effect to be as fast as possible.

For example, showing an edit button on a list item row when hovered (changing css display or visibility). I should be able to do this by styling the parent. Having to listen to mouseenter and mouseleave events on the parent, storing state, and using that to style the child is not a cleaner option. Even performance wise, I need that hover effect to be as fast as possible.

I just ran into the same exact problem, my hacky workaround is type selectors and nth child selectors 馃槖

Had the same problem last week. My "fix" was a rollback to 9.x. Upgrading to 10 came with to many challenges.

Sorry, we don't plan to bring back support for this - truth to be told we don't believe using any descendant selectors is a good idea. It's sometimes convenient - that's for sure, but it makes for a tightly coupled styles which are harder to maintain over the long period of time.

And we are not alone with that. Let's take a look at a recent post by Facebook - https://engineering.fb.com/web/facebook-redesign/ . We can find this there:

We guarantee that the styles are applied in a stable order, and we don鈥檛 support CSS descendant selectors.

If you really need to target a descendant element with emotion the best you can do is to use data attributes to select them, but again - we don't recommend this.

I understand, and I might even agree. In my case, I'm just stuck with a code base that uses them, and I don't have the time to migrate all those cases.

So I respect your decision. Even though it means that I'm stuck to using 9.x.

So, how am I supposed to change the style of a child component when the parent is hovered?

@Andarist ^

I appreciate the 'no descendant selectors' position, but I don't see an alternative for hover. Thank you and everyone else that contributes to Emotion. <3

Also, note that :hover has some problems associated with it - you can read more about it here: https://react-spectrum.adobe.com/blog/building-a-button-part-2.html

@Andarist First I want to thank you and the rest of people that have been working on emotion-js. That is still one of my favourite projects about styling and motivated me enough to try inline styling for one pet project.

I understand your position on descendant selectors.

My position however is that:

  1. I don't want to deal with JS when I want a simple hover style change
  2. I also have projects that don't care about touch devices and especially projects at my work place as they tend to be more enterprise-oriented.
  3. I don't want to change the DOM (adding classname) when I can just use a already existing CSS-only functionality

I think a completely valid and common case could be having a Card element on the page (card elements are quite common right now) that includes things like icons inside of it. The whole card element is interactable as a single action (click). When the card is hovered I want the icon to change color.

This all happens in the same React component, there is no fear or styles messing up as the referred Facebook article suggests. There is also no touch screens.

When I can I want to keep it simple. Actually that issue made me switch to linaria. I miss things from emotion there but I feel grown up enough to decide my project convention, not my styling technology to decide it for me.

Well - ultimately everyone has to decide what's best for them and their project. That is expected. We don't want to facilitate all possible use cases and all people's needs. We purposefully constraint the possibilities that are baked in the vanilla CSS because we believe that it's a good tradeoff for benefits that it gives us - or just because it forces us to write better code. That can be - more maintainable, but it can also be - more accessible. I don't believe that in 2020 "There is also no touch screens" is a valid statement to make for any project unless it's internal and you control all devices that can access it.

A huge percentage of web traffic comes from mobile devices - and sure, many people just visit web pages and rarely access more complex applications from their web browsers, but there is also a significant amount of people doing that (accessing complex applications from mobile web browsers). Even if it's just for a quick look or anything else - people have mobiles in their pockets most of the time (if not all of the time) and they want to use the applications they need using that device. It shouldn't be required to get to your desktop to get a decent UX experience.

there is no fear or styles messing up as the referred Facebook article suggests.

I have found this not to be true - it often starts as a true statement but as the project grows people just tend to take shortcuts and go for those simpler solutions even for components higher up in the tree, creating potential problems for the project in the future.

It's a similar case as with design systems - you certainly should have one which provides a constrained way of building your application and you most likely shouldn't allow for free-form customization based on descendant selectors or any other techniques.

We don't want to facilitate all possible use cases and all people's needs.

I absolutely understand. Especially for open source projects I believe owners/maintainers should have this freedom.

... in 2020 "There is also no touch screens"

Well, sadly there many enterprise clients with massive fleet of desktop computers and products that are only used on-prem. So such projects do exist. May be these fall into "internal" category. But still, many products state straight out "we don't support touch/mobile devices" as they don't want to invest extra effort for something the clients won't pay them for.

And still we as developers want to be able to do inline css.

... as the project grows people just tend to take shortcuts

True. And that can be successfully prevented by custom lint rules allowing only nesting selectors within the same component. We are not talking about free-form selectors but component-bound selectors.

In any case I am not arguing against your decision, just layouting out that other cases are valid.

How about that:

const errorMessage = css`
  color: red;
`;

const input = css`
  &:invalid ~ .${errorMessage} {
    opacity: 1;
  }
`;

How can we apply styles to error message without selector ? We can't reproduce the same behavior only with JS

We've also encountered this problem when rewriting some old scss code into emotion. But also there is one gimmick with class names generated by emotion. They are exactly the same if the content is exactly the same. So

const title = css`
  font-weight: bold;
`;

const paragraph = css`
  font-weight: bold;
`;

console.log(title === paragraph);

So using a generated class name as a cascade selector you can go into some troubles: fiddle with an example

They are exactly the same if the content is exactly the same.

That's by design and that's one of the problems you might run into if you try to use descendant selectors with emotion-generated classnames. That's why we don't recommend using such patterns.

Was this page helpful?
0 / 5 - 0 ratings