Files
dotfiles/pi/files.macos/agent/skills/attio-frontend-rules/SKILL.md
T
2026-03-19 16:37:27 +00:00

8.3 KiB

name, description
name description
attio-frontend-rules Styling conventions and component guidelines for the Attio frontend codebase. Covers styled-components patterns, transient props, data attributes, spacing, color tokens, and design system usage. Use when modifying frontend UI code in the Attio monorepo.

Attio Frontend Rules

Guidelines and conventions for working on the Attio frontend codebase. Use whenever modifying the frontend.


Styling

General

  • We use styled-components to style our components within our TS code.
  • We use the $ prefix to indicate props that are consumed in Styled Components. This prevents them from being passed to the DOM as attributes.

Explore available components

Before writing custom components or reaching for custom CSS, check if there are existing components that fit what you're trying to do. Try asking AI agents to find existing components or have a look at available ones in storybook.

Run yarn workspace @attio/design start-storybook to open the storybook in a new tab in your browser.

Re-styling existing components

Most reusable components offer props which affect their styling.

For example, Layout.Stack exposes various props to adjust padding (p, px, py, …), margins (m, mx, …), width, height, flex layout properties, etc.

Components like Button or Typography expose a variant prop to select between multiple colour variants commonly used in the codebase.

Always prefer using these props over creating custom styling. When you need to change the styling of a reusable component just for one particular part of the UI, use styled-components:

import {styled} from "styled-components"

const Container = styled(Layout.Stack)`
    border: 1px solid ${({theme}) => theme.tokens.stroke.primary};
    border-radius: ${({theme}) => theme.borderRadii["12"]};
    background-color: ${({theme}) => theme.tokens.surface.secondary};
    overflow: hidden;
`

When implementing reusable components, add a className prop and assign it to the topmost component in the DOM subtree. This enables re-styling via styled.

export function Stack({..., className}: {..., className: string | undefined}) {
    return (
        <div className={className}>
            ...
        </div>
    )
}

If the same re-styling is applied multiple times, it should become its own reusable component (or component variant).

Layout.Stack defaults

Layout.Stack defaults align to "center" (i.e. align-items: center). Always explicitly set align="flex-start" when you need left/top alignment — don't assume it will be the default.

// Good — explicit alignment
<Layout.Stack direction="column" align="flex-start">
    <Typography.Body.Standard.Component>Title</Typography.Body.Standard.Component>
    <Typography.Caption.Standard.Component>Description</Typography.Caption.Standard.Component>
</Layout.Stack>

// Bad — text will be centered, not left-aligned
<Layout.Stack direction="column">
    <Typography.Body.Standard.Component>Title</Typography.Body.Standard.Component>
    <Typography.Caption.Standard.Component>Description</Typography.Caption.Standard.Component>
</Layout.Stack>

Other useful Layout.Stack props: direction, justify, gap, flex, shrink, minWidth, width, height, and all spacing props (p, px, py, pt, pb, pl, pr, m, mx, my, etc.). Always prefer these props over writing custom styled divs with display: flex.

Avoid layout assumptions

Components should not generally include external layout styles such as width, z-index, margin or flex. These properties should instead be set by the parent component using a styled(MyComponent) override.

Transient props

Use $transient props for anything consumed only inside a component's style. This prevents these props being passed as attributes to the underlying DOM node and generating noisy runtime errors.

// Good
const GoodContainer = styled.div<{$isReady: boolean}>`
   background: ${p => p.$isReady ? "green" : "red"};
`

// Bad
const BadContainer = styled.div<{isReady: boolean}>`
   background: ${p => p.isReady ? "green" : "red"};
`

Data attributes vs transient props

While transient props offer flexibility, consider using data attributes for variant styling. This improves CSS readability with many variants/selectors and offers marginal performance improvements.

Rule of thumb:

  • Transient props — CSS that needs to be interpolated at runtime.
  • Data attributes — styling variants that can be statically defined.
// Good: transient props for runtime interpolation
const Container = styled.div<{$size: Size}>`
    font-size: ${({ $size }) => getFontSize($size)}px;
`

// Good: data attributes for variant definitions
const Container = styled.div`
    &[data-variant="subtle"] {
        background-color: light-blue;
    }

    &[data-variant="outline"] {
        background-color: transparent;
    }
`

// Data attributes can be typed
enum Variants {
    SUBTLE = "subtle",
    OUTLINE = "outline"
}

const Container = styled.div`
    &[data-variant=${Variants.SUBTLE}] {
        background-color: light-blue;
    }

    &[data-variant=${Variants.OUTLINE}] {
        background-color: transparent;
    }
`

High cardinality props

Any prop which changes between many values at runtime should not be passed to a styled component. This avoids the overhead of styled-components generating a new class for each value. Use a traditional inline style prop instead.

interface Props {
   isReady: boolean
   widthPx: number // Varies with a continuous distribution
}

// Good
const GoodContainer = styled.div<{$isReady: boolean}>`
   background: ${p => p.$isReady ? "green" : "red"};
`

export function GoodComponent({isReady, widthPx}: Props) {
   return <GoodContainer $isReady={isReady} style={{width: `${widthPx}px`}} />
}

// Bad
const BadComponent = styled.div<{$isReady: boolean, $widthPx: number}>`
   background: ${p => p.$isReady ? "green" : "red"};
   width: ${p => p.$widthPx}px;
`

Spacing (padding, margins, gaps)

Many reusable components expose props for padding (p, px, py, pr, pl, pt, pb), margin (m, mx, my, …) and gap (gap). Prefer setting spacing through these props. They are limited to a specific subset of values (e.g. "4px", "8px") matching the design system — don't use other values for spacing 99% of the time.

When creating new components or overriding styles, use the Spacing constant from @attio/picasso:

import {styled} from "styled-components"
import {Form, Spacing} from "@attio/picasso"

const StyledFormHelpText = styled(Form.HelpText)`
    ${Spacing.px("16px")}
    ${Spacing.pt("16px")}
    ${Spacing.pb("0px")}
`

Use predefined values for border radii from theme:

export const Link = styled.a`
    border-radius: ${({theme}) => theme.borderRadii[6]};
`

Color tokens

We have fixed tokens for colours. Never define raw hex values or use string names for colours.

Many components expose a variant prop to select predefined colour variants:

<Typography.Caption.Standard.Component variant="secondary">
    Some text...
</Typography.Caption.Standard.Component>

When creating new components or adjusting styling, always use tokens from the theme. This is critical because we support light and dark mode — tokens ensure the correct colour is selected for both.

In styled-components:

export const CheckoutPreviewContainer = styled(Layout.Stack)`
    ${Spacing.p("20px")}
    border-left: 1px solid ${({theme}) => theme.tokens.stroke.primary};
    border-bottom-right-radius: ${({theme}) => theme.borderRadii["16"]};
    background: ${({theme}) => theme.tokens.surface.secondary};
`

In React components, access colours through the useTheme hook. Modify colours using utilities like opacify:

import Color from "color"
import {opacify} from "polished"

const theme = useTheme()
const backdropActiveColor = theme.alphas.bgOverlay
const backdropColor = opacify(1)(backdropActiveColor)
const activeOpacity = Color(backdropActiveColor).alpha()

Turn on scrollbars

Develop with scrollbars set to "Always" in macOS System Settings. This ensures you spot unexpected overflow issues that users with this setting (or Windows users) will see.