A compile, not a translation
The other Stylus pages on this site rewrite syntax. LESS wants braces back, SCSS wants a dollar sign in front of every variable. Plain CSS wants none of the Stylus machinery at all, so this page runs your code instead of rewording it. A variable named brand does not become --brand or $brand. The hex value gets written wherever the name stood. A mixin does not survive as anything. Its body lands inside each rule which called it, with the arguments filled in. Nothing in the output reads as Stylus, because a browser has no idea what Stylus is.
The same file, before and after:
brand = #1f6f8b
gap = 12px
button(bg)background bg
&:hover
background darken(bg, 12%).toolbar
padding (gap * 2) gap
a.cta
button(brand)&-title
font-weight 600.toolbar {padding: 24px 12px;}
.toolbar a.cta {background: #1f6f8b;}
.toolbar a.cta:hover {background: #144759;}
.toolbar-title {font-weight: 600;}Four Stylus ideas went in: two variables, one mixin with a nested hover, and a parent reference used as a BEM suffix. Zero came out. The arithmetic ran, the colour function ran, the selectors were joined. This is the same result the stylus binary prints for the file, minus one detail covered under where the real compiler still wins.
What each construct becomes
The ledger below is the whole contract. Green rows resolve without your help. Amber rows produce output plus a note in the panel under the editors. Grey rows produce nothing, on purpose, because Stylus itself prints nothing for them.
| Stylus line | What the CSS shows | Handling |
|---|---|---|
brand = #1f6f8b then color brand | color: #1f6f8b; | Resolved |
.card with .title indented under it | .card .title { | Flattened |
&:hover, &-title | .card:hover, .card-title | Flattened |
padding (gap * 2) gap | padding: 24px 12px; | Evaluated |
button(brand) or button brand | The mixin body, inline, with bg replaced | Expanded |
@media tablet nested inside .toolbar | @media ... { .toolbar { } } at the top level | Bubbled up |
@extend .btn inside .btn-alt | .btn, .btn-alt { on the extended rule | Merged |
darken(), lighten(), rgba(#000, .2), unit(), s() | The computed value | Evaluated |
@keyframes, @font-face, @supports | The same at-rule with braces and semicolons | Kept |
| Kept in both output formats | Kept |
| Kept in Expanded, dropped in Compact | Kept |
// line comment | Nothing. Stylus never prints these. | Dropped |
@import "reset.css" | @import "reset.css"; | Kept |
@import "mixins" (a .styl file) | The same import line, unresolved | Kept, noted |
if, unless, for, while blocks | Nothing | Skipped, noted |
add(a, b) with a return line | Calls stay as add(1, 2) | Left, noted |
+block-mixin() with a {block} body | Nothing | Skipped, noted |
| An empty rule with no declarations | Nothing | Dropped |
How the notes panel earns its place
A syntax rewriter fails quietly. It emits something which looks like CSS, and you find out in the browser when a rule does nothing. A compiler has to decide what a line means, so a line with no meaning is caught at the point of failure. Every one of those lines goes into the panel under the editors with its line number, the source text, and one sentence on why nothing came out. An empty panel reading "clean compile" means every source line is accounted for in the CSS. A panel with entries means read them before the file ships. Most of the time the fix is a one-line edit in the Stylus, not in the output.
The count in the top bar tells the other half of the story. If you pasted 40 lines with three variables and the bar reads zero variables resolved, the names were never used in a value, or they were defined after the lines using them. Stylus reads top to bottom, and so does this page.
Three checks before the output ships
- Division only happens inside parentheses.
font 16px/1.5 sans-serifcomes out as the shorthand, untouched, because Stylus treats a bare slash in a property as a literal.width 100% / 3also stays literal, which is a surprise to people arriving from Sass. Writewidth (100% / 3)and the output reads33.333%. Addition, subtraction and multiplication run without the parentheses as long as the operator has a space on each side. - Variables named after CSS keywords resolve like any other.Define
red = #c0392band every latercolor redbecomes the hex. The real compiler does the same. If you see a keyword in the output where you expected a hex, the assignment sits below the first use, or lives in a file this page never saw. - Mixins with block arguments do not expand.A call written as
+wrapper()with an indented body underneath is a block mixin. The page files a note and skips the call rather than guess at the output. Rewrite the mixin to take plain arguments, or compile the one partial with the Stylus CLI.
Where the real compiler still wins
This is a subset of Stylus, chosen to cover the constructs which appear in ordinary component files. The gaps are deliberate, and each one produces a note rather than silent output:
- No file system.
@import "variables"stays an import line. The values defined inside the imported file never arrive, so every reference to them stays a bare word.@requireis dropped. Paste the imported file above your component when you need its variables. - No control flow.
if,unless,forandwhileblocks are skipped whole. Functions with areturnline are not evaluated, and their calls stay as written in the CSS. - Five built-ins, not seventy.
rgba()on a hex,darken(),lighten(),unit()ands()run. Everything else in the Stylus built-in library passes through as a function call the browser will reject.niband other libraries are not loaded. - No partial references.
^[0],~/and../in selectors are not understood.&is. - Extend has no escape hatch.
!optionalis ignored, and a target with no matching rule produces a note instead of an error. - Numbers print to three decimals. Stylus prints more. A width of
(100% / 7)reads14.286%here and14.285714285714286%from the CLI.
Use this page for a component, a snippet from a blog post, a legacy theme file you need to read as CSS today, or a quick check on what a mixin expands to. For a build step, install the package and run stylus -p src/main.styl. The CLI reads your imports, runs your functions, and prints the exact bytes your users get. Nothing in a browser tab should be the source of truth for a production stylesheet.
Is plain CSS the right stop?
Native nesting shipped in every major browser in 2023. Custom properties have been there since 2016. Colour math arrived as color-mix() in the same window. Most of what Stylus gave a team in 2014 is now spelled in CSS itself, which is why the answer for a team leaving Stylus is usually plain CSS, not another preprocessor.
Two edits are worth making on the compiled output before you commit it. Anything you would want to change at runtime, a brand colour, a spacing scale, a theme switch, goes back into a --custom-property on :root. The compiled hex is fine for constants, and a variable in the source is a hint about which values were meant to change. Second, leave the flattened selectors alone. Native nesting is optional, the flat form is what every tool in your pipeline already understands, and a diff against the old compiled CSS is easier when the selectors match line for line.
Where plain CSS still falls short: mixins with logic, loops which generate utility classes, and colour functions with arguments computed at build time. If your Stylus leans on those, target SCSS and keep a compile step. If you have a LESS pipeline waiting, use Stylus to LESS instead. And if this page compiled your file with an empty notes panel, a preprocessor is dead weight in your build.
