Stylus Mixins with Default Parameters: Cutting CSS Boilerplate Without Losing Control
Stylus mixins with default parameters let you define reusable CSS rule blocks that gracefully handle missing arguments. Learn the syntax, see a worked button example, understand nested scope for theming, and avoid the silent-fallback trap.
15 Oct 2026, 13:55 UTC

The Problem: Repeating Yourself in CSS
Every CSS author knows the frustration: you write a button component, then need a variant with a different background, then another with a different border radius. Copy-paste scales poorly. Sass users reach for @mixin with default arguments; Stylus users have a similar but distinct mechanism that's worth understanding on its own terms.
Stylus mixins accept parameters with compile-time defaults using the = syntax. This lets you call a mixin with zero, some, or all arguments while keeping a single source of truth for the base styles.
Basic Syntax: Default Parameters in Action
Define a mixin by naming it, listing parameters with defaults, then writing the rule block. The = operator assigns a fallback value used when the caller omits that argument.
button-style(bg = #2563eb, radius = 4px, pad = 0.75rem 1.5rem)
display inline-flex
align-items center
justify-content center
background bg
border-radius radius
padding pad
color #fff
font-weight 600
border none
cursor pointer
transition background 0.15s ease
&:hover
background lighten(bg, 10%)
.primary-btn
button-style()
.secondary-btn
button-style(#64748b)
.pill-btn
button-style(#059669, 9999px)
Calling button-style() with no arguments produces a blue button with 4px radius. Passing one argument overrides only the background; the rest fall back. Three arguments fully customize the output. The compiled CSS contains three distinct rule sets with no duplication of the shared properties.
Nested Mixins for Theme Overrides
Stylus mixins inherit the lexical scope where they're defined. This means a mixin defined inside a selector can reference variables from that selector—useful for theme-specific overrides without duplicating the entire mixin.
$theme-primary = #2563eb
$theme-danger = #dc2626
themed-button(label, bg = $theme-primary)
button-style(bg)
&::before
content label
margin-right 0.5rem
.card
$theme-primary = #7c3aed
.primary-action
themed-button("Save")
.danger-action
themed-button("Delete", $theme-danger)Inside .card, the local $theme-primary shadows the global one. The .primary-action button compiles to purple (#7c3aed) while .danger-action explicitly passes the danger red. The mixin definition stays DRY; the theme context does the work.
Trade-offs and Limitations
- Legacy compiler support: The JavaScript-based Stylus compiler prior to version 0.57 does not support default parameters in mixins. If you're on an older build pipeline, you'll need to upgrade or rewrite without defaults.
- Silent fallbacks mask errors: A typo in a mixin call—
button-style(#2563eb, 8x)instead of8px—won't error; Stylus treats the malformed value as a string and passes it through. The browser ignores the invalidborder-radius, and you may not notice until visual regression testing. - Node version deprecation warnings: On Node 18 and earlier, certain mixin patterns (particularly rest parameters combined with defaults) emit deprecation warnings. Node 20+ with
--compressminifies cleanly.
Verifying the Output
Compile a test file and inspect the generated CSS to confirm defaults behave as expected:
# Create test.styl with the button-style mixin and three calls
stylus -u nib test.styl -o test.css
cat test.css
Run this in your project root (or any directory with the stylus binary on PATH). You need write permission to the output directory. The -u nib flag loads the Nib library if your mixins use its functions like lighten(). Check that each selector has the expected computed values and that no fallback leaked into a call that provided an explicit argument.
When to Reach for This Pattern
Default parameters shine when you have a family of components sharing 80% of their styles but differing in a few predictable knobs—color, spacing, radius. They're less useful when every variant needs completely different properties; at that point, separate mixins or composition via @extend-like patterns (Stylus supports @extend but with different semantics) communicate intent more clearly.
Start by extracting one repeated component into a mixin with sensible defaults. Add parameters only when a real variant demands it. The compiled CSS will tell you if you've over-abstracted: look for selectors that share nothing but the mixin name.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.