A modern templating engine with Vue-like Single File Components, Laravel Blade directives, and Bun-powered performance.
Features
- Vue-like SFC -
<script>,<template>,<style>structure - Auto-imported Components - Use your
<Card />directly, no imports needed - Reactive Signals -
state(),derived(),effect()for fine-grained reactivity - Blade Directives -
@if,@foreach,@layout,@section, and 40+ more - Expression Filters -
{{ price | currency }}with 30+ built-in filters - Props & Slots - Pass data and content to components
- Form Directives -
@form,@input,@select,@csrfwith validation - SEO & Sitemap -
@seo,@meta,@structuredDatawith auto-injection - PWA Support - Service worker, manifest, and offline page generation
- 200K+ Icons - Built-in Iconify integration
- Crosswind CSS - Utility-first CSS framework integration
- Native Desktop - Build desktop apps with
stx dev --native - Custom Directives - Extend with your own directives
Quick Start
# bunfig.toml preload = ["bun-plugin-stx"]
Single File Components
STX components use a Vue-like structure:
<!-- components/Greeting.stx --> <script server> const name = props.name || 'World' const time = new Date().toLocaleTimeString() </script> <template> <div class="greeting"> <h1>Hello, {{ name }}!</h1> <p>Current time: {{ time }}</p> <slot /> </div> </template> <style> .greeting { padding: 2rem; background: #f5f5f5; } </style>
Script Types
| Type | Behavior |
|---|---|
<script server> |
SSR only - extracted for variables, stripped from output |
<script client> |
Client only - preserved for browser, skips server evaluation |
<script> |
Both - runs on server AND preserved for client |
The export keyword is optional in <script> tags. All top-level declarations are automatically available to the template:
<script> const title = 'Hello' // auto-available export const count = 42 // also works function greet(name) { // auto-available return `Hi, ${name}!` } </script> <h1>{{ title }}</h1> <p>{{ greet('Alice') }}</p>
Components
Components in components/ are auto-imported using PascalCase:
<!-- pages/home.stx --> <Header /> <main> <UserCard name="John" role="Admin" /> <Card title="Welcome"> <p>This goes into the slot!</p> </Card> </main> <Footer />
Props
Pass data to components via attributes:
<!-- String prop --> <Card title="Hello" /> <!-- Expression binding with : --> <Card :count="items.length" :active="isActive" />
Access props in components:
<script server> const title = props.title || 'Default' const count = props.count || 0 </script> <template> <h1>{{ title }}</h1> <p>Count: {{ count }}</p> </template>
Slots
Use <slot /> to inject content into components:
<!-- components/Card.stx --> <template> <div class="card"> <h2>{{ props.title }}</h2> <slot /> </div> </template>
<!-- Usage --> <Card title="News"> <p>This content appears in the slot!</p> </Card>
Layouts
Wrap pages with common structure:
<!-- layouts/default.stx --> <!DOCTYPE html> <html> <head> <title>{{ title || 'My App' }}</title> </head> <body> <Header /> <main>@yield('content')</main> <Footer /> </body> </html>
<!-- pages/about.stx --> @layout('default') @section('content') <h1>About Us</h1> <p>Welcome to our site.</p> @endsection
Template Directives
Conditionals
@if(user.isAdmin) <AdminPanel /> @elseif(user.isEditor) <EditorTools /> @else <UserView /> @endif @unless(isAuthenticated) <LoginPrompt /> @endunless @switch(user.role) @case('admin') <AdminBadge /> @break @case('editor') <EditorBadge /> @break @default <UserBadge /> @endswitch
Loops
@foreach(items as item) <li>{{ item.name }}</li> @endforeach @foreach(users as index => user) <tr> <td>{{ index + 1 }}</td> <td>{{ user.name }}</td> </tr> @endforeach @forelse(notifications as notice) <div>{{ notice.message }}</div> @empty <p>No notifications.</p> @endforelse @for(let i = 0; i < 5; i++) <li>Item {{ i }}</li> @endfor
Loop control with @break and @continue:
@foreach(items as item) @continue(item.hidden) @break(item.isLast) <li>{{ item.name }}</li> @endforeach
Expressions & Filters
<!-- Escaped output (safe) --> {{ userInput }} <!-- Raw HTML (trusted content only) --> {!! trustedHtml !!} <!-- Filters --> {{ name | uppercase }} {{ price | currency }} {{ bio | truncate:100 }} {{ items | length }} {{ created | date:'medium' }} <!-- Chained filters --> {{ description | stripTags | truncate:200 | capitalize }}
Built-in filters: uppercase, lowercase, capitalize, truncate, replace, stripTags, number, currency, fmt, abs, round, join, first, last, length, reverse, slice, escape, json, default, urlencode, pluralize, date, translate.
Auth Guards
@auth <p>Welcome back, {{ user.name }}!</p> @endauth @guest <a href="/login">Please log in</a> @endguest @can('edit-posts') <button>Edit</button> @endcan
Other Directives
@isset(title) <h1>{{ title }}</h1> @endisset @empty(items) <p>Nothing here.</p> @endempty @env('production') <script src="/analytics.js"></script> @endenv {{-- This is a comment and won't appear in output --}}
Forms
Built-in form directives with CSRF protection, validation, and old value preservation:
@form('POST', '/register')
@input('name', '', { placeholder: 'Full Name' })
@error('name')
<span class="error">{{ $message }}</span>
@enderror
@input('email', '', { type: 'email', placeholder: 'Email' })
@textarea('bio')Write about yourself@endtextarea
@select('country')
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
@endselect
@checkbox('agree', '1')
@radio('plan', 'free')
@radio('plan', 'pro')
<button type="submit">Register</button>
@endform@csrf is automatically included in @form. For manual forms, add @csrf and @method('PUT') for non-POST methods.
SEO
@seo({
title: 'Product Name - My Store',
description: 'High quality product description.',
canonical: 'https://mystore.com/product/1',
openGraph: {
type: 'product',
image: 'https://mystore.com/img/product.jpg',
},
twitter: {
card: 'summary_large_image',
site: '@mystore',
},
})
@meta('author', 'John Doe')
@structuredData({
"@type": "Product",
"name": "Widget",
"description": "A great widget",
"offers": { "@type": "Offer", "price": "29.99" }
})Programmatic sitemap and robots.txt generation:
import { generateSitemap, generateRobotsTxt } from '@stacksjs/stx' const sitemap = generateSitemap([ { loc: '/', priority: 1.0 }, { loc: '/about', priority: 0.8 }, { loc: '/blog', changefreq: 'daily' }, ], { baseUrl: 'https://example.com' }) const robots = generateRobotsTxt({ rules: [{ userAgent: '*', allow: ['/'], disallow: ['/admin'] }], sitemap: 'https://example.com/sitemap.xml', })
Signals (Reactivity)
Fine-grained reactivity with state(), derived(), and effect():
<script> const count = state(0) const doubled = derived(() => count() * 2) effect(() => { console.log('Count changed:', count()) }) </script> <template> <p>Count: {{ count() }}</p> <p>Doubled: {{ doubled() }}</p> <button @click="count.set(count() + 1)">Increment</button> </template>
Custom Directives
import { stxPlugin, type CustomDirective } from 'bun-plugin-stx' const uppercase: CustomDirective = { name: 'uppercase', handler: (content, params) => params[0]?.toUpperCase() || content.toUpperCase(), } const wrap: CustomDirective = { name: 'wrap', hasEndTag: true, handler: (content, params) => `<div class="${params[0] || 'wrapper'}">${content}</div>`, } Bun.build({ entrypoints: ['./src/index.stx'], plugins: [stxPlugin({ customDirectives: [uppercase, wrap] })], })
Icons
200K+ icons via Iconify:
<HomeIcon size="24" /> <SearchIcon size="20" color="#333" />
bun stx iconify list bun stx iconify generate material-symbols
VS Code Extension
The stx VS Code extension provides full IDE support:
- Syntax highlighting for
.stxfiles - TypeScript IntelliSense inside
<script>blocks - Autocomplete for 250+ directives
- Hover documentation for directives and variables
- Go-to-definition for templates and components
- Real-time diagnostics (unclosed directives, missing templates)
- Crosswind CSS utility class previews, color decorations, and sorting
- Code folding, document links, and semantic tokens
The extension also exports all its features as a library for building custom plugins:
import { createHoverProvider, createCompletionProvider, VirtualTsDocumentProvider, ComponentRegistry, activateCrosswind, } from 'vscode-stacks'
Packages
| Package | Description |
|---|---|
stx |
Core template processing engine |
bun-plugin-stx |
Bun plugin for .stx file processing |
vscode-stacks |
VS Code extension with TypeScript support |
@stacksjs/desktop |
Native desktop app framework (via Craft) |
@stacksjs/markdown |
Markdown parsing with frontmatter |
@stacksjs/sanitizer |
HTML/XSS sanitization |
stx-devtools |
Development tooling |
Development
# Install dependencies bun install # Run tests bun test # Build all packages bun run build # Lint bun run lint
Documentation
License
MIT
