Based on your design system (SCSS) and applied to the current stack with styled-components. It keeps the philosophy and ordering, with equivalent utilities in TypeScript.
- Keep it simple and reuse as much as possible.
- Code that looks like it was written by one person.
- Write for scalability.
- Methodology: BEM for class names when you use
classNameor global styles. - Per-component style files:
ComponentName.styled.ts(plural not always applicable for components; for groups, e.g.,Buttons.styled.ts). - Classes in singular and lowercase when used (e.g.,
.gallery__button). - Name images relative to their block (e.g.,
hero_background.png).
Even though styled-components generates classes, we can keep BEM semantics in the component hierarchy and in auxiliary classNames:
import styled from 'styled-components';
// Block
export const Button = styled.button`
/* properties */
`;
// Element
export const ButtonIcon = styled.span`
/* properties */
`;
// Modifier (via prop)
export const ButtonPrimary = styled(Button)`
/* variant properties */
`;If you need explicit BEM classes (for testing/analytics), use src/utils/bem.ts:
import { bem, be, bemMods, classes } from '@/utils/bem';
const block = 'button';
const iconEl = be(block, 'icon'); // 'button__icon'
const mods = bemMods(block, null, { primary: true, size: 'lg' }); // ['button--primary', 'button--size-lg']- Space after the selector and before
{}(styled-components encourages this). - Spaces for indentation.
- Space after
:in declarations. - Visually separate CSS blocks (blank line between logical groups).
- Avoid deep nesting (max one level).
- Mixins for size, text styles, and numeric font sizes.
- Box model: display, width/height, margin/padding, border, box-sizing
- Positioning: position, top/right/bottom/left, z-index
- Typography: font, font-weight, font-size, line-height, text-transform, text-decoration, letter-spacing
- Decoration: color, background, shadows, filters, opacity, transitions
- Variables: use of
var(--...) - Mixins: styled-components helpers
Available:
fontSize(px)→ font size and relativeline-height.size(width, height?)→ setswidthandheight(omit height to mirror width).textStyle({ transform, decoration, weight })→ typography shortcuts.fontWeight(key | number)→ mapslight|normal|medium|bolder|blackor accepts a number.getOpacity(color, amount)→color-mixwith transparency.getContrastColor(bg, opts?)andcontrastText(bg)→ readable text color against a background.
Example:
import styled from 'styled-components';
import { fontSize, size, textStyle, spacing, colors, contrastText } from '@/ui/styles/scssTokens';
export const Button = styled.button`
/* Box model */
display: inline-flex;
${size(220, 40)};
padding: ${spacing.space_half} ${spacing.space};
border: 0;
border-radius: 8px;
/* Positioning */
position: relative;
/* Typography */
${textStyle({ transform: 'uppercase', weight: 'medium' })};
${fontSize(13)};
/* Decoration */
background: ${colors.dark};
${contrastText(colors.dark)};
transition: background-color 0.2s ease;
/* Variables / Additional mixins */
`;- Use CSS variables from
GlobalStyles(base.ts) and themes fromtheme.ts. - Prefer
var(--color-...)for colors andspacingfrom the token store.
import styled from 'styled-components';
import { be } from '@/utils/bem';
export const Card = styled.article.attrs({ className: 'card' })`
display: block;
padding: var(--space);
background: var(--color-bg-light);
`;
export const CardTitle = styled.h3.attrs({ className: be('card', 'title') })`
margin: 0 0 var(--space-half) 0;
font-family: var(--font-heading);
`;- Use
getContrastColorto ensure sufficient (WCAG) contrast between background and text. - Keep adequate touch targets and a clear visual hierarchy.
This guide keeps the intention of the original SCSS system but adapts it to a styled-components and TypeScript tokens workflow.