Skip to content
AgileLogics

CSS to LESS

Nests flat CSS selectors (e.g. ".card .title" or ".nav > li") into LESS-style nested blocks without changing what the styles do: rule order is kept, @media/@supports blocks keep their query, and comments, selector lists and other at-rules are copied through unchanged.

Working on a whole file? Open it in the CSS editor →

How it works

Paste flat CSS and click Convert.

Converting flat CSS into nested LESS

In plain CSS every rule repeats its full selector: .card, then .card .title, then .card .title a. LESS lets you nest those rules inside each other so the shared prefix is written once. This converter does that rewrite for you, and it's careful about one thing above all: the LESS it produces compiles back to CSS that behaves exactly like your input.

It reads the stylesheet properly (strings, comments, url(...) values and nested braces included), nests what can be nested safely and copies everything else through unchanged.

Example

Input:

.card { padding: 16px; border-radius: 8px; }
.card .title { font-size: 18px; font-weight: 600; }
.card .title a { color: #0a58ca; }
.card .body { color: #333; }

Output:

.card {
    padding: 16px;
    border-radius: 8px;
    .title {
        font-size: 18px;
        font-weight: 600;
        a {
            color: #0a58ca;
        }
    }
    .body {
        color: #333;
    }
}

Compiling that LESS gives back the original four selectors, because a nested selector in LESS means "descendant of the parent" (LESS nesting docs). Output is indented with four spaces, and each declaration is printed on its own line with a semicolon.

Combinators, comments and media queries

A slightly bigger stylesheet shows the rest of the behaviour:

/* Navigation */
.nav { display: flex; }
.nav > li { padding: 8px; }
.nav > li a { color: #0a58ca; }
.nav > li a:hover { color: #084298; }
@media (max-width: 600px) {
  .nav { flex-direction: column; }
  .nav > li { padding: 4px; }
}

becomes:

/* Navigation */
.nav {
    display: flex;
    > li {
        padding: 8px;
        a {
            color: #0a58ca;
        }
        a:hover {
            color: #084298;
        }
    }
}
@media (max-width: 600px) {
    .nav {
        flex-direction: column;
        > li {
            padding: 4px;
        }
    }
}

The child combinator stays attached to the selector that follows it, so .nav > li becomes > li inside .nav, which LESS compiles back to .nav > li. The comment is kept where it was, and the media query keeps its condition with its own rules nested inside it.

How the converter treats each kind of rule

Input What you get
.a .b descendant selectors Nested .a { .b { ... } }
.a > .b, .a + .b, .a ~ .b Nested .a { > .b { ... } } and so on
The same selector twice in a row One block with both sets of declarations
h1, h2 { ... } with a comma Kept whole as a top-level h1, h2 block
.btn:hover Separate top-level .btn:hover block, not &:hover
@media, @supports, @container, @layer Query kept, rules inside nested
@font-face, @keyframes, @import Copied through unchanged
/* comments */ Kept in place, inside or between rules
url(data:...;base64,...) and quoted strings Values kept whole
A rule that already contains nested blocks Copied through unchanged

Unbalanced braces, an unclosed string or an unclosed comment are reported as errors instead of producing a half-converted file.

Why the cascade is preserved

When two CSS rules match the same element with equal specificity, the later one wins, so the order of rules matters. The converter only merges a rule into an existing block when that block is the most recent thing written. Given .a .b { color: red; }, then .c { margin: 0; }, then .a .b { color: blue; }, you get two separate .a blocks with .c between them rather than one merged block. That looks a little repetitive, but the blue still comes last and still wins. You can merge such blocks by hand once you've checked nothing in between targets the same elements.

A safe workflow

  1. Check the stylesheet's braces and declarations with the CSS validator.
  2. Paste the stylesheet and click Convert.
  3. Review any blocks that were copied through unchanged (selector lists, at-rules other than the grouping ones) and nest them by hand if you want to.
  4. Compile the LESS once and compare the generated CSS with the original, for example in a diff tool.

What it leaves for you

The converter doesn't introduce any LESS features beyond nesting. Pseudo-classes aren't moved under their parent with &, repeated values aren't extracted into @variables, and shared groups of declarations aren't turned into mixins. Those are the changes that make a LESS codebase easier to work with, and they're judgement calls a mechanical tool can't make well. The output targets LESS 4, where / in a value such as font: 12px/1.5 is left as written.

If you'd rather keep plain CSS, modern browsers also support native CSS nesting, with similar syntax. Either way, the CSS minifier shrinks the compiled result, the online CSS editor is handy for cleaning it up, and the CSS lessons cover the selector and specificity rules that nesting builds on.

Last updated

Frequently asked questions

Does this handle @media queries or other at-rules?
Yes. @media, @supports, @container and @layer blocks keep their query, and the rules inside them are nested the same way as top-level rules. Other at-rules such as @font-face, @keyframes and @import are copied through unchanged.
Do I need to convert CSS before using it in a LESS file?
No. LESS is a backwards-compatible extension of CSS, so any valid stylesheet already compiles as LESS. Converting only changes the layout to nested blocks, which is easier to maintain.
Does it create variables or mixins?
No. It only nests selectors. Repeated colours and sizes stay as literal values; turning them into @variables is a manual step.
Why does .btn:hover stay at the top level?
The converter splits selectors at combinators (a space, >, + or ~). .btn:hover has none, so it's kept as its own selector rather than rewritten as &:hover inside .btn. The compiled CSS is the same either way.
Why do I sometimes see the same parent block twice?
Rules are only merged into an earlier block when nothing else came between them. Merging across other rules would reorder the cascade and could change which declaration wins, so the converter starts a new block instead.
Is my CSS uploaded?
No. The transformation is plain JavaScript running in your browser.