Emotion: RTL support

Created on 8 Aug 2018  路  27Comments  路  Source: emotion-js/emotion

Could someone please elaborate on the recommended approach for RTL support with emotion? While searching, I could only find hints that RTL might be possible with a custom stylus plugin. New react users with RTL project requirement would really benefit from any wisdom and experience of any emotion users supporting this. styled-components appears to have no support so being more explicit about how to accomplish it with emotion could be a deciding factor. Should we be using some recommended stylus plugin? Should we be using something like airbnb/react-with-direction? Thank you for your help!

feature request

Most helpful comment

For everyone in this issue, the intended way to do RTL in v10 is something like this. https://codesandbox.io/s/r7yq2lozr4

All 27 comments

Just a note - stylus !== stylis

Emotion is using under the hood stylis and it seems that the easiest way to handle RTL in ALL cases would be in fact using additional stylis plugin for RTL, but I'm not aware if there is any public at the moment.

It would probably have to be used with something like airbnb/react-with-direction but in general some other approaches could be taken to handle this case.

Sorry for not being more concrete, but I have no better answer from top of my head right now.

We just solved this problem last week, we wrote a simple Stylis plugin that uses CSSJanus to perform the RTL conversion of the rules that can be automatically detected. For other things that need to be flipped but can't be automatically converted (such as icons) we added another simple plugin that adds a mirror() function that adds a rule to transform / flip.
The latter is probably best done with the react-with-direction from airbnb, but we didn't have the luxury of rewriting our code base for that.

@Enalmada I'll try to provide an open source example of the plugin soon, I didn't do so yet as the RTL detection is currently based on the dir attribute of the documentElement which is a bit shady. Let me know if you need any more pointers and I'll try to help out.

@jopdeklein I believe an open source example would be very helpful to the emotion community, even if it is shady. For those like me just investigating which technologies match current/future requirements Fela seems to be the only popular CSS-in-JS option openly supporting RTL (fela-plugin-bidi, fela-plugin-rtl).

Hi @Enalmada, I've published a Stylis RTL plugin (works similar to the fela-plugin-rtl but operates on strings instead of objects) and added some simple instructions how to use it with Emotion.

https://github.com/FindHotel/stylis-rtl

and on NPM:
https://www.npmjs.com/package/stylis-rtl

I left out the conditional registration of the plugin as switching between LTR and RTL is project specific, but the way we currently do it:

const isRTL =
  typeof document !== 'undefined' &&
  document.documentElement &&
  document.documentElement.getAttribute('dir') === 'rtl'

That won't work for server side rendering obviously so you might need to adapt the switching strategies to your needs.

Hope this helps, let me know if you need more info.

Hi @Enalmada, I've published a Stylis RTL plugin (works similar to the fela-plugin-rtl but operates on strings instead of objects) and added some simple instructions how to use it with Emotion.

https://github.com/FindHotel/stylis-rtl

and on NPM:
https://www.npmjs.com/package/stylis-rtl

I left out the conditional registration of the plugin as switching between LTR and RTL is project specific, but the way we currently do it:

const isRTL =
  typeof document !== 'undefined' &&
  document.documentElement &&
  document.documentElement.getAttribute('dir') === 'rtl'

That won't work for server side rendering obviously so you might need to adapt the switching strategies to your needs.

Hope this helps, let me know if you need more info.

Can you please add an example on the README? Would be great :)

@OmarZeidan registering the plugin as mentioned in the docs will convert all rules automatically: https://github.com/FindHotel/stylis-rtl#using-with-emotion - once registered everything that Janus can automatically convert will be converted without any additional work on your part.

My suggestion above can be used to conditionally register the plugin based on the direction of the document, feel free to use that if it's good enough (we are using it in production since August without any issues).

If you for some reason don't want to convert a particular rule you can use comments as supported by Janus (see: https://github.com/FindHotel/stylis-rtl/blob/master/src/stylis-rtl.test.js#L15).

Let me know if you have any particular questions I can help you with to get you set up.

It'd be sweet if we could do this:

import {stylis} from 'emotion'
import stylisRTL from 'stylis-rtl'

const isRTL =
  typeof document !== 'undefined' &&
  document.documentElement &&
  document.documentElement.getAttribute('dir') === 'rtl'

if (isRTL) {
  stylis.use(stylisRTL)
}

This would make my life a LOT easier. I've got many teams at PayPal that will need the RTL plugin out of the box. I can publish my own package that does this, but then any packages that they use or the babel plugins will all still reference emotion instead of my own version of emotion that comes with the plugin. So then I have to tell every one of the teams that their webpack configuration needs to include a special alias for emotion. This all just adds a bunch of complexity for them that I really don't want them to have to bear.

I'd rather just say: "Import this file in your src/index.js file" and I'll add the plugin. It's a lot simpler that way.

Any thoughts on that?

In v10, it'll be possible to provide stylis plugins via a provider.

That's perfect! Thanks @mitchellhamilton!

@kentcdodds Sounds exactly as I would think of it as its really a pain supporting RTL now and making sure that everyone (remotely) using the same version of emotion.

@mitchellhamilton looking forward!

@OmarZeidan registering the plugin as mentioned in the docs will convert all rules automatically: https://github.com/FindHotel/stylis-rtl#using-with-emotion - once registered everything that Janus can automatically convert will be converted without any additional work on your part.

My suggestion above can be used to conditionally register the plugin based on the direction of the document, feel free to use that if it's good enough (we are using it in production since August without any issues).

If you for some reason don't want to convert a particular rule you can use comments as supported by Janus (see: https://github.com/FindHotel/stylis-rtl/blob/master/src/stylis-rtl.test.js#L15).

Let me know if you have any particular questions I can help you with to get you set up.

Thanks for that @jopdeklein

Quick question, lets say I have padding: 8px on English, but want it to be 6px on Arabic, any special comment for that?

Example:

font-size: 14px; /* rtl: 12px; */

Wouldnt something like this work?

const Div = styled.div`
  font-size: 14px;

  html[dir=rtl] & {
    font-size: 12px;
  }
`

@Andarist works, but this will add unused CSS for you, which the plugin should switch without adding any unnecessary code.

So I don't think this is supported right now - you could maybe propose such feature to the cssjanus project - https://github.com/cssjanus/cssjanus . Or you could try writing a simple stylis plugin yourself.

I just hope the community can always have the RTL support in mind whenever building a new solid tool. Will see what I can do and looking forward to have a more solid builtin solution for this.

I have used a plugin for Webpack, but didn't try to tweak it for styled components.
RTLCSS.

As mentioned above, we already have a plugin. We just need a better way to provide plugins to emotion without having to create our own instance of emotion which is going to work in emotion 10 with a provider which is a fine solution 馃榾

@kentcdodds And are we going to be able to write

font-size: 24px; /*rtl: 16px;*/

and RTL output to be 馃憞馃徏

font-size: 16px;

?

I'm pretty sure that feature is available in the plugin we're talking about: https://github.com/FindHotel/stylis-rtl
Which uses https://github.com/cssjanus/cssjanus which I believe is capable of specifying roles which should not be flipped.

@kentcdodds true, it uses /* @noflip */ rule.

But this is not what am talking about, so let me explain.

I want the font-size to be 24px on LTR and 16px on RTL.

example:

.element {
font-size: 24px; /*rtl:16px*/
}

This should throw font-size: 24px for LTR websites and font-size: 16px for RTL.
but couldn't find any rule in cssjanus doing this :)

I see... I guess you'd have to file an issue on that repo...

@OmarZeidan
The RTL plugin is purely aimed at flipping things with Janus, but we have the need to flip / mirror arbitrary rules (such as icons where a left/right pattern matching doesn't suffice).

We opted to write another custom 'mirror' plugin which can flip any selector:

.foo {
  background: url(flipMe.png);
  mirror() // this gets replaced by the plugin with a transformation to mirror this selector
}

A dummy implementation would then remove the mirror() rule in an LTR context, which so far meets all our needs.

However I think the solution you're proposing is more flexible, perhaps you can:

Alternatively, feel free to fork and implement the comment based control directive alongside CSSJanus in https://github.com/FindHotel/stylis-rtl and I'll be happy to accept your PR. Have a look at the plugin section of Stylis, it's quite straightforward: https://github.com/thysultan/stylis.js#plugins

Hi @jopdeklein

Thanks for the update.
However, I can see that create-emotion-styled been removed from the new emotion update, not so sure why. but still can use it, however, this breaks the sourceMaps as well, any thoughts?

For everyone in this issue, the intended way to do RTL in v10 is something like this. https://codesandbox.io/s/r7yq2lozr4

Cool, thanks @mitchellhamilton

Will look on customizing the RTL plugin then.

Thank you @mitchellhamilton!!

@mitchellhamilton (or anyone else) I'm trying to implement RTL using the example in codesandbox.

The issue I'm running into is that I'd like to be able to switch between LTR and RTL languages at runtime (if possible). I added the <CacheProvider> and made it so that it re-renders the app at the top level when the language changes:

const emotionCache = {
  LTR: createCache({
    key: 'ltr',
  }),
  RTL: createCache({
    key: 'rtl',
    stylisPlugins: [stylisRtl],
  }),
};

function renderApp(direction) {
  const selectedCache = direction !== 'rtl' ? emotionCache.LTR : emotionCache.RTL;
  render(
    <Provider store={store}>
      <CacheProvider value={selectedCache}>
        <App />
      </CacheProvider>
    </Provider>,
    document.getElementById("root")
  );
}

// called the first time the app loads. It is also called when the language changes (not shown)
renderApp(currentDirection);

However, this doesn't seem to be enough:

  • It doesn't re-render the children to use the new key
  • the old style rules aren't removed. (When I don't use a different key it just adds duplicate style rules, presumably loaded from the cache)

Is there a way to force clear the cache, or create two cache providers and have it switch between them at runtime? Or is this just not supported for now?

For right now (I'm still in proof-of-concept stage) I'm going to just make it force-reload the page when the language changes, but if there's a better way to do this I'd be very interested to know it.

Thanks!

It seems like all comments are stripped out of the source CSS before being passed to stylis plugins, so the /* @noflip */ functionality of the cssjanus library used by stylis-rtl plugin breaks. Is there a way to tell emotion not to strip out css comments that are intended to control stylis plugin functionality? It is important to be able to disable specific css properties or rules from being auto-flipped in RTL for real-world apps.

I tried removing the babel plugin to see if that was causing it, but even manually using /* @jsx jsx */, the css property still seems to remove all comments before the content is passed into the plugin.

Example css:

const myCss = css`
    /* @noflip */
    left: 0;
    top: 0;
`;

In the example above, the comment is stripped out, then left is flipped to right, breaking the UI.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pimmey picture pimmey  路  3Comments

tkh44 picture tkh44  路  3Comments

mitchellhamilton picture mitchellhamilton  路  3Comments

eddeee888 picture eddeee888  路  3Comments

Zn4rK picture Zn4rK  路  3Comments