I'm not sure if this is the right place to ask general questions on using Emotion, so I apologize in advance (if there's a better forum for this, please let me know). First off, I'm fairly new to React, and I'm very new to CSS-in-JS, so I'm still trying to wrap my head around the concepts. I've gone over the documentation, but I still have questions regarding usage. I have a bunch of CSS from an old Ember project that I want to port to ReactJS, so that's going to be my starting point in terms of what I want to accomplish.
In my old project, I had a bunch of base classes that are meant to be used anywhere. For example:
.link, .link:active, .link.visited {
color: blue;
cursor: pointer;
}
.link:hover {
color: purple;
}
I should be able to drop this into anywhere -- a <div>, a <button>, <MyFancyBox> -- and on a case by case basis (eg, I'm outputting a list with each <li> except the third one having .link). What's the paradigm in Emotion to do this?
One of the examples from styled-components was that of <Button> and being able to compose <Button primary> and <Button secondary>. The way I understand it, the way you would implement this is:
const Button = styled.button`
color: ${props => props.primary ? 'red' : props.secondary ? 'blue' : 'green';
background: ${props => primary ? 'white' : props.secondary ? 'gray' : 'white;
`
This seems cumbersome. If both primary and secondary are modifying, say, 8-10 properties, then our CSS will be filled with${props => props.primary ? ...} over and over again. And let's say we add another prop on top of that. Let's say we have an active prop that modifies most of the same parameters, BUT only in the case of secondary and not primary. In plain CSS, this would be
.button.primary {
/* stuff */
}
.button.secondary {
/* stuff */
}
.button.secondary.active {
/* stuff */
}
So in Emotion, how do I break this down into something readable and maintainable?
I'm a little unclear on where you can use theming. In your docs, you have this example:
const H1 = withTheme(styled(Heading)`
color: ${p => p.theme.purple};
`)
But can theming be used with the css` ` syntax? Why can't I do something like this?
const HBase = css`
color: ${props => props.theme.red};
}
const H1 = withTheme(styled(Heading))`
composes: ${HBase};
`
What I end up seeing in the Dev Console is something like this:
color: function (p) { return p.theme.purple; };
So I'm not exactly sure how to use props within a css` ` tag.
Thanks for any answers!
I want to answer this properly, but I'm going to be offline for the next few days. Hopefully, someone else can step in and help answer some of these questions before I get back. 馃槃
I kinda solved #2 above by passing in a series of objects to styled, so something like
const Button = styled.button({
backgroundColor: 'white',
color: 'green',
fontSize: '16px',
padding: '10px 20px'
}, (props) => {
let css = {};
if (props.primary) {
css.backgroundColor = 'white';
css.color = 'red'
}
}
if (props.secondary) {
css.backgroundColor = 'gray';
css.color = 'blue';
if (props.active) {
css.color = 'lightblue';
}
}
}
return css;
});
I don't know if this is the ideal solution. The main downside is the use of camelCase notation instead of actual CSS notation, and the fact that it doesn't feel as clean as a pure CSS file. But it's manageable.
You're looking for css.
Let's say we have the following program:
import { css, sheet } from "emotion";
const linkClass = css`
&, &:active, &.visited {
color: blue;
cursor: pointer;
}
&:hover {
color: purple;
}
`;
console.log(linkClass);
console.log(sheet.sheet.cssRules);
With a .babelrc of
{
"presets": [
["env", {
"targets": {
"node": "current"
}
}]
],
"plugins": [
"emotion/babel"
]
}
that compiles to:
// compiled.js
"use strict";
var _emotion = require("emotion");
const linkClass = /*#__PURE__*/(0, _emotion.css)([], [], function createEmotionStyledRules() {
return [{
"&, &:active, &.visited": {
"color": "blue",
"cursor": "pointer"
},
"&:hover": {
"color": "purple"
}
}];
});
console.log(linkClass);
console.log(_emotion.sheet.sheet.cssRules);
The output of compiled.js when run is
[ { cssText: '.css-10pfecp, .css-10pfecp:active, .css-10pfecp.visited{color:blue;cursor:pointer;}' },
{ cssText: '.css-10pfecp:hover{color:purple;}' } ]
There are two things to notice here. One is that linkClass is the className string, so you can put it anywhere you can use a normal classname. The other is that the rules that get inserted into the DOM.
.css-10pfecp, .css-10pfecp:active, .css-10pfecp.visited{
color:blue;
cursor:pointer;
}
I'll use an example that's probably a bit more complex than what you need. Assuming you have a Button which is a base level component (that is, configurable based on props) as such:
import { h } from "preact";
import styled from "preact-emotion";
export default styled.button`
cursor: pointer;
display: inline-block;
min-height: 1em;
outline: none;
border: none;
vertical-align: ${({ verticalAlign }) => verticalAlign};
background: ${({ background }) => background};
color: ${({ textColor }) => textColor};
font-family: ${({ fontFamily }) => fontFamily};
...
`
You can create then the variant -> object of configurable props mapping as such:
import { h, Component } from "preact";
import Button from "@ocode/button";
import { buttons, sizes } from "@ocode/constants/lib/buttons";
export default class Buttons extends Component {
render({ size, variant, children, ...props }) {
const b = buttons[variant] || buttons.primary;
const s = sizes[size] || sizes.default;
return (
<Button {...props} {...b} {...s}>
{children}
</Button>
);
}
);
Alternatively, a simpler approach would be to keep everything in the same file and use a props to construct composes. You can imagine that this function can contain any logic you want it to including fallbacks to defaults etc.
const primary = css`
background-color: blue;
font-size: 20px;
`
const secondary = css`
background-color: red;
font-size: 50px;
`
export default styled.button`
composes: ${({primary}) => primary ? secondary : blue};
border-radius: 3px;
`
But can theming be used with the css
syntax? Why can't I do something like this?
css returns a class name and doesn't know if it's being used inside React/Preact/Angular/raw JS/etc. For example, earlier in this post, I used it inside of some random node code.
Perhaps there's a way to treat css`` as something that returns a function and does fancy detection etc but I don't currently need or use themable utility classes so I'll let someone else speak to that. I generally use utility components which abstract the need for dealing with classes.
@ChristopherBiscardi Thanks for the detailed response.
Thanks, that was the solution I was hoping for. I tested out your example, and it worked exactly as I expected.
Your second example is pretty easy to understand, and i'll probably end up doing something really similar. But I do have a question about composes. I notice it gets applied first. So shouldn't what goes in composes be the base style, and what's in the main styled.button be for overriding that base class with whatever props (primary, secondary)? In your example, if there is no primary or secondary and I still want to set a background-color, where would I put it?
As for your first example, I'm a little confused what this is doing:
<Button {...props} {...b} {...s}>
Are you expecting b and s to be arrays? Objects? Classnames?
I ended up forgoing theming and just creating my own constants file with all the theming I needed (mainly colors) and importing that where ever I needed (eg. import {colors} from 'colors';). It seemed much cleaner and easier that way.
Ah yeah, so this line in that example:
import { buttons, sizes } from "@ocode/constants/lib/buttons";
imports objects shaped like this:
import { lighten, darken } from "polished";
import colors from "./colors";
export const buttons = {
primary: {
color: "white",
backgroundColor: colors.brand,
borderColor: colors.brand,
disabledColor: lighten(0.2, colors.brand),
hoverBackgroundColor: darken(0.2, colors.brand),
hoverBackgroundBordercolor: darken(0.2, colors.brand)
},
digital: {
borderColor: colors.digitalBlue,
backgroundColor: colors.digitalBlue,
color: "#fff",
disabledColor: lighten(0.2, colors.digitalBlue),
hoverBackgroundColor: darken(0.2, colors.digitalBlue),
hoverBackgroundBordercolor: darken(0.2, colors.digitalBlue)
},
...
};
export const sizes = {
xsmall: {
paddingX: ".5rem",
paddingY: ".2rem",
fontSize: ".75rem",
lineHeight: "1.5"
},
...
};
Where the pattern is basically:
const thing = {
variant: cssObj
}
so when you spread {...b} you're spreading a cssObj, which is just the configurable props from the base Button that are relevant for primary or large or whatever.
So shouldn't what goes in composes be the base style...
I avoid this kind of inheritance patterning generally but you could do it that way.
In your example, if there is no primary or secondary and I still want to set a background-color, where would I put it?
If there's no primary or secondary I usually default to primary for example. You could also just leave composes blank for those values and expect that they'll be passed in piecemeal to each prop. You could even put logic there to prevent them from being set if there is a primary, etc.
It's up to what you want to accomplish really, there are a few options.
I ended up forgoing theming and just creating my own constants file with all the theming I needed (mainly colors) and importing that where ever I needed (eg. import {colors} from 'colors';). It seemed much cleaner and easier that way.
Yeah, that's a valid option if you'll only have a single theme and the values are static, etc. I'm doing something similar but using styled-theming, etc to set the JSON in the context at the root of my app so I can swap themes for different users, etc.
@ChristopherBiscardi Thank you. I think I have a much better understanding of how everything comes together now.
I do have one last question: how do you handle a component whose styling may depend on the CSS of a parent DIV or component? For example, I have a <Button>, but if it's with a <ButtonGroup>, such as:
<ButtonGroup>
<Button>A</Button>
<Button>B</Button>
</ButtonGroup>
there needs to be some adjustment to the <Button> CSS (for example, padding). Would the pattern be to add the styling of <Button> within <ButtonGroup>, such as:
const ButtonGroup = styled.div`
/* button group styling */
& button {
/* button styling */
}
`
Or is there another way to do this?
This gets more into React/Preact/etc code than it has to do with emotion. The concept is that you want your button to be able to handle applying its own styles based on props for any context you intend to render it in. In the following example, I use the btnGrouper function to apply different paddings based on a prop that is passed in from ButtonGroup. Since ButtonGroup knows the relevant information about its children, we can apply the required props to <Button> in render. (please excuse the naming, etc I rushed-wrote this example on the emotion site 馃槄 )
function btnGrouper({ firstInGroup, lastInGroup }) {
if (firstInGroup) {
return "0 30px";
}
if (lastInGroup) {
return "30px 0";
}
return "30px";
}
const Button = styled("button")`padding: ${btnGrouper};`;
class ButtonGroup extends React.Component {
render() {
const count = React.Children.count(this.props.children);
return (
<span>
{React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
firstInGroup: i === 0,
lastInGroup: i === count - 1
});
})}
</span>
);
}
}
render(
<ButtonGroup>
<Button>A</Button>
<Button>B</Button>
<Button>c</Button>
</ButtonGroup>,
mountNode
);
And here's a screenshot of the result:

If you do it this way, the Button owns it's own styles rather than making ButtonGroup override Button styles. This makes it easier to maintain and modify because you're locating all Button related styles in Button and ButtonGroup is only responsible for grouping logic.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.
Most helpful comment
1. Base Classes
You're looking for css.
Let's say we have the following program:
With a
.babelrcofthat compiles to:
The output of
compiled.jswhen run isThere are two things to notice here. One is that
linkClassis the className string, so you can put it anywhere you can use a normal classname. The other is that the rules that get inserted into the DOM.2. Adapting based on props.
I'll use an example that's probably a bit more complex than what you need. Assuming you have a
Buttonwhich is a base level component (that is, configurable based on props) as such:You can create then the
variant-> object of configurable props mapping as such:Alternatively, a simpler approach would be to keep everything in the same file and use a props to construct
composes. You can imagine that this function can contain any logic you want it to including fallbacks to defaults etc.3. Using theming
cssreturns a class name and doesn't know if it's being used inside React/Preact/Angular/raw JS/etc. For example, earlier in this post, I used it inside of some random node code.Perhaps there's a way to treat css`` as something that returns a function and does fancy detection etc but I don't currently need or use themable utility classes so I'll let someone else speak to that. I generally use utility components which abstract the need for dealing with classes.