Sites 06 / 13
Components
Build a nav or a card once, use it anywhere, and edit it in one place forever.
Components
Creating a reusable piece of your site is as simple as creating a .blade.php file inside the resources/views/components/ directory. The nav, the footer, that call-to-action panel you're proud of — build it once, use it anywhere, and every copy stays in sync.
Create once, use everywhere
A component is just a Blade file in your components/ folder:
<!-- resources/views/components/nav.blade.php -->
<nav class="flex items-center justify-between px-6 py-4">
<a href="/" class="font-bold">My Site</a>
<a href="/about" class="text-zinc-500">About</a>
</nav>
Drop it into any page or layout with an <x-…> tag — the name after x- is the file (without .blade.php):
<x-nav/>
Folders become dots: components/sections/hero.blade.php is used as <x-sections.hero/>. Both the self-closing form (<x-nav/>) and the paired form (<x-nav>…</x-nav>) work — the paired form is how you pass a slot (more below).
Edit once, update everywhere ✨
This is the whole point of components. Your nav is used on ten pages? Change components/nav.blade.php once and all ten pages update instantly. No hunting, no copy-paste drift.
In Code mode, the components/ folder in the file tree is home base for your reusable pieces — create, open, and edit them there.
Props: same component, different content
Declare the attributes a component accepts with @props at the top of the file, then echo them as variables. Pass them on the tag:
<!-- resources/views/components/card.blade.php -->
@props(['title' => 'Untitled', 'body' => ''])
<div class="rounded-xl border p-6">
<h3 class="font-semibold">{{ $title }}</h3>
<p class="mt-2 text-sm text-zinc-500">{{ $body }}</p>
</div>
<!-- any page -->
<x-card title="Fast" body="No build step. Ever." />
<x-card title="Simple" body="Just HTML and Tailwind." />
One component, two cards, one place to change the design. A few details worth knowing:
@props(['title' => 'Untitled'])declares each prop with a default. The default is used when the tag omits that attribute — so a bare<x-card/>still renders. Defaults live right here, in@props.- Attribute values go in double quotes —
title="Fast". Need a literal quote inside? Write it as". - Every echoed variable must be declared in
@props, or be the global$site, or be a global collection. A{{ $subtitle }}you never declared is a build error — your cue to add it to@props. Give optional props an empty default:@props(['subtitle' => '']).
Components and data: collections, $site, and bound attributes
Components see three things automatically: their own declared props, the global $site, and every collection under resources/data/collections/ — by name, with zero wiring. That's what makes a nav component backed by resources/data/collections/links.json just work, no matter how deeply it's nested inside a layout: declare no prop for links at all (or a bare @props(['links' => null])) and read $links directly.
<!-- resources/views/components/nav.blade.php — no prop, no bound attribute -->
<nav class="flex items-center justify-between px-6 py-4">
@foreach ($links as $link)
<a href="{{ $link->url }}">{{ $link->text }}</a>
@endforeach
</nav>
A default can't win against a collection. A @props default can never override an already-present collection of the same name — only a value the tag actually passes wins. So a prop named after a collection is effectively reserved for it; give a component's own, purely-local prop a different name.
Reach for a bound attribute — an attribute with a : prefix, whose value is an expression instead of a literal string — only when you want per-instance data: the same component fed a different slice in different places, under a name that isn't the shared collection's own:
<!-- the page passes its own slice, the component receives it as `items` -->
<x-sections.feature-grid heading="Why teams pick us" :items="$features" />
<!-- resources/views/components/sections/feature-grid.blade.php -->
@props(['heading' => 'Features', 'items'])
<section class="mx-auto max-w-5xl py-20">
<h2 class="text-3xl font-semibold">{{ $heading }}</h2>
<ul class="mt-8 grid gap-6 sm:grid-cols-2">
@foreach ($items as $item)
<li class="rounded-xl border p-6">
<h3 class="font-medium">{{ $item->title }}</h3>
<p class="mt-2 text-zinc-600">{{ $item->description }}</p>
</li>
@endforeach
</ul>
</section>
heading="Why teams pick us"passes the literal stringWhy teams pick us.:items="$features"passes the value of$features(the collection) — the:means "evaluate this as an expression."
Declaring a prop you'll pass in. A prop that receives a collection is declared bare — @props(['items']), no default. You can't give an array a default in @props; if you want it optional, default it to null and guard with @if ($items).
Making sections editable: the companion .yml
Components become editable in the builder's section options panel automatically — but you control how:
- No
.yml— the inspector reads your@propsand shows a plain text field for each prop that has a string default. Great for headings and short copy, zero extra files. - With a companion
.yml(same name,.ymlextension) — you get typed fields: textareas, URL pickers, image uploaders, color swatches, and dropdowns, each with a friendly label.
# resources/views/components/sections/hero.yml
title: Split hero
description: Headline, supporting copy, and the primary action
fields:
heading:
type: text
label: Heading
body:
type: textarea
label: Supporting copy
button_text: { type: text, label: Button text }
button_link: { type: url, label: Button link }
image: { type: image, label: Image }
Field types: text, textarea (add rows:), url, image, select (add options: { value: Label }), color, number and range (both take min / max / step), and toggle (the value is "1" when on and empty when off — gate markup with @if ($prop)). Inside a repeater's sub_fields: there's one more: richtext — a full rich text editor (headings, bold, lists, links) over an HTML string, made for entry bodies like a blog post's content. And you rarely need to declare it per section: a collection-backed repeater inherits column types from the collection's own schema yml for anything its sub_fields: doesn't cover. Add required: true to mark a field, and description: for a hint under it. The yml declares which props are editable and how; the actual defaults live in the component's @props.
Anything list-like — menu items, FAQ rows, feature cards — is a repeater: declare sub_fields: (scalar types plus richtext), optionally nestable: true for one level of children (dropdown menus), plus add_button_label: and item_label: (the sub-field that titles each row). Repeater data never lands on the tag as a literal value — it's wired one of three ways, and the build checks that it's wired at all (an unwired repeater edits JSON nothing renders):
- Collection-backed — the default. Name the field after its collection: field
links↔resources/data/collections/links.json. The component reads the collection directly ($links) — no prop, no bound attribute, collections are global exactly like$site. Instances stay bare (<x-nav/>). Create the collection file with real content; a same-named@propsdefault never overrides it. - Per-instance — a bound attribute. The same component fed different data in different places:
:items="$faqs"editsresources/data/collections/faqs.json(see collections), matched by an@propsdefault for when nothing's bound. - Site-wide —
site.json-backed. For values that belong with the rest of the business data::links="$site->nav_links"edits thenav_linkskey ofresources/data/site.json, and the component reads$site->nav_linksdirectly.
Every section bound to the same data updates together.
Who wins? Editing a field in the options panel writes the value back onto that page's <x-…> tag as an attribute — it's that page's own copy. The component's @props defaults (and every other page using the section) stay untouched.
Editing a component on a page
Once a component is sitting on a page, changing its content on that page works in either face of the builder:
Click the section on the canvas. The inspector opens with a form — one field per editable prop (text fields from @props, or the typed fields from a companion .yml). Edit a field and the new value is written back onto that page's <x-…> tag.
Edit the attributes on the <x-…> tag itself:
<x-card title="Blazing fast" body="Still no build step." />
Change an attribute, and autosave takes care of the rest.
Components inside components
Components can use other components — a footer.blade.php that includes a <x-newsletter-form/>, for example. Nest away. If a component ever references itself in a loop, there's a generous recursion cap so a runaway include can't hang your page.
What if the file doesn't exist?
An <x-…> tag pointing at a missing component doesn't break your page — it renders an HTML comment like <!-- pocketknife:missing component "sections.hero" --> where the component would have been. If a component seems to be silently missing, check the page source in the preview for one of these.
Next up
Components handle repeated markup. For repeated content — team members, features, sites — you want Collections.