Javascript is required
How it worksPublished 2026-07-28Updated 2026-08-088 min read

CSS Property Reordering: PostCSS and Fallback Risks

Using PostCSS to parse CSS AST (Abstract Syntax Tree) enables automatic property reordering under rules like Concentric or Recess, improving team formatting consistency. However, physical declaration order inside a CSS rule often represents browser fallbacks and vendor prefix precedence. This article explains PostCSS AST reordering safety.

By: Tiny
PostCSSASTCSS FormatterProperty Sorting

1. Problem: declaration order destruction during AST reordering

PostCSS parses CSS source code into Root, Rule, and Declaration nodes. Reordering a Declaration array alphabetically or by naive dictionary lookup can corrupt browser fallback declarations and vendor prefix override sequences.

2. Minimal reproduction: fallback and prefix order corrupted by reordering

The CSS below uses duplicate property declarations to provide browser fallbacks. If property reordering removes duplicate declarations or swaps their order, fallback mechanisms in older browsers fail.

/* Source: display: block serves as fallback for non-flex browsers */
.box {
  display: block;
  display: flex;
  -webkit-transform: scale(1);
  transform: scale(1);
}

/* Incorrect reorder: duplicate display declaration deduplicated, losing fallback */
.box {
  display: flex;
  transform: scale(1);
}

3. Root cause: declaration cascade inside rules

Inside a CSS rule, when duplicate declarations (e.g., multiple display or color properties) or vendor prefixes exist, browsers process them sequentially, ignoring unsupported values and retaining the last valid declaration. Swapping order invalidates fallback logic.

4. Recommendation: AST sorting with fallback and vendor prefix preservation

1. Strictly forbid deduplication or order swapping for duplicate property declarations. 2. Vendor prefixes (e.g., -webkit-) must precede standard unprefixed properties so standard properties take priority. 3. Group CSS variables (--*) at the top while maintaining relative order.

5. Complete code: AST declaration sorting with fallback protection

This TypeScript code demonstrates PostCSS AST Declaration sorting, preserving relative positions for duplicate fallbacks and vendor prefixes.

interface CSSDeclaration {
  prop: string;
  value: string;
}

const PROPERTY_GROUPS = [
  ["position", "top", "right", "bottom", "left", "z-index"],
  ["display", "flex", "grid", "width", "height", "margin", "padding"],
  ["color", "background", "font-size", "line-height"],
];

function getPropertyOrder(prop: string): number {
  const cleanProp = prop.replace(/^-(webkit|moz|ms|o)-/, "");
  for (let groupIndex = 0; groupIndex < PROPERTY_GROUPS.length; groupIndex++) {
    const itemIndex = PROPERTY_GROUPS[groupIndex].indexOf(cleanProp);
    if (itemIndex !== -1) {
      return groupIndex * 100 + itemIndex;
    }
  }
  return 9999;
}

function safeReorderDeclarations(decls: CSSDeclaration[]): CSSDeclaration[] {
  const indexed = decls.map((d, index) => ({ decl: d, originalIndex: index }));

  indexed.sort((a, b) => {
    const propA = a.decl.prop;
    const propB = b.decl.prop;

    // 1. Same property: strictly preserve original relative order for fallbacks
    if (propA === propB) {
      return a.originalIndex - b.originalIndex;
    }

    // 2. Vendor prefix vs standard: prefix comes first
    const baseA = propA.replace(/^-(webkit|moz|ms|o)-/, "");
    const baseB = propB.replace(/^-(webkit|moz|ms|o)-/, "");
    if (baseA === baseB) {
      const isPrefixA = propA.startsWith("-");
      const isPrefixB = propB.startsWith("-");
      if (isPrefixA && !isPrefixB) return -1;
      if (!isPrefixA && isPrefixB) return 1;
      return a.originalIndex - b.originalIndex;
    }

    // 3. Different properties: sort by defined property groups
    const orderA = getPropertyOrder(propA);
    const orderB = getPropertyOrder(propB);
    if (orderA !== orderB) {
      return orderA - orderB;
    }

    return a.originalIndex - b.originalIndex;
  });

  return indexed.map((item) => item.decl);
}

const sampleDecls: CSSDeclaration[] = [
  { prop: "display", value: "block" },
  { prop: "display", value: "flex" },
  { prop: "-webkit-transform", value: "scale(1)" },
  { prop: "transform", value: "scale(1)" },
  { prop: "color", value: "red" },
];

console.log(safeReorderDeclarations(sampleDecls));

6. Incorrect approaches

Converting Declarations to a JavaScript Object.fromEntries() dictionary, which silently overwrites duplicate fallback properties. Placing standard properties before vendor prefixes, causing outdated prefix rules to override standard CSS.

7. Boundaries: @keyframes declarations and CSS Grid namespaces

Inside @keyframes blocks, declaration order directly impacts keyframe interpolation. CSS Grid area lines also require contextual continuity and should not be forcibly reordered.

8. Verification

Configure stable sorting rules in Stylelint order/properties-order. Use headless browsers to compare computed styles before and after AST reordering.

9. FAQ

Does PostCSS property reordering improve Gzip compression? Slightly, because consistent ordering increases repeated string patterns for LZ77 compression.

What is the most common bug during property sorting? Storing AST declarations in a Key-Value Map, which discards duplicate display or color fallback declarations.

Why is PostCSS safer than regex replacement? PostCSS constructs a full AST that isolates selectors, comments, media queries, and declarations accurately.

10. Summary

When reordering CSS properties via PostCSS AST, preserve relative order for duplicate fallback declarations and vendor prefixes. Never convert AST nodes into overwriting key-value dictionaries.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool