Skip to content

Components: using and overriding x-theme.*

How the x-theme.* component library works, the fixed overridable set, and how to ship your own copy of one so it wins.

7 min readUpdated Aug 13, 2026
On this page

Components: using and overriding x-theme.*#

Salieno Core ships a small library of anonymous Blade components under the theme.* namespace — you write <x-theme.card>, <x-theme.btn-primary>, and so on. Their job is to give every complete theme a working set of core surfaces (cards, buttons, the pricing switcher, the compare table) out of the box, and to let a theme replace any of them with its own design without touching Core.

This article explains where those components live, the fixed set your theme can override, exactly how an override wins, and when to skip the whole system and write plain markup instead.

What <x-theme.*> actually is#

The theme.* components are Core-provided anonymous components (Blade files, no PHP class) that live at:

code
resources/views/components/theme/

Because Laravel's default component finder maps resources/views/components/ to the un-namespaced component root, <x-theme.card> resolves to resources/views/components/theme/card.blade.php. That file is the Core base — a theme-neutral fallback. Open the directory in a fresh checkout and you will see them:

code
resources/views/components/theme/
  card.blade.php
  btn-primary.blade.php
  btn-secondary.blade.php
  compare/table.blade.php
  pricing/cycle-switcher.blade.php
  pricing/plan-card.blade.php
  ui/button.blade.php
  ui/logo.blade.php
  ui/tabs.blade.php

The base versions are deliberately inline-styled and token-driven — they use CSS custom properties (var(--card-bg, …), var(--btn, …)) rather than Tailwind utility classes. A Core fallback cannot assume the active theme's compiled stylesheet ships any particular utilities, so it styles itself with tokens that degrade to sensible defaults. That is why a theme which overrides nothing still renders every page acceptably.

The fixed overridable set#

A theme does not override components by dropping files wherever it likes. Core registers a fixed list of component names, and only those can be replaced. The list lives in app/Providers/AppServiceProvider.php:

Component nameBase file
theme.cardcomponents/theme/card.blade.php
theme.btn-primarycomponents/theme/btn-primary.blade.php
theme.btn-secondarycomponents/theme/btn-secondary.blade.php
theme.pricing.cycle-switchercomponents/theme/pricing/cycle-switcher.blade.php
theme.pricing.plan-cardcomponents/theme/pricing/plan-card.blade.php
theme.compare.tablecomponents/theme/compare/table.blade.php
theme.ui.logocomponents/theme/ui/logo.blade.php
theme.ui.buttoncomponents/theme/ui/button.blade.php
theme.ui.tabscomponents/theme/ui/tabs.blade.php

At boot, AppServiceProvider walks this list and, for each name, checks whether the active theme ships its own copy. If it does, that file is re-registered under the same component name:

php
// app/Providers/AppServiceProvider.php (shape of the registration)
$themeName = activeThemeName();

$overridableComponents = [
    'theme.pricing.cycle-switcher',
    'theme.pricing.plan-card',
    'theme.compare.table',
    'theme.ui.logo',
    'theme.ui.button',
    'theme.ui.tabs',
    'theme.btn-primary',
    'theme.btn-secondary',
    'theme.card',
];

foreach ($overridableComponents as $componentName) {
    $viewPath = "themes.{$themeName}.components." . str_replace('.', '/', $componentName);
    if (view()->exists($viewPath)) {
        Blade::component($viewPath, $componentName);
    }
}

Two consequences follow from this being an explicit, fixed loop:

  • You can only override the names in the list. Dropping a file for some new name (say components/theme/hero.blade.php) does nothing — Core never registers it, so <x-theme.hero> would only ever resolve to a non-existent Core base and error. Add new components under your own namespace instead (see below), or use plain partials.
  • Override or not, the call site is unchanged. A blade that writes <x-theme.card> gets your theme's card when you ship one, and the Core base when you do not. Page templates never branch on which theme is active.

Overriding a component — the worked example#

To replace a component, ship your own Blade file at the mirror path inside your theme:

code
resources/views/themes/{your-theme}/components/theme/card.blade.php

Core resolves that as the view themes.{your-theme}.components.theme.card, sees it exists, and registers it as theme.card. Your file now wins every <x-theme.card> in the app.

The one hard rule: accept the same props and slot as the base component, so existing call sites keep working. Read the base file's @props first. For card, the Core base declares:

blade
@props([
    'tag' => 'div',
    'padding' => 'p-6 sm:p-8',
    'hover' => false,
    'solid' => true,
])

So your override must honour tag, padding, hover, and solid, and render {{ $slot }}. Here is a clean, copy-pasteable override that keeps that contract but styles the card with your theme's own Tailwind utility classes (which live in your compiled theme.css):

blade
{{-- themes/{your-theme}/components/theme/card.blade.php --}}
@props([
    'tag'     => 'div',
    'padding' => 'p-6 sm:p-8',
    'hover'   => false,
    'solid'   => true,
])

@php
    // One surface, one hairline, one radius — vary the radius by scale, never the border.
    $surface = 'relative overflow-hidden rounded-lg border bg-white border-slate-200'
        . ' shadow-[0_1px_2px_rgba(16,24,40,0.04),0_12px_28px_-8px_rgba(16,24,40,0.10)]'
        . ' dark:border-white/[0.07] dark:shadow-[0_18px_40px_-12px_rgba(0,0,0,0.65)]';

    $surface .= $solid ? ' dark:bg-surface-mid' : ' dark:bg-transparent';

    $hoverCls = $hover
        ? ' transition-[transform,border-color] duration-300 hover:-translate-y-0.5'
        . ' hover:border-slate-300/80 dark:hover:border-white/[0.14]'
        : '';

    $classes = trim($surface . $hoverCls . ' ' . $padding);
@endphp

<{{ $tag }} {{ $attributes->merge(['class' => $classes]) }}>
    {{ $slot }}
</{{ $tag }}>

Notes on what makes this correct:

  • `$tag` lets one component be a `div`, `article`, `section`, or a link. The reference theme uses <x-theme.card tag="a" href="…" hover> for clickable cards, so honouring tag is not optional.
  • `{{ $attributes->merge([...]) }}` passes every extra attribute at the call site (class, href, x-data, id, ARIA) straight onto the root element. Merge your classes rather than overwrite class, so a caller's class="…" is additive.
  • This override compiles against Tailwind utilities, which is fine for a theme: your assets/css/theme.css ships those classes precompiled. The Core base avoids utilities precisely because it cannot rely on your stylesheet. See /theme-development/theme-assets for how compiled CSS is shipped and referenced.

Override the other eight the same way — copy the base file's @props, keep the slot, restyle the body. You do not have to override all of them; override the ones whose default look you want to change and leave the rest to the Core base.

You do not have to use x-theme.* at all#

The component library is a convenience, not a requirement. A theme is free to ignore <x-theme.*> entirely and write plain markup or its own @include partials:

blade
{{-- A theme's own partial — no x-theme.* in sight --}}
<article class="my-card">
    {{ $heading ?? '' }}
    <div class="my-card__body">…</div>
</article>

Two practical guidelines:

  • *Namespace your own components under your theme, not `theme..** Since Core only registers the fixed nine names, your bespoke components need a path Laravel's finder can reach. The simplest, override-proof approach is plain @include(activeTheme().'partials.my-card')` partials, which always resolve from your theme directory. (See how pages assemble partials in /theme-development/theme-pages and /theme-development/theme-sections.)
  • What you may not drop is the Livewire wiring. Cart, checkout, domain search, and the auth screens are Livewire components. However you render their markup — component, partial, or inline — the wire:model, wire:submit, and wire:click bindings must survive verbatim, or the interaction breaks. This is covered end-to-end in /theme-development/theme-auth.

Keeping Livewire bindings when a component wraps an input#

If you do wrap a form control in a component, the binding lives at the call site, not inside the component — so the component must forward it. Spread {{ $attributes }} onto the control and every wire:model/x-model/id passed in rides along:

blade
{{-- an input wrapper: attributes (incl. wire:model) pass through to the <input> --}}
@props(['label' => null, 'error' => null])

<div class="space-y-1.5">
    @if(filled($label))
        <label class="block text-sm font-medium">{{ $label }}</label>
    @endif

    <input {{ $attributes->merge(['class' => 'input']) }} />

    @if(filled($error))
        <p class="field-error">{{ $error }}</p>
    @endif
</div>

Called as <x-theme.ui.input label="Email" wire:model="email" type="email" />, the wire:model="email" lands on the <input> because it is part of $attributes. If you instead pin a fixed set of attributes yourself and drop the rest, the binding is silently lost and the field stops talking to Livewire. When in doubt, merge — do not enumerate.

Where to look in the reference theme#

The first-party salieno theme overrides all but one of the set — it ships its own copy of every overridable component except theme.ui.button, which falls through to the Core base. Read its copies to see production-grade versions of each, then simplify for your own design:

code
resources/views/themes/salieno/components/theme/
  card.blade.php
  btn-primary.blade.php   btn-secondary.blade.php
  pricing/cycle-switcher.blade.php   pricing/plan-card.blade.php
  compare/table.blade.php
  ui/logo.blade.php   ui/tabs.blade.php

Compare each against its Core base in resources/views/components/theme/ to see exactly which props the contract requires and how the reference restyles the body. For the full list of helpers these components lean on (themeAsset, activeTheme, themeMeta), see /theme-development/theme-helpers.

Next: /theme-development/theme-helpers — the complete theme helper reference.

componentsbladex-themelivewire
Was this article helpful?
Still stuck?Contact support
Components: using and overriding x-theme.* · Salieno Docs