Skip to content

.compiled.css files are not sorted in dev mode, causing CSS ordering bugs with extracted packages #1895

Description

@lewishealey

The Vite plugin (@compiled/vite-plugin) does not sort .compiled.css files during development. In production builds, generateBundle applies sort() from @compiled/css to CSS assets, ensuring correct rule ordering (base before @media, shorthand before longhand, LVFHA pseudo ordering). However in dev mode, Vite injects each .compiled.css import as a <style> tag in module-graph traversal order — and that order is not guaranteed to keep base rules before their @media overrides, or shorthand properties before their longhand counterparts.
This causes a family of CSS ordering bugs when consuming packages that use CSS extraction (e.g. @atlaskit/*):

  1. Media query ordering: A base display:none atomic class wins over @media (min-width:48rem){ display:grid } because the base rule is inserted later in the merged stylesheet. This breaks responsive layouts — e.g. navigation side-nav grid-area, search bar visibility, and top-nav grid-template-columns all break.

  2. Font shorthand vs longhand: The font shorthand (e.g. font: var(--ds-font-body, normal 400 14px/20px ...)) embeds font-weight: 400 and is inserted after an explicit font-weight: var(--ds-font-weight-bold, 700) longhand class, causing bold text to render as regular weight.

  3. Border shorthand reset: A border-width: 0 reset class from an Atlaskit component is inserted after explicit border-width: var(--ds-border-width, 1px) declarations, causing borders to disappear.
    To Reproduce

  4. Create a Vite project that uses @compiled/vite-plugin

  5. Import a package that ships pre-compiled .compiled.css files with both base rules and @media query rules (e.g. @atlaskit/navigation-system or @atlaskit/primitives)

  6. Run the Vite dev server (vite dev)

  7. Observe that responsive styles are broken — e.g. elements that should be display:grid at desktop widths remain display:none, or bold text renders as normal weight
    The issue does NOT reproduce in production builds because generateBundle applies sort() to CSS assets.
    Expected behavior
    .compiled.css files should be sorted in dev mode the same way they are in production builds. Base rules should always appear before @media/@supports at-rules, and shorthand properties should appear before their longhands, so that CSS specificity and source-order semantics work correctly regardless of module-graph traversal order.
    Desktop (please complete the following information):

• OS: macOS
• Browser: Chrome, Safari (all browsers — the issue is in CSS source ordering, not browser-specific)
• Version: @compiled/vite-plugin v1.1.4, @compiled/react v0.21.0
Additional context
The fix is straightforward: add .compiled.css handling at the top of the transform hook, before the JS/TS file filter. The existing sort() function from @compiled/css is already imported and used in generateBundle — it just needs to also be applied to .compiled.css files during transform:

async transform(code: string, id: string): Promise {
// Sort .compiled.css files to ensure correct rule ordering.
if (id.endsWith('.compiled.css') && code.includes('._')) {
try {
const sortedCss = sort(code, {
sortAtRulesEnabled: options.sortAtRules,
sortShorthandEnabled: options.sortShorthand,
});
return { code: sortedCss, map: null };
} catch {
// Fall through — serve unsorted CSS rather than breaking
}
}

// ... existing JS/TS transform logic
}
The full fix with tests is available at /tmp/compiled-upstream on branch fix/sort-compiled-css-in-dev-mode (2 files changed, +106 lines, all 22 tests passing).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions