Directive Reference

On this page 81

Complete reference for all stx template directives. stx uses Laravel Blade-inspired syntax with @directive patterns.

Table of Contents


Expressions

Output with Escaping

{{ variable }}
{{ user.name }}
{{ formatDate(date) }}

Output is automatically HTML-escaped to prevent XSS attacks.

Raw Output (Unescaped)

{!! rawHtml !!}
{!! markdown(content) !!}

Warning: Only use with trusted content. Does not escape HTML.

Filters

{{ name | uppercase }}
{{ price | currency }}
{{ text | truncate(100) }}
{{ items | join(', ') }}

Available Filters: uppercase, lowercase, capitalize, escape, json, number, currency, date, truncate, join, first, last, length, reverse, slice, replace, stripTags, urlencode, abs, round, default, pluralize


Layout & Inheritance

@extends

Extend a parent layout template.

@extends('layouts/main')

@section('content')
  <h1>Page Content</h1>
@endsection

@section / @endsection

Define a section that can be yielded in layouts.

@section('sidebar')
  <nav>Sidebar content</nav>
@endsection

@yield

Output a section in a layout.

<main>
  @yield('content')
  @yield('sidebar', '<p>Default sidebar</p>')
</main>

@parent

Include parent section content when overriding.

@section('scripts')
  @parent
  <script src="page.js"></script>
@endsection

@push / @prepend

Add content to a stack.

@push('scripts')
  <script src="app.js"></script>
@endpush

@prepend('styles')
  <link rel="stylesheet" href="critical.css">
@endprepend

@stack

Output all pushed content.

<head>
  @stack('styles')
</head>
<body>
  @stack('scripts')
</body>

Conditionals

@if / @elseif / @else / @endif

@if(user.isAdmin)
  <span>Admin</span>
@elseif(user.isModerator)
  <span>Moderator</span>
@else
  <span>User</span>
@endif

@unless / @endunless

Inverse of @if.

@unless(user.isGuest)
  <a href="/dashboard">Dashboard</a>
@endunless

@switch / @case / @default / @endswitch

@switch(status)
  @case('pending')
    <span class="badge yellow">Pending</span>
  @case('approved')
    <span class="badge green">Approved</span>
  @default
    <span class="badge gray">Unknown</span>
@endswitch

@isset / @endisset

Check if variable exists and is not null.

@isset(user.avatar)
  <img src="{{ user.avatar }}" alt="Avatar">
@endisset

@empty / @endempty

Check if variable is empty.

@empty(notifications)
  <p>No notifications</p>
@endempty

@env / @endenv

Conditional by environment.

@env('local')
  <div class="debug-panel">Debug Info</div>
@endenv

@production / @development / @staging / @testing

Environment-specific shortcuts.

@production
  <script src="analytics.js"></script>
@endproduction

@development
  <script src="debug.js"></script>
@enddevelopment

Loops

@foreach / @endforeach

@foreach(users as user)
  <li>{{ user.name }}</li>
@endforeach

Loop Variables available inside @foreach:

VariableDescription
loop.indexZero-based index
loop.iterationOne-based count
loop.firstIs first iteration
loop.lastIs last iteration
loop.countTotal items
$loopAlias (avoids conflicts)
@foreach(items as item)
  <div class="{{ loop.first ? 'first' : '' }}">
    {{ loop.iteration }}. {{ item.name }}
  </div>
@endforeach

@forelse / @empty / @endforelse

Loop with empty state fallback.

@forelse(posts as post)
  <article>{{ post.title }}</article>
@empty
  <p>No posts found.</p>
@endforelse

@for / @endfor

Traditional for loop.

@for(let i = 0; i < 5; i++)
  <span>{{ i }}</span>
@endfor

@while / @endwhile

@while(items.length > 0)
  {{ items.pop() }}
@endwhile

@break / @continue

Control loop flow.

@foreach(users as user)
  @if(user.hidden)
    @continue
  @endif

  @if(count > 10)
    @break
  @endif

  <li>{{ user.name }}</li>
@endforeach

Conditional versions:

@foreach(users as user)
  @continue(user.hidden)
  @break(loop.index > 10)
  <li>{{ user.name }}</li>
@endforeach

Includes & Components

@include

Include a partial template.

@include('partials/header')
@include('partials/card', { title: 'Hello', content: 'World' })

@includeIf

Include only if file exists.

@includeIf('partials/optional-widget')

@includeWhen / @includeUnless

Conditional includes.

@includeWhen(user.isPremium, 'partials/premium-features')
@includeUnless(user.isGuest, 'partials/user-menu')

@includeFirst

Include first existing template from array.

@includeFirst(['custom/header', 'default/header'])
@includeFirst(['custom/widget', 'partials/widget'], {}, '<p>Fallback</p>')

@component

Include a component with props.

@component('components/alert', { type: 'warning', message: 'Be careful!' })

Component Tags

PascalCase or kebab-case component syntax.

<Alert type="error" message="Something went wrong" />
<user-card :user="currentUser" />

@slot / @endslot

Define slot content for components.

@component('components/modal')
  @slot('header')
    <h2>Modal Title</h2>
  @endslot

  <p>Modal body content</p>
@endcomponent

/

Render content only once per request (deduplication).

@once
  <link rel="stylesheet" href="component.css">
@endonce

Authentication & Authorization

Show content to authenticated users.

@auth
  <a href="/logout">Logout</a>
@endauth

Required Context:

{ auth: { check: true, user: { ... } } }

/

Show content to unauthenticated users.

@guest
  <a href="/login">Login</a>
@endguest

@can / @endcan

Check permission/ability.

@can('edit', post)
  <button>Edit Post</button>
@endcan

Required Context:

{
  permissions: {
    check: (ability: string, type?: string, id?: any) => boolean
  }
}

@cannot / @endcannot

Inverse permission check.

@cannot('delete', post)
  <span>You cannot delete this post</span>
@endcannot

@elsecan / @elsecannot

@can('admin')
  <button>Admin Panel</button>
@elsecan('moderator')
  <button>Mod Tools</button>
@endcan

Forms & Validation

Insert CSRF token.

<form method="POST">
  @csrf
  <!-- Creates: <input type="hidden" name="_token" value="..."> -->
</form>

@method

HTTP method spoofing.

<form method="POST" action="/posts/1">
  @method('PUT')
  @csrf
</form>

@form /

Form wrapper with automatic CSRF.

@form('POST', '/submit', { class: 'my-form' })
  <input name="email" type="email">
  <button type="submit">Submit</button>
@endform

@input

Form input with validation state.

@input('email', user.email, { type: 'email', placeholder: 'Email' })

@textarea

@textarea('bio', user.bio, { rows: 5 })

@select

@select('country', countries, user.country, { class: 'form-select' })

@checkbox / @radio

@checkbox('newsletter', true, { label: 'Subscribe' })
@radio('plan', 'premium', { label: 'Premium Plan' })

@error / @enderror

Display validation errors.

@error('email')
  <span class="error">{{ message }}</span>
@enderror

Internationalization (i18n)

@translate / @t

Translate text with optional parameters.

@translate('welcome')
@t('greeting', { name: user.name })

With pluralization:

@t('items_count', { count: items.length })

Translation file format:

{
  "items_count": "One item|{count} items"
}

SEO & Meta

Every SEO directive takes expressions, evaluated against the template's context (<script server> variables, the render context, and a partial's own <script server> when the directive sits in a partial). A directive that cannot produce its tag says so: an HTML comment where the tag would have been, such as <!-- [SEO Error [1101]]: @seo(seo): "seo" is not defined here... -->, and a [stx] console warning naming the file. It never renders nothing without a word.

: meta directive requires at least the meta name -->: meta directive requires at least the meta name -->

Generate a meta tag. The first argument is the name, the second the content.

@meta('description', 'Page description here')   <!-- quoted: literal text -->
@meta('author', author)                         <!-- a variable -->
@meta('og:title', post.title)                   <!-- any expression -->
@meta('keywords', tags)                         <!-- an array joins with ", " -->
@meta('og:site_name', `${site.name} Docs`)
@meta('og:title')                               <!-- one argument: reads `title`, then `openGraph.title` -->
  • A quoted value is always literal text. It is never looked up in the context, so @meta('description', 'title') writes the word "title".
  • An unquoted value is an expression. null, undefined, false and '' render no tag; a name the template does not have is reported (quote it if you meant the text).
  • The name may be quoted, a bare name (@meta(og:image, '/og.png')), or an expression (@meta(tag.name, tag.value)).
  • og:, article:, profile: and the other Open Graph namespaces are written as property; everything else, including twitter:, as name.

In a page, @meta tags are placed in <head> wherever the directive sits. In a partial they are written in place, which suits a head partial.

@metaTag

A meta tag with full control over its attributes. The argument is any expression that evaluates to { name?, property?, httpEquiv?, content }.

@metaTag({ httpEquiv: 'refresh', content: '30' })
@metaTag(themeColorTag)

@seo

Comprehensive SEO generation. The argument is any expression that evaluates to an object:

@seo({
  title: 'Page Title',
  description: 'Page description',
  keywords: ['keyword1', 'keyword2'],
  canonical: 'https://example.com/page',
  openGraph: {
    type: 'article',
    image: '/images/og.jpg'
  },
  twitter: {
    card: 'summary_large_image',
    site: '@username'
  },
  structuredData: { '@type': 'Article', headline: 'Page Title' }
})

@seo(seo)                              <!-- a variable from <script server> -->
@seo({ ...seo, title: post.title })    <!-- spread and override -->
@seo(buildSeo(post))                   <!-- a function call -->
@seo(page.seo ?? {})                   <!-- a variable only some pages define -->

@seo reads title, description, keywords, robots, canonical, openGraph, twitter and structuredData. Any other key is ignored with a warning that says where it belongs (image is openGraph.image, url is canonical or openGraph.url, type is openGraph.type). structuredData gets "@context": "https://schema.org" when it has none.

A variable that exists but holds null or undefined renders nothing. A variable that does not exist, or a value that is not an object, is reported.

@seo writes its tags where it sits, so put it inside <head>: in the layout, or in a head partial. At the top of a page body the tags land in <body>, where crawlers ignore them; use useSeoMeta() in <script server> there instead.

@structuredData

JSON-LD structured data. The argument is any expression that evaluates to an object, or an array of objects; @context defaults to https://schema.org.

@structuredData({
  '@type': 'Article',
  headline: article.title,
  author: {
    '@type': 'Person',
    name: article.author
  }
})

@structuredData(product)
@structuredData(buildSchema(post))

</script> inside a value is escaped, so data cannot close the block.


Security

Insert Content Security Policy meta tag.

<head>
  @csp
</head>

Requires CSP configuration in stx.config.ts:

export default {
  csp: {
    enabled: true,
    directives: {
      'default-src': ["'self'"],
      'script-src': ["'self'", "'strict-dynamic'"]
    }
  }
}

Output nonce for inline scripts/styles.

<script nonce="@cspNonce">
  console.log('Safe inline script');
</script>

Or with expression:

<script nonce="{{ cspNonce }}">
  // ...
</script>

Accessibility

Add accessibility hints as HTML comments.

@a11y('landmark', 'This section needs a heading')

/

Content visible only to screen readers.

@screenReader
  <span>Opens in new window</span>
@endscreenReader

@ariaDescribe

Connect elements with descriptions.

@ariaDescribe('password-help', 'Password must be at least 8 characters')
<input type="password" aria-describedby="password-help">

Animations

@transition directive requires at least a transition type
/

Add transition effects.

@transition('fade', 300)
  <div>Fading content</div>
@endtransition

@transition('slide', 500, 'ease-out')
  <div>Sliding content</div>
@endtransition

Transition Types: fade, slide, scale, flip, rotate

@animationGroup directive requires a group name and at least one element selector
/ @endanimationGroup

Group animations together.

@animationGroup('stagger')
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
@endanimationGroup

@scroll / @endscroll

Scroll-triggered animations.

@scroll
  <section class="animate-on-scroll">
    Content revealed on scroll
  </section>
@endscroll

Code Execution

Execute JavaScript server-side.

@js
  const greeting = `Hello, ${name}!`;
@endjs

<p>{{ greeting }}</p>

Execute TypeScript server-side.

@ts
  interface User { name: string; age: number }
  const user: User = { name: 'John', age: 30 };
@endts

Utility

@json

Output data as JSON.

<script>
  const config = @json(appConfig);
  const formatted = @json(data, true);
</script>

@route

Generate named route URLs.

<a href="@route('users.show', { id: user.id })">View Profile</a>

Comments & Escaping

Comments

{{-- This comment will not appear in HTML output --}}

Escape @ Symbol

@@if  <!-- Outputs: @if -->

Escape Expressions

@{{ variable }}  <!-- Outputs: {{ variable }} -->

Processing Order

Directives are processed in a specific order. Understanding this helps debug complex templates:

  1. Comments removal
  2. Escaped directives/expressions
  3. Stack directives (@push, @prepend)
  4. Layout resolution (@extends, @section, @yield)
  5. Code execution (@js, @ts)
  6. Custom directives
  7. Components
  8. Animations
  9. Authentication/Authorization
  10. Forms
  11. Includes
  12. Loops
  13. Conditionals
  14. i18n
  15. Accessibility
  16. SEO
  17. Security (CSP)
  18. Expressions ({{ }})
  19. Auto-injections (SEO tags, CSP meta)

Custom Directives

Register custom directives in configuration:

// stx.config.ts
export default {
  customDirectives: [
    {
      name: 'uppercase',
      handler: (content) => content.toUpperCase(),
      hasEndTag: true,
      description: 'Convert content to uppercase'
    },
    {
      name: 'datetime',
      handler: (_, params) => new Date(params[0]).toLocaleString(),
      hasEndTag: false
    }
  ]
}

Usage:

@uppercase
  this will be uppercase
@enduppercase

@datetime('2024-01-15')

See Also

Pre-paint appearance

Use @appearanceBootstrap({...}) when persisted appearance must be applied to the root element before the browser parses the application shell. The compiler emits a synchronous, CSP-aware script at the directive location. This avoids a light or layout flash without placing raw browser JavaScript in an stx template.

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

The storage value is a JSON object. The example reads sidebarStyle and colorMode, writes data-appearance and data-color-mode, toggles the root dark class, and writes data-theme. Stored values are accepted only when they match the configured appearance allowlist or one of light, dark, and system. Invalid or unavailable storage falls back to the declared defaults.