UI Styling Standard

The eslint plugin to enforce and educate on Atlassian's UI Styling Standard

Patterns

All styles should be written using Compiled, (opens new window) or Primitives and conform to the following patterns.

Static styles

For styles that are not affected by props or state, use the css() or xcss() API.

Compiled

When using Compiled, write static styles with css() and apply them with the css prop.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';

const cardStyles = css({
    backgroundColor: token('elevation.surface.raised'),
    boxShadow: token('elevation.shadow.raised'),
    padding: token('space.100'),
});

const Card = () => <div css={cardStyles} />;

Primitives

When using Primitives, write static styles with xcss() and apply them with the xcss prop.

import { Box, xcss } from '@atlaskit/primitives';

const cardStyles = xcss({
    backgroundColor: 'elevation.surface.raised',
    boxShadow: 'elevation.shadow.raised',
    padding: 'space.100',
});

const Card = () => <Box xcss={cardStyles} />;

Toggled styles

For styles that are toggled on and off, write them as static styles and then conditionally apply them.

Compiled

When using Compiled, conditionally apply styles with the css prop.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';

const baseStyles = css({
    backgroundColor: token('color.background.neutral'),
    border: `1px solid ${token('color.border')}`,
});

const selectedStyles = css({
    backgroundColor: token('color.background.selected'),
    borderColor: token('color.border.selected'),
});

const Selectable = ({ isSelected = false }: { isSelected?: boolean }) => (
    // The `selectedStyles` are conditionally applied
    <div css={[baseStyles, isSelected && selectedStyles]} />
);

Primitives

When using Primitives, conditionally apply styles with the xcss prop.

import { Box, xcss } from '@atlaskit/primitives';

const baseStyles = xcss({
    backgroundColor: 'color.background.neutral',
    borderWidth: 'border.width',
    borderStyle: 'solid',
    borderColor: 'color.border',
});

const selectedStyles = xcss({
    backgroundColor: 'color.background.selected',
    borderColor: 'color.border.selected',
});

const Selectable = ({ isSelected = false }: { isSelected?: boolean }) => (
    // The `selectedStyles` are conditionally applied
    <Box xcss={[baseStyles, isSelected && selectedStyles]} />
);

Variant styles

For styles that compose a set of known variants, use cssMap or an object of xcss() styles and then dynamically access them.

Compiled

When using Compiled, write variants with cssMap and apply them with the css prop.

import { css, cssMap } from '@compiled/react';
import { token } from '@atlaskit/tokens';

type Status = 'success' | 'warning' | 'danger';

const baseStyles = css({
    font: token('font.body.large'),
    padding: token('space.100'),
});

const statusStyles = cssMap({
    success: {
        color: token('color.text.success'),
        backgroundColor: token('color.background.success'),
    },
    warning: {
        color: token('color.text.warning'),
        backgroundColor: token('color.background.warning'),
    },
    danger: {
        color: token('color.text.danger'),
        backgroundColor: token('color.background.danger'),
    },
});

const StatusMessage = ({ message, status }: { message: string; status: Status }) => (
    // The `statusStyles` are dynamically accessed
    <div css={[baseStyles, statusStyles[status]]} />
);

Primitives

When using Primitives, write variants using an object with xcss() values and apply them with the xcss prop.

import { Box, xcss } from '@atlaskit/primitives';

type Status = 'success' | 'warning' | 'danger';

const baseStyles = xcss({
    font: 'font.body.large',
    padding: 'space.100',
});

const statusStyles = {
    success: xcss({
        color: 'color.text.success',
        backgroundColor: 'color.background.success',
    }),
    warning: xcss({
        color: 'color.text.warning',
        backgroundColor: 'color.background.warning',
    }),
    danger: xcss({
        color: 'color.text.danger',
        backgroundColor: 'color.background.danger',
    }),
};

const StatusMessage = ({ message, status }: { message: string; status: Status }) => (
    // The `statusStyles` are dynamically accessed
    <Box xcss={[baseStyles, statusStyles[status]]} />
);

Dynamic styles

Use with caution

Only a tiny percentage (~1%) of styles need to be written this way. If you are using it frequently, you are likely doing something wrong!

For styles that cannot be expressed statically, use the style prop.

This might be needed when:

  • Styles are derived from user input
  • Integrating with a third party library that provides a style object

Compiled

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';

const baseStyles = css({
    height: '100%',
    minWidth: '200px',
    backgroundColor: token('color.background.neutral'),
});

const Resizable = ({ width }: { width: string }) => <div css={baseStyles} style={{ width }} />;

Primitives

import { Box, xcss } from '@atlaskit/primitives';

const baseStyles = xcss({
    height: '100%',
    minWidth: '200px',
    backgroundColor: 'color.background.neutral',
});

const Resizable = ({ width }: { width: string }) => <Box css={baseStyles} style={{ width }} />;

More examples

Feature flagging styles

Dynamic styles are forbidden by no-dynamic-styles so cannot be used to implement feature flagged styles.

There are two supported approaches for feature flagging styles:

  1. Toggled styles to dynamically apply feature flagged styles.
  2. Multiple components which are dynamically switched between.

Toggled styles

Prefer toggled styles for feature flagging as it requires less code duplication.

Adding styles

Use a logical expression, (opens new window) to add new styles behind a feature flag.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';
import { ff } from '@atlassian/feature-flags';

const linkStyles = css({ color: text('color.link') });

const underlineStyles = css({ textDecoration: 'underline' });

export const Link = ({ children, href }: { children: string; href: string }) => (
    <a href={href} css={[linkStyles, ff('my-feature-flag') && underlineStyles]}>
        {children}
    </a>
);
Replacing styles

Use a conditional (ternary) expression, (opens new window) to replace styles behind a feature flag.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';
import { ff } from '@atlassian/feature-flags';

const originalStyles = css({ color: text('color.text.accent.red') });

const featureFlaggedStyles = css({ color: text('color.text.danger') });

export const ErrorMessage = ({ children }: { children: string }) => (
    <span css={ff('my-feature-flag') ? featureFlaggedStyles : originalStyles}>{children}</span>
);

Multiple components

Use multiple components for feature flagging if toggled styles are not suitable.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';
import { ff } from '@atlassian/feature-flags';

const linkStyles = css({ color: text('color.link') });

const underlineStyles = css({ textDecoration: 'underline' });

const LinkWithoutUnderline = ({ children, href }: { children: string; href: string }) => (
    <a href={href} css={linkStyles}>
        {children}
    </a>
);

const LinkWithUnderline = ({
    children,
    href,
    hasUnderline = true,
}: {
    children: string;
    href: string;
    hasUnderline?: boolean;
}) => (
    <a href={href} css={[linkStyles, ff('my-feature-flag') && hasUnderline && underlineStyles]}>
        {children}
    </a>
);

export const Link = ff('my-feature-flag') ? LinkWithUnderline : LinkWithoutUnderline;

Migrating from Emotion or styled-components

Using @emotion/* or styled-components for styling is forbidden by use-compiled.

Migrating from styled

The styled API is forbidden by no-styled.

Use css() or xcss() to declare styles instead.

Before

import { styled } from '@compiled/react';

type Status = 'success' | 'warning' | 'danger';

type Props = {
    width: string;
    status: Status;
    isSelected: boolean;
};

const textColors: Record<Status, string> = {
    success: token('color.text.success'),
    warning: token('color.text.warning'),
    danger: token('color.text.danger'),
};

const Component = styled.div<Props>(
    {
        padding: token('space.100'),
        width: (props) => props.width,
    },
    ({ status }: { status: Status }) => ({
        color: textColors[status],
    }),
    ({ isSelected }) => (isSelected ? { border: `1px solid ${token('color.border.selected')}` } : {}),
);

After

We apply a combination of the patterns above:

import type { ReactNode } from 'react';
import { css, cssMap } from '@compiled/react';

type Status = 'success' | 'warning' | 'danger';

type Props = {
    width: string;
    status: Status;
    isSelected: boolean;
    // Props that were implicitly added now need to be made explicit
    children: ReactNode;
};

const baseStyles = css({
    padding: token('space.100'),
});

const selectedStyles = css({
    border: `1px solid ${token('color.border.selected')}`,
});

const statusStyles = cssMap({
    success: {
        color: token('color.text.success'),
    },
    warning: {
        color: token('color.text.warning'),
    },
    danger: {
        color: token('color.text.danger'),
    },
});

const Component = ({ width, status, isSelected, children }: Props) => (
    <div css={[baseStyles, isSelected && selectedStyles, statusStyles[status]]} style={{ width }}>
        {children}
    </div>
);

Migrating nested selectors

Nested selectors are forbidden by no-nested-selectors.

Apply styles directly to elements instead.

Third party libraries

If a third party library does not let you control rendering then investigate possible workarounds. For example, if you're using a React abstraction then consider using the lower level JS-only library instead.

If you've exhausted all avenues you can suppress the lint rule and move on, but this is considered tech debt.

Atlassian platform styles

Never use nested selectors to override Atlassian platform styles (including Design System components). Overriding platform styles is unsafe and will break. This blocks shipping changes to all customers, and is a pain point for everyone.

Before

The example below uses nested selectors to style child elements that it does not render.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';

const tableStyles = css({
    tbody: {
        border: 'none',
    },
    tr: {
        border: 'none',
        borderBlockEnd: `1px solid ${token('color.border')}`,
        '&:hover': {
            backgroundColor: token('color.background.neutral.subtle.hovered'),
        },
    },
    td: {
        verticalAlign: 'middle',
    },
});

export const Table = ({ children }: { children: ReactNode }) => (
    <table css={tableStyles}>{children}</table>
);

After

Instead we should apply styles directly to elements, even if it requires API changes.

import { css } from '@compiled/react';
import { token } from '@atlaskit/tokens';

const tableBodyStyles = css({
    border: 'none',
});

export const TableBody = ({ children }: { children: ReactNode }) => (
    <tbody css={tableBodyStyles}>{children}</tbody>
);

const tableRowStyles = css({
    border: 'none',
    borderBlockEnd: `1px solid ${token('color.border')}`,
    '&:hover': {
        backgroundColor: token('color.background.neutral.subtle.hovered'),
    },
});

export const TableRow = ({ children }: { children: ReactNode }) => (
    <tr css={tableRowStyles}>{children}</tr>
);

const tableCellStyles = css({
    verticalAlign: 'middle',
});

export const TableCell = ({ children }: { children: ReactNode }) => (
    <td css={tableCellStyles}>{children}</td>
);

Migrating spread props

Explicitly define and apply all props used by a component.

Before

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

type IconProps = {
    color: string;
};

const Icon = ({ color, ...props }: IconProps) => {
    return <svg fill={color} {...props} />;
};

After

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

type IconProps = {
    color: string;
    'aria-label'?: string;
};

const Icon = ({ color, 'aria-label': ariaLabel }: IconProps) => {
    return <svg fill={color} aria-label={ariaLabel} />;
};

Sharing common styles

Share reusable components instead of exporting styles.

Before

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

export const ellipsisStyles = css({
    overflow: 'hidden',
    whiteSpace: 'nowrap',
    textOverflow: 'ellipsis',
});

After

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

const ellipsisStyles = css({
    overflow: 'hidden',
    whiteSpace: 'nowrap',
    textOverflow: 'ellipsis',
});

export const ClampedText = ({ children }: { children: string }) => (
    <span css={ellipsisStyles}>{children}</span>
);
Was this page helpful?
We use this feedback to improve our documentation.
© 2026 AtlassianTrademark, (opens new window)Privacy, (opens new window)License