
Product
Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.
@stacksjs/stx
Advanced tools

A Blade-like template engine plugin for Bun, enabling simple and powerful templating with .stx files.
:class, :style, :text, @click, @model)state, derived, effect)useRef, useFetch, useLocalStorage, etc.)bun add bun-plugin-stx
Add the plugin to your bunfig.toml:
preload = [ "bun-plugin-stx" ]
# or as a serve plugin
[serve.static]
plugins = [ "bun-plugin-stx" ]
Or register the plugin in your build script:
import { build } from 'bun'
import stxPlugin from 'bun-plugin-stx'
await build({
entrypoints: ['./src/index.ts', './templates/home.stx'],
outdir: './dist',
plugins: [stxPlugin],
})
In your build script or Bun configuration:
// build.js
import { build } from 'bun'
import stxPlugin from 'bun-plugin-stx'
await build({
entrypoints: ['./src/index.ts', './templates/home.stx'],
outdir: './dist',
plugins: [stxPlugin],
})
You can import .stx files directly in your ESM code:
// app.js
import homeTemplate from './templates/home.stx'
// Use the processed HTML content
document.body.innerHTML = homeTemplate
You can serve .stx files directly with Bun's server:
// server.js
import { serve } from 'bun'
import homeTemplate from './home.stx'
serve({
port: 3000,
fetch(req) {
return new Response(homeTemplate, {
headers: { 'Content-Type': 'text/html' }
})
}
})
Or use as route handlers:
import about from './about.stx'
// server.js
import home from './home.stx'
export default {
port: 3000,
routes: {
'/': home,
'/about': about
}
}
stx templates use a syntax inspired by Laravel Blade. Templates can contain HTML with special directives for rendering dynamic content.
<!DOCTYPE html>
<html>
<head>
<title>stx Example</title>
<script>
// Define your data as an ESM export
export const title = "Hello World";
export const items = ["Apple", "Banana", "Cherry"];
export const showFooter = true;
</script>
</head>
<body>
<h1>{{ title }}</h1>
<ul>
@foreach (items as item)
<li>{{ item }}</li>
@endforeach
</ul>
@if (showFooter)
<footer>Copyright 2023</footer>
@endif
</body>
</html>
There are two ways to expose data in your stx templates:
<script>
// Modern ESM named exports
export const title = "Hello World";
export const count = 42;
// Export functions
export function getFullName(first, last) {
return `${first} ${last}`;
}
// Export default object
export default {
items: ["Apple", "Banana", "Cherry"],
showDetails: true
};
</script>
<script>
// Legacy CommonJS exports
module.exports = {
title: "Hello World",
items: ["Apple", "Banana", "Cherry"],
showFooter: true
};
</script>
stx supports defining your own custom directives for template processing:
import type { CustomDirective } from 'bun-plugin-stx'
// Configure custom directives
import stxPlugin from 'bun-plugin-stx'
// Create custom directives
const uppercaseDirective: CustomDirective = {
name: 'uppercase',
handler: (content, params) => {
return params[0] ? params[0].toUpperCase() : content.toUpperCase()
},
// No hasEndTag needed for single-parameter directives
}
const wrapDirective: CustomDirective = {
name: 'wrap',
handler: (content, params) => {
const className = params[0] || 'default-wrapper'
return `<div class="${className}">${content}</div>`
},
hasEndTag: true, // This directive requires an end tag (@wrap...@endwrap)
}
// Register custom directives
await build({
entrypoints: ['./src/index.ts', './templates/home.stx'],
outdir: './dist',
plugins: [stxPlugin],
stx: {
customDirectives: [uppercaseDirective, wrapDirective],
},
})
Then use them in your templates:
<!-- Single-parameter directive -->
<p>@uppercase('hello world')</p>
<!-- Block directive with content and optional parameter -->
@wrap(highlight)
<p>This content will be wrapped in a div with class "highlight"</p>
@endwrap
Custom directives have access to:
content: The content between start and end tags (for block directives)params: Array of parameters passed to the directivecontext: The template data context (all variables)filePath: The current template file pathDisplay content with double curly braces:
<h1>{{ title }}</h1>
<p>{{ user.name }}</p>
Use @if, @elseif, and @else for conditional rendering:
@if (user.isAdmin)
<div class="admin-panel">Admin content</div>
@elseif (user.isEditor)
<div class="editor-tools">Editor tools</div>
@else
<div class="user-view">Regular user view</div>
@endif
Iterate over arrays with @foreach:
<ul>
@foreach (items as item)
<li>{{ item }}</li>
@endforeach
</ul>
Use @for for numeric loops:
<ol>
@for (let i = 1; i <= 5; i++)
<li>Item {{ i }}</li>
@endfor
</ol>
Output unescaped HTML content:
{!! rawHtmlContent !!}
stx includes a built-in signals system for client-side reactivity. Define reactive state in your <script> block and bind it directly to your template using :class, :style, :text, and other directive bindings.
<script>
const count = state(0)
const doubled = derived(() => count() * 2)
effect(() => {
console.log('Count changed:', count())
})
function increment() {
count.set(count() + 1)
}
</script>
<button @click="increment()">Count: {{ count() }}</button>
<p>Doubled: {{ doubled() }}</p>
state(initialValue) β creates a reactive signal. Read with signal(), write with signal.set(value)derived(fn) β creates a computed value that auto-tracks dependencieseffect(fn) β runs a side effect whenever its tracked signals changebatch(fn) β batches multiple signal updates into a single re-render:class)Bind classes reactively using :class with object, array, or string syntax. The static class attribute is preserved β :class adds/removes classes on top of it.
Object syntax β toggle classes based on conditions:
<script>
const activeTab = state('home')
</script>
<button
class="tab-btn px-3 py-1.5 rounded text-sm font-medium"
:class="{ active: activeTab() === 'home' }"
@click="activeTab.set('home')"
>
Home
</button>
<button
class="tab-btn px-3 py-1.5 rounded text-sm font-medium"
:class="{ active: activeTab() === 'settings', disabled: !isAdmin() }"
@click="activeTab.set('settings')"
>
Settings
</button>
Array syntax β apply a list of classes:
<div :class="[baseClass(), isLarge() ? 'text-lg' : 'text-sm']">
Content
</div>
String syntax β bind a dynamic class string:
<div :class="isError() ? 'bg-red-500 text-white' : 'bg-zinc-800'">
Content
</div>
All three syntaxes are reactive β when the underlying signals change, the classes update automatically.
:style)Bind inline styles reactively:
<div :style="{ color: textColor(), fontSize: size() + 'px' }">
Styled content
</div>
:text, :html)<span :text="message()"></span>
<div :html="richContent()"></div>
:attr)Bind any HTML attribute reactively:
<input :disabled="isLoading()" :placeholder="hint()" />
<a :href="linkUrl()">Dynamic link</a>
@click, @submit, etc.)Bind event handlers directly in the template:
<button @click="count.set(count() + 1)">Increment</button>
<form @submit.prevent="handleSubmit()">...</form>
<input @keydown.enter="search()" />
Supported modifiers: .prevent, .stop, .self, .once, .capture, .passive, .ctrl, .alt, .shift, .meta, .enter, .escape, .space, .tab
@if, @show)<div @if="isLoggedIn()">Welcome back!</div>
<div @show="isVisible()">Toggles display</div>
@for)<ul>
<li @for="item in items()">{{ item.name }}</li>
</ul>
@model)<input @model="username" />
<p>Hello, {{ username() }}</p>
<script>
onMount(() => {
console.log('Component mounted')
fetchData()
})
onDestroy(() => {
console.log('Cleaning up')
})
</script>
stx provides 35+ utility composables for common browser tasks:
<script>
// Refs
const el = useRef('my-element')
// Events
useEventListener('click', handler, { target: '#my-btn' })
useClickOutside(el, () => closeDropdown())
// Storage
const theme = useLocalStorage('theme', 'dark')
// Routing
const route = useRoute()
const path = route.path
// Async data
const { data, isLoading, error } = useFetch('/api/data')
// Timing
const debouncedSearch = useDebounce(search, 300)
// Responsive
const { width } = useWindowSize()
const isMobile = useMediaQuery('(max-width: 768px)')
// State utilities
const [isDark, toggleDark] = useToggle(false)
const { count, inc, dec } = useCounter(0)
</script>
stx supports rendering Markdown content directly in your templates using the @markdown directive:
<div class="content">
@markdown
# Heading 1
This is a paragraph with **bold text** and *italic text*.
- List item 1
- List item 2
- List item 3
```js
// Code block
function hello() {
console.log('Hello world');
}
@endmarkdown
```You can also pass options to the markdown renderer:
<!-- Enable line breaks (converts single line breaks to <br>) -->
@markdown(breaks)
Line 1
Line 2
@endmarkdown
<!-- Disable GitHub Flavored Markdown -->
@markdown(no-gfm)
Content here
@endmarkdown
stx supports internationalization to help you build multilingual applications. Translation files are stored in YAML format (JSON also supported) and support nested keys and parameter replacements.
Configure i18n in your build script:
import stxPlugin from 'bun-plugin-stx'
await build({
entrypoints: ['./templates/home.stx'],
outdir: './dist',
plugins: [stxPlugin],
stx: {
i18n: {
locale: 'en', // Current locale
defaultLocale: 'en', // Fallback locale
translationsDir: 'translations', // Directory containing translations
format: 'yaml', // Format of translation files (yaml, yml, json, or js)
fallbackToKey: true, // Use key as fallback when translation not found
cache: true // Cache translations in memory
}
}
})
Create translation files in your translationsDir:
# translations/en.yaml
welcome: Welcome to stx
greeting: Hello, :name!
nav:
home: Home
about: About
contact: Contact
# translations/de.yaml
welcome: Willkommen bei stx
greeting: Hallo, :name!
nav:
home: Startseite
about: Γber uns
contact: Kontakt
stx provides multiple ways to use translations in your templates:
@translate Directive
<!-- Basic translation -->
<p>@translate('welcome')</p>
<!-- With parameters -->
<p>@translate('greeting', { "name": "John" })</p>
<!-- Nested keys -->
<p>@translate('nav.home')</p>
<!-- With fallback content -->
<p>@translate('missing.key')Fallback Content@endtranslate</p>
Filter Syntax
<!-- Basic translation as filter -->
<p>{{ 'welcome' | translate }}</p>
<!-- With parameters -->
<p>{{ 'greeting' | translate({ "name": "Alice" }) }}</p>
<!-- Short alias -->
<p>{{ 'nav.home' | t }}</p>
Parameters in translations use the :param syntax, similar to Laravel:
greeting: Hello, :name!
items: You have :count items in your cart.
Then in your template:
<p>@translate('greeting', { "name": "John" })</p>
<p>@translate('items', { "count": 5 })</p>
stx now provides seamless integration with Web Components, allowing you to automatically build and use custom elements from your stx components.
Enable web component integration in your build configuration:
import { build } from 'bun'
import stxPlugin from 'bun-plugin-stx'
await build({
entrypoints: ['./templates/home.stx'],
outdir: './dist',
plugins: [stxPlugin],
config: {
stx: {
webComponents: {
enabled: true,
outputDir: 'dist/web-components',
components: [
{
name: 'MyButton', // Class name for the component
tag: 'my-button', // HTML tag name (must contain a hyphen)
file: 'components/button.stx', // Path to the stx component
attributes: ['type', 'text', 'disabled'] // Observed attributes
},
{
name: 'MyCard',
tag: 'my-card',
file: 'components/card.stx',
shadowDOM: true, // Use Shadow DOM (default: true)
template: true, // Use template element (default: true)
styleSource: 'styles/card.css', // Optional external stylesheet
attributes: ['title', 'footer']
}
]
}
}
}
})
Include web components in your templates with the @webcomponent directive:
<!DOCTYPE html>
<html>
<head>
<title>Web Component Demo</title>
<!-- Include the web components -->
@webcomponent('my-button')
@webcomponent('my-card')
</head>
<body>
<h1>Web Components Demo</h1>
<!-- Use the custom elements -->
<my-button type="primary" text="Click Me"></my-button>
<my-card title="Card Title" footer="Card Footer">
This is the card content
</my-card>
</body>
</html>
The original stx components can be simple:
<!-- components/button.stx -->
<button class="btn {{ type ? 'btn-' + type : '' }}" {{ disabled ? 'disabled' : '' }}>
{{ text || slot }}
</button>
<!-- components/card.stx -->
<div class="card">
<div class="card-header">{{ title }}</div>
<div class="card-body">
{{ slot }}
</div>
<div class="card-footer">{{ footer }}</div>
</div>
Web components support several configuration options:
shadowDOM: Enable/disable Shadow DOM (default: true)template: Use template element for better performance (default: true)extends: Extend a specific HTML element classstyleSource: Path to external stylesheetattributes: List of attributes to observe for changesstx ships its TypeScript declarations as part of the package. As soon as
@stacksjs/stx is installed, TypeScript automatically picks up:
*.stx and *.md module declarations (so .stx imports typecheck)<script client> blocks: state,
derived, effect, batch, onMount, onDestroy, defineStore,
useStore, useHead, useSeoMeta, navigate, useRoute, etc.window.stx registry interfaceYou do not need to create your own stx.d.ts. Just include .stx
files in your tsconfig.json:
{
"compilerOptions": {
"types": ["bun"],
"moduleResolution": "bundler"
},
"include": ["**/*.ts", "**/*.stx"]
}
If you have an old stx.d.ts from a previous version declaring *.stx
or runtime globals, it's now redundant and can be deleted.
Run a development server with your stx templates:
// serve.ts
import home from './home.stx'
const server = Bun.serve({
routes: {
'/': home,
},
development: true,
fetch(req) {
return new Response('Not Found', { status: 404 })
},
})
console.log(`Listening on ${server.url}`)
To test the plugin with the included examples:
bun run test-build.ts
bun run serve-test.ts
http://localhost:3000).The plugin works by:
bun test
stx uses Headwind for utility-first CSS generation. Headwind is a blazingly fast CSS framework built with Bun that generates only the CSS you need.
# Build CSS from your .stx templates
bun run build:css
# The CSS will be generated at ./examples/dist/styles.css
Headwind is configured via headwind.config.ts:
import type { HeadwindConfig } from 'headwind'
import path from 'node:path'
const config: Partial<HeadwindConfig> = {
content: [
'./examples/**/*.stx',
'./examples/**/*.{html,js,ts,jsx,tsx}',
],
output: './examples/dist/styles.css',
minify: false,
}
export default config
stx templates support all Headwind/Tailwind-compatible utility classes:
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-md hover:shadow-lg">
<h1 class="text-2xl font-bold text-gray-900">Hello World</h1>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
Click Me
</button>
</div>
The build process automatically scans all your .stx files and generates only the CSS classes you actually use. For more information, visit the Headwind documentation.
Please see our releases page for more information on what has changed recently.
Please review the Contributing Guide for details.
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
You will always be free to use any of the Stacks OSS software. We would also love to see which parts of the world Stacks ends up in. Receiving postcards makes us happyβand we will publish them on our website.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States π
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
Many thanks to the following core technologies & people who have contributed to this package:
The MIT License (MIT). Please see LICENSE for more information.
Made with π
stx can automatically generate documentation for your components, templates, and directives. This helps developers understand your UI components and how to use them.
Generate documentation using the CLI:
# Generate markdown documentation (default)
stx docs
# Generate HTML documentation
stx docs --format html
# Generate JSON documentation
stx docs --format json
# Specify output directory
stx docs --output my-docs
# Only generate specific sections
stx docs --no-components
stx docs --no-templates
stx docs --no-directives
# Specify custom directories
stx docs --components-dir src/components --templates-dir src/views
You can configure documentation generation in your stx.config.ts file:
export default {
// ...other config options
docs: {
enabled: true,
outputDir: 'docs',
format: 'markdown', // 'markdown', 'html', or 'json'
components: true,
templates: true,
directives: true,
extraContent: '## Getting Started\n\nThis is additional content to include in the documentation.',
},
}
stx can extract component metadata from JSDoc comments in your component files:
<!--
Alert component for displaying messages to the user.
This component supports different types (success, warning, error).
-->
<div class="alert alert-{{ type }}">
<div class="alert-title">{{ title }}</div>
<div class="alert-body">{{ message }}</div>
</div>
<script>
/**
* The type of alert to display
* @type {string}
* @default "info"
*/
const type = module.exports.type || "info";
/**
* The alert title
* @type {string}
* @required
*/
const title = module.exports.title;
/**
* The alert message
* @type {string}
*/
const message = module.exports.message || "";
// Prepare the component's context
module.exports = {
type,
title,
message
};
</script>
This component will be documented with all its properties, types, default values, and requirements.
stx will automatically document web components defined in your configuration:
export default {
// ... other config
webComponents: {
enabled: true,
outputDir: 'dist/web-components',
components: [
{
name: 'MyButton',
tag: 'my-button',
file: 'components/button.stx',
attributes: ['type', 'text', 'disabled'],
description: 'A customizable button component'
}
]
}
}
The documentation will include:
This makes it easy for developers to understand how to use your web components in their HTML.
FAQs
A performant UI Framework. Powered by Bun.
The npm package @stacksjs/stx receives a total of 44,141 weekly downloads. As such, @stacksjs/stx popularity was classified as popular.
We found that @stacksjs/stx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.

Research
More than 140 Mastra npm packages were compromised in a supply chain attack that used a typosquatted dependency to deliver a cross-platform infostealer during installation.

Research
/Security News
A new npm package tests AI malware scanners with prompt injection, safety-triggering comments, context flooding, and obfuscated JavaScript.