A modern templating engine with Vue-like Single File Components, Laravel Blade directives, and Bun-powered performance.
- 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
bun add bun-plugin-stx# bunfig.toml
preload = ["bun-plugin-stx"]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>| 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 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 />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>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>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@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@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>
@endforLoop control with @break and @continue:
@foreach(items as item)
@continue(item.hidden)
@break(item.isLast)
<li>{{ item.name }}</li>
@endforeach<!-- 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
<p>Welcome back, {{ user.name }}!</p>
@endauth
@guest
<a href="/login">Please log in</a>
@endguest
@can('edit-posts')
<button>Edit</button>
@endcan@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 --}}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({
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',
})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>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] })],
})200K+ icons via Iconify:
<HomeIcon size="24" />
<SearchIcon size="20" color="#333" />bun stx iconify list
bun stx iconify generate material-symbolsThe 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'| 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 |
# Install dependencies
bun install
# Run tests
bun test
# Build all packages
bun run build
# Lint
bun run lintMIT
