Choosing Stylus in 2026: Syntax Options, Built-in Features, and Whether to Adopt It
Stylus offers two syntaxes and powerful mixins, but its archived status and native CSS overlap make it a questionable choice for new projects in 2026. This guide compares options, shows validation steps, and outlines when to choose alternatives.
23 Aug 2025, 03:51 UTC

The Decision: Should You Use Stylus for a New Project?
Stylus is a Node.js-based CSS preprocessor that offers two syntax flavors in one tool: whitespace-significant (indented, brace-free) and standard CSS-like syntax with braces and semicolons. It also ships with mixins, functions, and module imports that historically reduced repetition. The problem in 2026 is that the original repository was archived around 2024, native CSS now covers variables, nesting, and color manipulation, and the ecosystem has shifted toward Sass, PostCSS, or no preprocessor at all. This guide compares Stylus's supported options, highlights the trade-offs, and shows how to validate behavior before committing.
Syntax Options: Indented vs. Braced
Stylus lets you pick per file. The indented syntax omits braces, semicolons, and often colons; the braced syntax looks like vanilla CSS. Both compile to identical CSS, but the parser's permissiveness means a typo can silently become a valid selector or property.
| Aspect | Indented Syntax | Braced Syntax |
|---|---|---|
| File extension | .styl | .styl (same extension; syntax detected from content) |
| Braces | Omitted | Required |
| Semicolons | Optional | Required |
| Colons | Optional | Required |
| Ambiguity risk | Higher (missing colon can merge property/value) | Lower (explicit delimiters) |
| Team familiarity | Python/Haml veterans | CSS/SCSS veterans |
Recommendation: Pick one style per project and enforce it with a linter. Mixing styles in the same codebase creates review friction and subtle parse differences.
Mixins: Transparent, Parameterized, and Rest Arguments
Stylus mixins are called like properties (transparent mixins) or with explicit +/@include. They support default parameters and rest arguments (...) for variable arity.
// transparent mixin - called like a property
border-radius()
-webkit-border-radius arguments
-moz-border-radius arguments
border-radius arguments
button
border-radius 4px 8px
// parameterized with defaults
shadow(offset-x = 0, offset-y = 0, blur = 4px, color = rgba(0,0,0,.25))
box-shadow offset-x offset-y blur color
card
shadow 2px 2px 8px rgba(0,0,0,.3)
// rest arguments for multiple values
transitions(props...)
transition props
link
transitions color .2s ease, background .3s ease
Unlike Sass placeholder selectors (%placeholder), Stylus mixins don't require @extend gymnastics—they inline directly where called. This keeps output predictable but can bloat CSS if a large mixin is used in many selectors.
Functions: Built-ins and JavaScript Escape Hatch
Stylus ships color helpers (darken(), lighten(), rgba()), unit math, and type inspection. For anything missing, you can register a JavaScript function at compile time.
// Stylus file
brand-contrast(ratio = 4.5)
// custom logic delegated to JS
return contrast-adjust(brand-color, ratio)
// In your build script (Node)
const stylus = require('stylus')
stylus(str)
.define('contrast-adjust', (color, ratio) => {
// compute and return a Stylus Color node
return stylus.nodes.Color.fromRGB(...)
})
.render((err, css) => { /* ... */ })
This keeps logic close to styles but couples your stylesheets to a Node build step. Native CSS color-mix() and relative color syntax now cover many of these cases without a preprocessor.
@import vs @require: Deduplication Matters
Both directives pull in partials, but @require prevents duplicate inclusion of the same file. In large codebases where multiple components import a shared reset or token file, @import duplicates rules; @require emits them once.
// tokens.styl
$primary = #0066ff
// component-a.styl
@require 'tokens'
.button-a { color: $primary }
// component-b.styl
@require 'tokens'
.button-b { color: $primary }
// compiled output: $primary definition appears once
Use @require for shared partials; reserve @import for CSS @import passthrough or when you intentionally want multiple inclusions.
Validation Checklist Before Adoption
- Verify current maintenance status. Run
npm view stylus version time.modifiedin a terminal (no elevated permissions) to see the latest publish date. Check the GitHub repo for archived status and open security advisories. - Confirm syntax parity. Create a scratch project:
Expect zero diff. If output differs, the parser interpreted something differently—investigate before committing.mkdir stylus-test && cd stylus-test npm init -y npm install stylus # indented.syntax.styl body color #333 .button padding 1rem # braced.syntax.styl body { color: #333; .button { padding: 1rem; } } npx stylus indented.syntax.styl braced.syntax.styl --out ./out diff out/indented.syntax.css out/braced.syntax.css - Test @require deduplication. Compile a file that
@requires the same partial twice and verify the partial's rules appear once in the output CSS. - Enable linting. Install
stylelintwithstylelint-config-stylus(or a community config). Run it on a file containing a deliberate typo (e.g.,colr: red) and confirm it flags the error. Without linting, Stylus's permissive parser treatscolras a valid property name.
Trade-offs Versus Modern Alternatives
| Factor | Stylus | Sass (Dart) | PostCSS | Native CSS |
|---|---|---|---|---|
| Maintenance | Archived ~2024 | Active | Active | Browser-implemented |
| Syntax flexibility | Two styles, optional punctuation | SCSS (braced) + indented | CSS-like, plugin-defined | Standard only |
| Variables | $var | $var | Via plugin | --custom-property |
| Nesting | Built-in | Built-in | Via plugin | Native (most browsers) |
| Color functions | Built-in | Built-in | Via plugin | color-mix(), relative syntax |
| JS extensibility | Direct .define() | Custom functions (async) | Plugin API | N/A |
| Bundle size impact | Preprocessor only | Preprocessor only | Can run in bundler | Zero |
If your team already knows Stylus and has a maintained codebase, staying put is reasonable—just add linting and pin the version. For new projects, the default choice should be native CSS with PostCSS for autoprefixing and future syntax transforms, or Sass if you need a mature preprocessor ecosystem.
Limitations and Risks
- No security updates: An archived repo means unpatched vulnerabilities could surface. Pin an exact version (
npm install stylus@0.64.0) and monitor advisories. - Silent parse errors: Typos like
margin 1rem 2rem 3rem(missing fourth value) may compile without warning. Linting is not optional. - Build dependency: Stylus requires Node at build time. Native CSS removes that dependency entirely.
- IDE support: Language server support lags behind Sass/PostCSS; expect weaker autocomplete and refactoring.
Practical Verification Step
After adding linting, run this in your CI pipeline to catch regressions:
# package.json scripts
"lint:stylus": "stylelint '**/*.styl' --custom-syntax postcss-stylus",
"build:css": "stylus src -o dist --include-css"
# Run locally before commit
npm run lint:stylus && npm run build:css
If the lint step passes and the build produces expected CSS, your Stylus setup is functionally sound. Re-run the npm view check quarterly to catch maintenance changes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.