stacks/stx
publicClone
Push over the same URL. A password will not work: create a token under access tokens and use it in place of one.
A performant & powerful UI templating language.
- .config
- .github
- .vscode
- docs
- examples
- packages
- scripts
- test-install
- test-utils
- .editorconfig 147 B
- .gitattributes 12 B
- .gitignore 2.0 KB
- bumpx.config.ts 306 B
- bun.lock 162.4 KB
- bunfig.toml 555 B
- CHANGELOG.md 1.1 MB
- CLAUDE.md 66.2 KB
- deps.yaml 246 B
- LICENSE.md 1.0 KB
- logsmith.config.ts 103 B
- netlify.toml 66 B
- package.json 5.3 KB
- pantry.lock 70.7 KB
- README.md 11.5 KB
- stx 55 B
- stx.config.ts 448 B
- tsconfig.json 2.2 KB

stx
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 -
with 30+ built-in filters - Props & Slots - Pass data and content to components
- Form Directives -
@form,@input,@select,with validation - SEO & Sitemap -
@seo,@meta,@structuredDatawith auto-injection - PWA Support - Service worker, manifest, and offline page generation
- Web Component Libraries - Compile
.stxdesign systems to progressive custom elements with CSS-first rendering, SSR/Declarative Shadow DOM, CEM, JSX types, and ESM bundles - 200K+ Icons - Built-in Iconify integration
- Css CSS - Utility-first CSS framework integration
- Native Desktop - Build desktop apps with
stx dev --native - Custom Directives - Extend with your own directives
Quick Start
bun add bun-plugin-stx# 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>README.md</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>
@endsectionTemplate 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 />
@endswitchLoops
@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>
@endforeachExpressions & 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>
@endcanOther 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 is automatically included in @form. For manual forms, add and 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')
@meta('og:title', post.title) <!-- unquoted arguments are expressions -->
@structuredData({
"@type": "Product",
"name": "Widget",
"description": "A great widget",
"offers": { "@type": "Offer", "price": "29.99" }
})Every SEO directive argument is an expression, so : "seo" is not defined here. Values a directive reads must come from