Skip to content
Back to skills

Stacks Stx

ASecurity

Use when working with STX templates in a Stacks application - template syntax, components, directives, signals, reactivity, SSR, streaming, hydration, or debugging STX rendering. STX is the ONLY templating system for Stacks.

  • 631 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 5, 2026
developmenttypescriptgoshellbashreactvueexpresstestingdebugginggit

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add stacksjs/stacks --skill stacks-stx --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Stacks Stx?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Stacks Stx
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/stacksjs-stacks-stx/badge)](https://www.skillsdirectory.com/skills/stacksjs-stacks-stx)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: stacks-stx
description: Use when working with STX templates in a Stacks application - template syntax, components, directives, signals, reactivity, SSR, streaming, hydration, or debugging STX rendering. STX is the ONLY templating system for Stacks.
license: MIT
compatibility: Bun >= 1.3.0, TypeScript
allowed-tools: Read Edit Write Bash Grep Glob
---

# STX Templating Engine

STX is the full-stack templating and component framework for Stacks. It handles template rendering, reactivity, SSR, streaming, hydration, and more.

## Design & anti-slop skills

When the task is how a page should *look* (not just how stx renders), pair this skill with the design-taste family: `stacks-design-taste` (flagship), the aesthetic presets `stacks-design-soft` / `stacks-design-minimalist` / `stacks-design-brutalist`, `stacks-redesign`, `stacks-design-output`, and the image-first `stacks-image-to-code` / `stacks-imagegen-web` / `stacks-imagegen-mobile` / `stacks-brandkit`. They translate premium design rules into stx + Crosswind + composables.

## Key Paths
- STX config: `config/ui.ts`
- STX plugin: `bun-plugin-stx` (loaded via bunfig.toml)
- STX build cache + route manifest: `storage/framework/stx/` (stx's `stateDir`, set in `config/ui.ts`)
- Components: `resources/components/`
- Layouts: `resources/layouts/`
- Partials: `resources/partials/`
- Views: `resources/views/`
- Package: `@stacksjs/stx`

## CRITICAL Rules
1. **ALWAYS use STX** for templating — never write vanilla JS
2. **NEVER use** `var`, `document.*`, `window.*` in STX templates
3. STX `<script>` tags should ONLY contain stx-compatible code (signals, composables, directives)

### Pre-paint appearance

Use `@appearanceBootstrap({...})` when persisted appearance must be applied
before the browser parses the application shell. Do not add a raw inline
script for `localStorage`, `document`, or `matchMedia`. The directive emits the
synchronous compiler-owned guard, validates storage through explicit
allowlists, applies the root data attributes and `dark` class, and receives the
request CSP nonce when nonce support is enabled.

```stx
@appearanceBootstrap({
  storageKey: 'app-appearance',
  appearance: {
    key: 'sidebarStyle',
    attribute: 'appearance',
    allowed: ['macos', 'arc'],
    default: 'macos',
  },
  colorMode: {
    key: 'colorMode',
    attribute: 'color-mode',
    default: 'system',
  },
})
```

Persist a JSON object under `storageKey`. Invalid JSON, storage errors, unknown
appearance values, and unsupported color modes all fall back to the declared
defaults before first paint.

## Template Structure

```html
<script client>
const count = state(0)
const title = state('Hello STX')
const showDescription = state(true)

function increment(): void {
  count.update(value => value + 1)
}
</script>

<template>
  <div class="container">
    <h1>{{ title() }}</h1>
    <p :if="showDescription()">Rendered reactively</p>
    <button type="button" @click="increment()">Count: {{ count() }}</button>
  </div>
</template>

<style>
/* Use crosswind utility classes or custom CSS */
.container { max-width: 1200px; margin: 0 auto; }
</style>
```

STX signals are callable. Read with `count()`, replace with
`count.set(value)`, and update from the current value with
`count.update(value => value + 1)`. Do not use Vue-style `.value` access in
STX templates or `resources/functions`.

### Client loop conditionals

Attribute loops create a client-owned scope. Their item and index aliases may
be used with either reactive attribute conditionals or STX block conditionals.
The compiler promotes a block chain that reads those aliases into one reactive
runtime chain:

```html
<template :for="(field, index) in fields()">
  @if(field.editable)
    <input x-model="drafts[field.key]">
  @elseif(index === 0)
    <span>First read-only field</span>
  @else
    <span>{{ field.value }}</span>
  @endif
</template>
```

Loop aliases are scoped to the owning element and inherited by nested client
loops. A condition outside that element remains server-owned. Do not mix a
server context value into a client loop chain. Serialize the value into client
state when the browser must reevaluate it.

### Native form binding

Use `x-model` for two-way form state instead of pairing `:value` with a
manual `@input` or `@change` handler. The expression is the signal name
without parentheses:

```html
<script client>
const query = state('')
const quantity = state(1)
const enabled = state(false)
const categories = state<string[]>([])
</script>

<input x-model.trim="query" type="search">
<input x-model.number="quantity" type="number">
<input x-model="enabled" type="checkbox">
<input x-model="categories" type="checkbox" value="news">
<select x-model="query">
  <option value="">All</option>
  <option value="active">Active</option>
</select>
```

`x-model` supports text inputs, textareas, selects, radio buttons, boolean
checkboxes, and checkbox arrays. Use `.trim` when whitespace should be
removed and `.number` when the signal should receive a number. Keep a
dedicated signal for each binding. Arbitrary expressions such as
`x-model="user.name"` are not writable bindings.

## Configuration (config/ui.ts)

```typescript
import type { StxOptions } from '@stacksjs/stx'

export default {
  componentsDir: 'components',
  layoutsDir: 'layouts',
  partialsDir: 'partials',
} satisfies StxOptions
```

### Full StxConfig

```typescript
interface StxConfig {
  enabled: boolean
  debug: boolean
  componentsDir: string
  partialsDir: string
  layoutsDir?: string
  defaultLayout?: string
  templatesDir?: string
  cachePath: string
  ssr?: boolean
  cache?: boolean
  defaultTitle?: string
  defaultDescription?: string

  // Feature modules
  i18n?: Partial<I18nConfig>
  webComponents?: Partial<WebComponentConfig>
  streaming?: Partial<StreamingConfig>
  hydration?: Partial<HydrationConfig>
  a11y?: Partial<A11yConfig>
  seo?: Partial<SeoFeatureConfig>
  animation?: Partial<AnimationConfig>
  markdown?: Partial<MarkdownConfig>
  forms?: Partial<FormConfig>
  pwa?: Partial<PwaConfig>
  components?: Partial<ComponentConfig>
  media?: Partial<MediaConfig>
  strict?: boolean | StrictModeConfig
  customDirectives?: CustomDirective[]
}
```

## STX Capabilities (118+ modules)

### Core
- Template parsing and compilation
- Reactivity system (ref, computed, watch)
- Component composition and lifecycle
- Dependency injection (provide/inject)
- DOM update scheduling with the browser-auto-imported `nextTick()`

### Template refs after structural updates

Use `useRef()` with `nextTick()` when an element is inserted by `:if`, a modal, or another structural directive and must be focused or measured immediately afterward:

```ts
const open = state(false)
const searchInput = useRef('searchInput')

function showSearch(): void {
  open.set(true)
  void nextTick(() => searchInput.current?.focus())
}
```

```html
<template :if="open()">
  <input ref="searchInput" type="search">
</template>
```

`nextTick()` runs after the current synchronous signal and effect flush. Prefer it over `querySelector`, manual DOM polling, or `requestAnimationFrame` when the task is waiting for STX to materialize reactive markup.

### Rendering
- **SSR** — Server-Side Rendering
- **Streaming** — Progressive HTML streaming
- **Hydration** — Progressive and islands-based hydration
- **Suspense** — Async component loading with fallbacks
- **Error boundaries** — Graceful error handling in components

### Features
- **Router** — Client-side routing
- **Forms** — Built-in form handling and validation
- **i18n** — Internationalization support
- **SEO** — Meta tags, Open Graph, Twitter cards, structured data
- **PWA** — Progressive Web App support
- **Animation** — CSS and JS animation system
- **Markdown** — Markdown rendering with syntax highlighting
- **A11y** — Accessibility checking and auto-fixing

### SEO directives

Every argument is an expression evaluated against the template context (`<script server>` variables, the render context, and, inside an `@include` partial, the partial's own `<script server>`). A quoted string is literal text.

```html
<script server>
const seo = { title: post.title, description: post.excerpt, canonical: url, openGraph: { image: post.cover } }
</script>

@seo(seo)                              <!-- or @seo({ ...seo, title }), @seo(buildSeo(post)), @seo({ title: 'Literal' }) -->
@seo(page.seo ?? {})                   <!-- a variable only some pages define -->
@meta('author', post.author)           <!-- unquoted value: an expression -->
@meta('description', 'Plain text')     <!-- quoted value: literal, never looked up -->
@meta('keywords', tags)                <!-- an array joins with ", " -->
@meta('og:title')                      <!-- one argument: reads `title`, then `openGraph.title` -->
@metaTag({ httpEquiv: 'refresh', content: '30' })
@structuredData(product)               <!-- object or array of objects; @context defaults to schema.org -->
```

- `@seo` reads only `title`, `description`, `keywords`, `robots`, `canonical`, `openGraph`, `twitter`, `structuredData`. `image`, `url` and `type` are not top-level keys (use `openGraph.image`, `canonical`, `openGraph.type`); stray keys are ignored with a warning.
- A directive that cannot produce its tag (a variable that is not defined, a value of the wrong type, a syntax error, an unclosed call) leaves `<!-- [SEO Error ...] -->` in its place and logs `[stx] @seo(seo) in <file>: ...`. A defined variable holding `null` renders nothing, quietly. Read those warnings; they are the only sign a page shipped without its preview card.
- `@meta` in a page is staged into `<head>`. `@seo`, `@metaTag` and `@structuredData` write where they sit, so put them inside `<head>` (a layout or head partial). In a page body use `useSeoMeta({ title, description, ogImage })` in `<script server>`, which always reaches `<head>`.
- Requires `@stacksjs/stx` >= 0.2.343. Older versions only matched `@seo({ ... })` literally and sent unquoted `@meta` values such as `post.author` as text, without a warning.

### Dev Tools
- **Dev server** with HMR (Hot Module Replacement)
- **Image optimization**
- **Asset pipeline**
- **Testing utilities**

## Plugin Loading

```toml
# bunfig.toml
[serve]
plugins = ["bun-plugin-stx"]
```

The STX plugin processes `.stx` files during serve and build.

## Scaffolding

```typescript
import { createProject, addComponent, addPage, addStore, addLayout } from '@stacksjs/stx'

// Create a new project
await createProject('my-app', { template: 'dashboard' })

// Add to existing project
await addComponent('UserCard', { props: true, styles: true })
await addPage('about', { layout: 'default' })
await addStore('cart', { persist: true, actions: true })
await addLayout('admin', { nav: true, footer: true })
```

### Project Templates
`default`, `minimal`, `full`, `blog`, `dashboard`, `landing`

## Gotchas
- **STX is the ONLY templating system** — do not use other template engines
- **`bun-plugin-stx` must be loaded** — without it, `.stx` files won't be processed
- **Auto-imports** — browser auto-imports defined in `storage/framework/browser-auto-imports.json`
- **Imported module dependencies** - browser auto-imports are injected into the STX script entry only; imported `.ts` modules must explicitly import every function or store they use
- **Project-root server imports** - use `~/path` or `@/path` inside `<script server>` when a layout, partial, or page needs a project file. STX resolves both aliases against the application root before executing the server script. Relative imports still resolve against the `.stx` file
- **Browser package inputs are bundled** - core browser helpers and model auto-import bootstraps are compiler bundle inputs. Rendered HTML must never contain a bare `import '@stacksjs/browser'`, because browsers cannot resolve package specifiers without an import map
- **Server-to-client values are explicit** - a JSON-serializable top-level value exported by `<script server>` can be referenced by name in `<script client>`. STX serializes only referenced values and never overwrites a client-owned declaration
- **`storage/framework/stx/`** — stx build cache and the generated route manifest. `config/ui.ts` sets stx's `stateDir` here, so nothing lands in the project root. Gitignored; safe to delete
- **Reactivity is signal-based** - use callable `state()` and `derived()` signals, not Vue-style `ref()` or `.value`
- **Client loop conditions** - `@if` chains that read `:for` or `@for` aliases compile into scoped runtime chains; aliases do not leak outside the loop element
- **Component tag case is semantic** - `<Input v-model:value="query">` is a paired component, while lowercase `<input v-model="query">` is a native void element. Keep component tags PascalCase, including names that collide with native elements
- **Structural DOM timing** - use `nextTick()` with `useRef()` after opening reactive markup
- **Crosswind for styling** — use utility classes, not inline styles
- **Script block restrictions** — only stx-compatible code (signals, composables, directives), no vanilla DOM APIs
- **Pre-paint state** - use `@appearanceBootstrap`, never a raw browser script in the template
- **Components go in `resources/`** — not in `app/` or `storage/`
- **SEO directives take expressions** - `@seo(seo)`, `@meta('author', post.author)` and `@structuredData(product)` are evaluated; a failure shows up as an inline `<!-- [... Error] -->` comment plus a `[stx]` console warning naming the file, never as silent emptiness. Keep `@seo` inside `<head>`
- **118+ modules** — STX is a comprehensive framework covering rendering, routing, forms, i18n, SEO, PWA, and more

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.