CtrlK
BlogDocsLog inGet started
Tessl Logo

likethemammal/emotion-required

Enforce Emotion (@emotion/styled and @emotion/react) as the only styling library; forbid styled-components usage.

95

1.61x
Quality

97%

Does it follow best practices?

Impact

92%

1.61x

Average score across 4 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

good-examples.mdreferences/

Good examples

Canonical Emotion patterns from the codebase.

1. Combined styled + css + polished in a component file

libs/core/src/components/general/Button/Button.component.js:

import styled from '@emotion/styled';
import { css } from '@emotion/react';
import { darken } from 'polished';

const getButtonBackground = (background) => {
  const hover = darken(0.03, background);
  const active = darken(0.05, background);

  return css`
    background: ${background};

    &:hover  { background: ${hover}; }
    &:active { background: ${active}; }
  `;
};

export const BlankButton = styled.button`
  background: none;
  appearance: none;
  color: inherit;
  border: none;
  padding: 0;
  font: inherit;
  outline: inherit;

  &:hover { cursor: pointer; }

  &:disabled {
    opacity: 0.4;
    &:hover { cursor: default; }
  }
`;

export const SimpleButton = styled(BlankButton)`
  border-radius: 4px;
  padding: 0.7rem 1.6rem 0.6rem;
  font-size: 1.5rem;
  text-transform: uppercase;
  font-weight: bold;
  font-family: sans-serif;
  letter-spacing: 0.02rem;
  color: hsl(0, 0%, 20%);

  ${() => getButtonBackground('#1ba2f6')};
`;

Key shapes:

  • Both @emotion/styled and @emotion/react are imported in the same file
  • css template literal returns a fragment that's interpolated into a styled.x via ${() => getButtonBackground(...)}
  • darken from polished does state-color math; results are interpolated into the same css block
  • Reset primitive (BlankButton) is extended via styled(BlankButton) for the skinned variant (SimpleButton) — matches the Blank/Simple primitive-prefix convention

2. Reusable shared CSS chunks with the XxxCSS suffix

libs/apps/site/src/components/Button.styles.js:

import { css } from '@emotion/react';

export const EmptyButtonCSS = css`
  font-family: sans-serif;
  font-size: 1.2rem;
  line-height: 1.2rem;
  outline: 0;
  background: transparent;
  color: white;
  white-space: nowrap;
  border: 0;

  &:hover, &:active, &:focus {
    text-decoration: none;
  }
`;

Consumed by another styles file:

libs/apps/site/src/components/Sections/Sections.styles.js:

import styled from '@emotion/styled';
import { EmptyButtonCSS } from '../Button.styles';

export const ShowMore = styled.button`
  ${EmptyButtonCSS};
  cursor: pointer;
  font-size: 1.4rem;
  line-height: 1.4rem;
  color: inherit;

  &:hover, &:active, &:focus {
    text-decoration: underline;
  }
`;

Key shapes:

  • The shared chunk is a top-level export const named with the CSS suffix
  • Consumed via ${EmptyButtonCSS}; (trailing semicolon convention) inside another styled block
  • The chunk file uses only @emotion/react; the consuming file uses both @emotion/styled and the imported chunk

3. Variant chains via styled(Base)

libs/core/src/components/themes/base/Page.styles.js:

import styled from '@emotion/styled';
import { css } from '@emotion/react';

export const Inner = styled.div`
  position: relative;
  padding: 0 3rem;
  margin: 0 auto;
  max-width: 107.5rem;

  @media (max-width: 515px) {
    padding: 0 2.0rem;
  }
`;

export const WideInner = styled(Inner)`
  max-width: 150rem;
  padding: 0 4rem;
`;

export const BannerInner = styled(Inner)`
  padding: 0 3.8rem;
`;

Key shapes:

  • styled.x for the base, styled(Base) for variants
  • Variant name follows the <Modifier><Base> convention (WideInner extends Inner, BannerInner extends Inner)
  • One large flat catalogue per .styles.js file is normal — don't pre-split

4. Prop-driven styles

libs/apps/site/src/components/CardGrid/CardGrid.styles.js:

import styled from '@emotion/styled';

export const LoadMoreContainer = styled.div`
  justify-content: center;
  padding: 0 0 2rem 0;
  display: ${({ shouldLoadMore }) => shouldLoadMore ? `none` : 'flex'};
`;

Key shapes:

  • Inline arrow function reads props directly via destructure
  • For shared prop-readers used across many styled blocks, hoist to module scope (see the getColor pattern in Chrome.js)

5. Storybook decorator going through the wrapped provider

libs/core/src/config/storybook/decorators/withStyling.js:

import React from 'react';
import StylingProviders from '../../../components/providers/StylingProviders';

export default ({ theme } = {}) => (Story) => {
  return <StylingProviders theme={theme}>
    <Story/>
  </StylingProviders>;
};

Key shape: stories receive Emotion theming via the wrapped StylingProviders, not via Emotion's ThemeProvider directly. This is the canonical way to expose theme to a story.

SKILL.md

tile.json