Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Stylus Laravel Package

kylekatarnls/stylus

PHP implementation of the Stylus CSS preprocessor. Compile .styl stylesheets into plain CSS without a Node.js toolchain, using variables, mixins, nesting, and functions to keep styles maintainable in Laravel and other PHP projects.

Deep Wiki
Context7

Getting Started

Install as a build-time dependency, not a runtime one. The compiler never needs to run in production if you commit or deploy compiled CSS:

composer require --dev kylekatarnls/stylus

Read the source before the README. Documentation for this package is thin, so the fastest orientation is the compiler class itself (vendor/kylekatarnls/stylus/src) plus the test suite — the tests are the de facto syntax spec and tell you immediately which Stylus features the port actually implements. Budget 20 minutes for this; it saves hours of "why doesn't @extend work" later.

Minimal first compile. The API surface is deliberately small: instantiate the compiler, hand it source, get CSS back.

use Stylus\Stylus;

$css = (new Stylus())->compileString(<<<'STYL'
primary = #3490dc

body
  color primary
  a
    color darken(primary, 20%)
STYL);

For files, point it at a path instead of a string (compileFile() / equivalent — confirm the exact method name against the class, the docs don't spell it out). Options are passed as a constructor/compile array; the two that matter day to day are compression (production) and readable/line-numbered output (development).

First real use case: a fidelity check. Take the .styl file you already have working under node-stylus, compile it with this package, and diff the output against the CSS you currently ship. That single exercise tells you whether the port is viable for your stylesheet far more reliably than the feature list does. If the diff is clean, proceed; if it's not, you've learned that in ten minutes instead of after a migration.

CLI. A bundled console command exists for one-off compilation (vendor/bin/stylus-style binary; check composer.json's bin section for the name). It's fine for a quick loop, but you'll replace it with an Artisan command almost immediately — see below.

Implementation Patterns

Compile-on-demand with an mtime cache. The single most common integration: a helper that compiles only when sources change, so views stay simple and requests stay fast.

// app/Support/StylusCompiler.php
public function css(string $name): string
{
    $src = resource_path("stylus/{$name}.styl");
    $out = public_path("css/{$name}.css");

    if (! is_file($out) || $this->isStale($src, $out)) {
        File::ensureDirectoryExists(dirname($out));
        File::put($out, (new Stylus(['compress' => ! app()->isLocal()]))
            ->compileFile($src));
    }

    return asset("css/{$name}.css");
}
<link rel="stylesheet" href="{{ app(\App\Support\StylusCompiler::class)->css('app') }}">

Note isStale() needs to consider imported partials, not just the entrypoint — see Gotchas.

An Artisan build command, wired into Composer. This is what you actually want in CI and deploys, because it never runs at request time and it fails loudly:

class BuildStylus extends Command
{
    protected $signature = 'stylus:build';

    public function handle(StylusCompiler $compiler): int
    {
        foreach (File::glob(resource_path('stylus/*.styl')) as $entry) {
            if (str_starts_with(basename($entry), '_')) {
                continue; // partial, imported not compiled
            }
            $compiler->compile($entry);
            $this->line('compiled ' . basename($entry));
        }
        return self::SUCCESS;
    }
}
"scripts": {
  "post-autoload-dump": ["@php artisan stylus:build"]
}

Hooking post-autoload-dump means a fresh composer install on any machine produces CSS with zero extra instructions — which is the whole point of a no-JavaScript preprocessor.

Partials and shared tokens. Keep _variables.styl and _mixins.styl as underscore-prefixed partials, imported at the top of every entrypoint with an explicit base path:

new Stylus(['paths' => [resource_path('stylus')]])

Set that base path explicitly and pass absolute paths everywhere. Stylus @import is textual, so every entrypoint must re-import the token file — there's no shared module scope.

String compilation as a test harness. Because there's no Node toolchain to boot, you can unit-test style logic directly:

public function test_mixins_expand(): void
{
    $css = (new Stylus())->compileString("rounded(r)\n  border-radius r\n.btn\n  rounded(4px)");

    $this->assertStringContainsString('border-radius: 4px', $css);
}

Use this to pin down exactly which built-ins and operators the port supports — a living compatibility matrix instead of guesswork.

Keep it out of the JS build entirely. Don't try to wire this into Vite/Laravel Mix as a loader; run it as a separate Composer/Artisan step that writes to public/css. Two independent pipelines (PHP→CSS, JS→bundle) is the correct shape and avoids fighting the Node plugin ecosystem.

Gotchas and Tips

It's effectively unmaintained. Last release January 2019. Pin the exact version in composer.json, and expect PHP 8.x deprecation noise from an older codebase. Before committing to it, run your real stylesheets through it on your target PHP version — that's the only compatibility statement that matters. Be prepared to fork and vendor if you hit a blocker; the compiler is small enough that this is realistic.

A port is not the reference implementation. Node-stylus has years of accumulated behavior; this is a reimplementation. The features that most often diverge are selector interpolation, @extend, @media bubbling, and less-common built-in functions. Diff against node-stylus output before you migrate, not after.

Indentation is semantics. Tabs vs. spaces and inconsistent depth are the number one cause of a silent mis-compile — you get plausible-looking CSS with rules nested in the wrong place. Put a .editorconfig in place and enforce one style. This bites hardest on files copied from mixed sources.

mtime caching must cover partials. Checking only the entrypoint's mtime means edits to _variables.styl never trigger a rebuild — a genuinely confusing class of bug where "the change didn't apply." Either walk the @import graph, or just rebuild every entrypoint unconditionally outside local development.

No sourcemaps. You get line numbers or expanded output, not a mapping back to .styl. Debug in the browser against the expanded output and correlate by selector; don't expect devtools to show you the original file.

Create output directories yourself. The compiler writes CSS, not folders. File::ensureDirectoryExists() before every write, or a first deploy on a clean box dies on a missing public/css.

Wrap exceptions with context. Errors tend to surface as generic exceptions without the offending filename. Catch at your build command and re-throw with the path and a line number if you can extract one — otherwise one typo in one partial fails the whole deploy with an unhelpful stack trace.

Never serve .styl from public/. Sources belong in resources/stylus; only compiled CSS in public/css. Obvious, but people get it wrong when they start from the CLI's relative-path examples.

Use it as an extension point, not a fork. Wrap the compiler behind your own thin interface in app/Support. If a built-in is missing, add it in the wrapper rather than patching vendor/. That one indirection makes swapping to a maintained compiler — or dropping the preprocessor altogether — a single-file change.

Ask whether you still need it. With native CSS nesting, custom properties, and @layer, a lot of what people used Stylus for in 2019 is now baseline CSS. If your stylesheets only use variables and nesting, the honest assessment is that this dependency costs more (unmaintained, imperfect fidelity, no sourcemaps) than it saves. It's worth it if you have substantial existing .styl assets and want to keep them compiling without Node; it's not worth adopting greenfield.

Add a CI drift check. Compile in CI and git diff --exit-code public/css. Committed compiled assets silently rot otherwise, and a stale CSS file in production is nearly invisible until someone notices the design looks wrong.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log