Skip to content

Start building your next app or website — with AI, right in your browser.

Visit devdojo.com
Docs
Syntax Reference

Sites 13 / 13

Syntax Reference

Every Blade construct DevDojo Sites supports on top of plain HTML, with exact behavior — the one page to bookmark.

Syntax Reference

Everything DevDojo Sites adds on top of plain HTML, on one page — bookmark it, ⌘F it, ship it. Sites speaks a focused subset of Blade, Laravel's templating language: echoes put values on the page, directives shape structure, and <x-…> tags pull in components. Two ground rules before the details:

  • {{ }} escapes what it prints. Text and attribute values are HTML-escaped, so a stray < or & in your data can't break the page. Reach for {!! !!} only when you want raw HTML.
  • Undefined variables are build errors. If you echo {{ $subtitle }} and nothing named $subtitle is in scope, the build stops and tells you where. The fix is either to declare it (in @props, or as a collection) or to default it with ??. This strictness is a feature — a typo surfaces immediately instead of rendering blank.

Folder conventions

Your site mirrors a Laravel site:

Folder Purpose
resources/views/pages/ Your pages. pages/about.blade.php/about; index.blade.php/; folders nest (pages/blog/hi.blade.php/blog/hi). Dynamic: pages/post/[post.slug].blade.php serves /post/{slug} per post collection entry, $post bound to the match (details).
resources/views/components/layouts/ Page shells, used with <x-layouts.name>, holding {{ $slot }}.
resources/views/components/ Reusable components, used with <x-name> (folders become dots).
resources/data/site.json Global data, available everywhere as $site.
resources/data/collections/ JSON arrays — each name.json is bound as $name everywhere: pages, layouts, and components, exactly like $site.
resources/data/content/post/ Markdown items, bound as the $post collection.
resources/css/site.css Your Tailwind entry + custom CSS. @vite inlines it.
public/ Files served verbatim from your site root (public/robots.txt/robots.txt, public/images/logo.svg/images/logo.svg).

Every template file ends in .blade.php.

Echoing values

Syntax Behavior
{{ $expr }} Escaped output. HTML-special characters (& < > " ') become entities. Use this for all text and attribute values.
{!! $expr !!} Raw, unescaped output. Only for HTML you trust — like a Markdown item's rendered content.
{{-- comment --}} A Blade comment, removed entirely from the output.
<h1>{{ $site->name }}</h1>
<p>{{ $plan->name }} — priced at {{ $plan->price }}</p>
<article>{!! $item->content !!}</article>
{{-- This note never reaches the browser. --}}

HTML comments still run their tags. A Blade tag inside an ordinary <!-- … --> comment is still compiled<!-- {{ $secret }} --> prints the value. To actually hide a block (tags and all), wrap it in a Blade comment {{-- … --}}, which is removed before anything runs.

Control flow

The whole set — nothing else is available.

@if / @elseif / @else

@if ($plan->featured)
    <span class="badge">Popular</span>
@elseif ($plan->legacy)
    <span class="badge">Legacy</span>
@else
    <span class="badge">Standard</span>
@endif

Works at the top level of a page or inside a loop. Unlike simpler template tools, Blade gives you real @elseif and @else — no need to write a second inverted condition.

@foreach

@foreach ($features as $feature)
    <li>{{ $feature->title }}</li>
@endforeach

@foreach ($settings as $key => $value)
    <tr><td>{{ $key }}</td><td>{{ $value }}</td></tr>
@endforeach

Loops can be nested. Items render in the order they appear in the JSON file.

@break and @continue

Both take an optional condition. @break stops the loop; @continue skips to the next item. @break with a condition is the idiom for "show the first N," since the dialect has no method calls:

@foreach ($sites as $site)
    <article>{{ $site->title }}</article>
    @break($loop->iteration == 3)   {{-- render 3, then stop --}}
@endforeach

$loop inside a loop

Field What it is
$loop->index Position, starting at 0
$loop->iteration Position, starting at 1
$loop->first true on the first item
$loop->last true on the last item
$loop->count Total number of items
$loop->even / $loop->odd true on even / odd iterations

(That's the full set. remaining, depth, and parent — the field for reaching an outer loop from a nested one — need your site running as a full app; see the boundary below.)

Components

Components live under resources/views/components/ and are used with an <x-…> tag. Dots are folders: components/sections/hero.blade.php<x-sections.hero/>.

<x-nav/>                                    {{-- self-closing --}}
<x-sections.hero heading="Welcome"/>        {{-- with a prop --}}
<x-card>Body goes in the slot</x-card>      {{-- paired, with slot content --}}

@props — declaring inputs

@props(['heading' => 'Features', 'items'])
  • A 'key' => 'default' entry declares a prop with a fallback used when the tag omits it. Defaults are scalars (strings, numbers, true/false/null).
  • A bare 'items' entry declares a prop with no default — the form for a prop you always intend to pass in explicitly, such as a bound attribute carrying a different slice of data per instance (a collection read directly needs no @props entry named after it at all). You can't give an array a default here; use null and guard with @if.
  • Declared props become $variables inside the component. A component also sees the global $site and every collection under resources/data/collections/ by name, automatically — no prop, no bound attribute needed. A @props default can never override an already-present collection of that name; only a value the tag actually passes does.

String vs. bound attributes

On the tag Passes
heading="Our work" The literal string Our work.
:items="$sites" The evaluated expression — here, the whole $sites collection.

The : prefix means "this value is an expression, not a string." Use it to hand a component per-instance data — a different slice than the collection it could already read directly:

<x-sections.feature-grid heading="Why us" :items="$features"/>

Slots

  • Default slot — the tag's body, read as {{ $slot }} in the component.
  • Named slots — declare the name in @props, fill it with <x-slot:name>…</x-slot>:
<x-layouts.marketing title="Launch">
    <x-slot:hero><h1>We're live!</h1></x-slot>
    <p>Everything you need, today.</p>
</x-layouts.marketing>

The expression grammar

Everything inside {{ }}, an @if, a :bound attribute, or a @break condition is an expression. The supported pieces:

Piece Examples
Variables $site, $plan, $loop
Property access $plan->price, $site->name
Array access $items[0], $row['key']
Null-coalescing $site->tagline ?? 'Welcome'
Comparisons == != > < >= <=
Logic && (and) `
Literals 'text', "text", 42, 2.5, true, false, null

Comparisons follow PHP's rules: '1' == 1 is true, [] and '' and 0 are falsy. When a value might be missing, default it with ?? — this is also the sanctioned way to reference something optional, since ?? suppresses the undefined-variable error for its left side: {{ $site->phone ?? '' }} is always safe.

Data bindings

Source Available as Notes
resources/data/site.json $site A flat object, available everywhere — pages, layouts, and components.
resources/data/collections/plans.json $plans A JSON array, available everywhere — pages, layouts, and components — exactly like $site. A component's own @props default can't override it; only a passed attribute can. Order = the order in the file.
resources/data/content/post/*.md $post Markdown items, ordered by filename. Each carries its frontmatter plus content (rendered HTML) and link (its route).

Read a field with the arrow: {{ $plan->name }}, {{ $site->tagline }}. Loop a collection with @foreach. Render a Markdown item's body with {!! $item->content !!}.

@vite — Tailwind and your CSS

@vite('resources/css/site.css')

Put this in your layout's <head>. It renders the Tailwind CSS engine plus your resources/css/site.css inlined — so Tailwind utilities work everywhere and your custom styles ship. Start your CSS with @import "tailwindcss";. Behavior is identical in the preview, on your published site, and in ZIP exports. (Bundling JavaScript through @vite is an app-tier feature; for a static site, use plain <script> tags or a CDN.)

Escaping a literal @

Blade treats @word as a directive. To print a literal one — common in CDN URLs and email-in-code — double it:

<script src="https://unpkg.com/@@tailwindcss/browser"></script>
{{-- renders: https://unpkg.com/@tailwindcss/browser --}}

(An @ mid-word, like in a mailto: address, doesn't need escaping — only an @ at a word boundary.)

When something's off

Sites tells you exactly what's wrong, in one of two ways:

  • Build errors stop the page and point at the line: an undefined variable, a malformed expression, echoing a whole array, or a missing directive. The preview shows the message so you can fix it fast.
  • Missing components render an HTML comment in place — <!-- pocketknife:missing component "sections.hero" --> — rather than breaking the page. If a section seems to have silently vanished, check the preview's page source for one.

The app-tier boundary — your upgrade story

DevDojo Sites are static: they compile to plain HTML that serves from anywhere, with no server running your code. That's what makes them fast, cheap, and portable. The trade-off is a deliberate line: some perfectly valid Blade needs a running application behind it, so it's not part of the static subset.

These constructs are the boundary — writing one signals "this needs your site running as a full app" instead of producing output:

  • Function and method calls of any kind — count($items), route('home'), asset(...), $post->format(). No () calls at all.
  • Directives beyond the ones above@php, @auth, @guest, @include, @can, @error, @forelse, and the rest.
  • $attributes, the ?-> operator, string concatenation with ., arithmetic (+ - * /), ternaries (? : — use ?? instead), assignments, and closures.

None of this is a dead end — it's the dial from a static site to a full Laravel app. When your site genuinely needs authentication, form submissions, a database, or dynamic logic, that's the moment to upgrade it to an app, and this exact syntax starts working. Until then, build the static shell — precompute values into your data files, link with plain <a href>, and load assets with <link> / <script> / <img> tags.

A page that uses (almost) everything

<x-layouts.main title="Home" description="Everything, on one page.">

    <x-sections.hero heading="Hi there" :items="$features"/>

    <section class="mx-auto max-w-5xl py-20">
        @foreach ($features as $feature)
            <div>
                <span>#{{ $loop->iteration }}</span>
                <h3>{{ $feature->title }}</h3>
                @if ($feature->highlight)
                    <span>★ Featured</span>
                @endif
            </div>
            @break($loop->iteration == 3)
        @endforeach

        <a href="/about">About us</a>
    </section>

</x-layouts.main>
© 2026 DevDojo Edit this page