Using Styled Components
To apply styles to HTML, we use a CSS-in-JS library called Emotion. This allows us to define styles in the same file where the component is defined and used. Styles are scoped to avoid global selectors and having to manually specify classnames as strings. You can also leverage the theme
object to reference common values and utilities.
The best styles are the ones you don’t write - whenever possible use existing components.
Basics
To use Style Components pull in styled
and apply your CSS. For consistent spacing you should also use space()
:
import styled from '@emotion/styled';
import {space} from 'sentry/styles/space';
const Numeric = styled('span')`
font-variant-numeric: tabular-nums;
`;
const SideBySide = styled('div')`
display: flex;
gap: ${space(2)};
flex-wrap: wrap;
align-items: flex-start;
`;
export default ExampleComponent() {
return (
<SideBySide>
<p>a number: <Numeric>1</Numeric></p>
<p>and another: <Numeric>2</Numeric></p>
</SideBySide>
)
}
Theme
Take constants (z-indexes, paddings, colors) from props.theme
import styled from '@emotion/styled';
import {space} from 'sentry/styles/space';
const SomeComponent = styled('div')`
border-radius: 1.45em;
font-weight: bold;
z-index: ${p => p.theme.zIndex.modal};
padding: ${space(1)} ${space(2)};
border: 1px solid ${p => p.theme.borderLight};
color: ${p => p.theme.purple};
box-shadow: ${p => p.theme.dropShadowHeavy};
`;
Dynamic Properties
You can pass in custom props to your custom component and use that to set styles. This does create overhead at runtime and should be avoided. What follows is an example of how to pass custom props, and how to avoid doing so.
Avoid Dynamic Props
import styled from '@emotion/styled';
// ❌ Try to avoid this
const Label = styled('label')<{isDisabled: boolean; isError: boolean}>`
color: ${p => p.isDisabled
? p.theme.gray200
: p.isError
? p.theme.error400
: inherit
};
`;
return <Label disabled={false} isError={true}>There is an error</Label>;
Choose one of the strategies below instead.
Use Data Selectors
Data selectors can be an easy way to set styles based on simple/pre-defined state. When defining these attributes, don't forget that browsers have built-in selectors like :disabled
, :focus
, :first-child
and so on.
import styled from '@emotion/styled';
// ✅ Use a data attribute to pass in your state, and leverage CSS selectors:
const Label = styled('label')`
color: inherit;
&[data-is-error="true"] {
color: ${p => p.theme.error400};
}
&:disabled {
color: ${p => p.theme.gray200};
}
`;
return <Label disabled={false} data-is-error={true}>There is an error</Label>;
Use Style & CSS Attributes
The style
and css
attributes can be used, but the values of style
are not linted. Avoid setting too many values at a time for readability.
import styled from '@emotion/styled';
import {css} from '@emotion/react';
// ✅ Don't be afraid of inline styles for one-off values
const Grid = styled('div')`
display: grid;
grid-template-areas:
'logo search'
'sidebar main';
gap: var(--grid-gap, 0px);
@media (max-width: ${p => p.theme.breakpoints.small}) {
grid-template:
'logo' auto
'search' auto
'main' auto / 1fr;
/* Hide sidebar, which is in an auto-column */
grid-auto-columns: 0px;
}
`;
return (
<Grid css={css`--grid-gap: ${space(1)};`}>
<HomeLink style={{gridArea: 'logo'}} />
<SearchForm style={{gridArea: 'search'}} />
<Navigation style={{gridArea: 'sidebar'}} />
<DataTable style={{gridArea: 'main'}} />
</Grid>
);
Imperatively Set Styles
For values that change frequently it's beneficial to use a component ref and set the values imperatively. This can be done without triggering react which can result in much more efficient updates.
Note that you can set CSS values directly, or if the component is reading a CSS variable, call elem.style.setProperty('--foo', 'value')
instead.
import styled from '@emotion/styled';
import {useRef} from 'react';
const Area = styled('div')`
width: 500px;
height: 500px;
position: relative;
`;
const MovingCircle = styled('div')`
position: absolute;
width: 50px;
height: 50px;
background: black;
border-radius: 50%;
`;
const ref = useRef<HTMLDivElement>(null);
return (
<Area
onMouseMove={event => {
if (!ref.current) {
return;
}
// ✅ Imperatively set values that change frequently to avoid react re-renders
ref.current.style.top = event.clientY + 'px';
ref.current.style.left = event.clientX + 'px';
}}
>
<MovingCircle ref={ref} />
</Area>
);
stylelint
Errors
"No duplicate selectors"
This happens when you use a styled component as a selector, we need to tell stylelint that what we are interpolating is a selector by using comments to assist the linter. e.g.
const ButtonBar = styled("div")`
${/* sc-selector */Button) {
border-radius: 0;
}
`;
See https://styled-components.com/docs/tooling#interpolation-tagging for other tags and more information.