Pricing
Pricing sections — from a simple three-tier table (with an optional billing-period toggle) to gradient and elevated cards, tinted and horizontal layouts, and a tabbed switcher.
Preview
@props([
'heading' => 'Pricing built for the way you work',
'subheading' => 'Pick the segment that fits, then choose a plan. Switch anytime — no lock-in.',
'tabs' => null,
])
@php
// Fixed token-tint maps. NEVER splice a variable into a class — every value
// below is a whole, JIT-visible class string picked by key.
$tints = [
'primary' => 'bg-primary/10',
'accent' => 'bg-accent/10',
'secondary' => 'bg-secondary/20',
'muted' => 'bg-muted',
];
$blobs = [
'primary' => 'bg-primary/20',
'accent' => 'bg-accent/30',
'secondary' => 'bg-secondary/40',
'muted' => 'bg-muted-foreground/20',
];
$orbs = [
'primary' => 'bg-gradient-to-br from-primary to-primary/60 text-primary-foreground',
'accent' => 'bg-gradient-to-br from-accent to-accent/50 text-accent-foreground',
'secondary' => 'bg-gradient-to-br from-secondary to-secondary/60 text-secondary-foreground',
'foreground' => 'bg-gradient-to-br from-foreground to-foreground/70 text-background',
];
$tabs ??= [
'individuals' => [
'label' => 'Individuals & Teams',
'plans' => [
[
'name' => 'Solo',
'initial' => 'S',
'tint' => 'muted',
'blobA' => 'muted',
'blobB' => 'secondary',
'orb' => 'secondary',
'featured' => false,
'description' => 'For makers shipping their first project.',
'price' => '$0',
'originalPrice' => null,
'note' => 'Free forever — no card required.',
'cta' => 'Get started',
'href' => '#',
'features' => ['1 active project', 'Community support', 'Core components', '1 GB storage'],
'footnote' => 'Upgrade whenever you are ready.',
],
[
'name' => 'Pro',
'initial' => 'P',
'tint' => 'primary',
'blobA' => 'primary',
'blobB' => 'accent',
'orb' => 'primary',
'featured' => true,
'description' => 'For teams shipping production apps weekly.',
'price' => '$24',
'originalPrice' => '$29',
'note' => 'Per seat / month, billed annually.',
'cta' => 'Start free trial',
'href' => '#',
'features' => ['Unlimited projects', 'Priority support', 'All components & blocks', 'Advanced analytics', '100 GB storage'],
'footnote' => '14-day trial, cancel anytime.',
],
[
'name' => 'Studio',
'initial' => 'St',
'tint' => 'accent',
'blobA' => 'accent',
'blobB' => 'primary',
'orb' => 'accent',
'featured' => false,
'description' => 'For agencies juggling many clients.',
'price' => '$82',
'originalPrice' => '$99',
'note' => 'Per seat / month, billed annually.',
'cta' => 'Choose Studio',
'href' => '#',
'features' => ['Everything in Pro', 'Client workspaces', 'Brand theming', 'Shared component library', '1 TB storage'],
'footnote' => 'Volume discounts at 10+ seats.',
],
],
],
'enterprise' => [
'label' => 'Enterprise',
'plans' => [
[
'name' => 'Business',
'initial' => 'B',
'tint' => 'secondary',
'blobA' => 'secondary',
'blobB' => 'primary',
'orb' => 'secondary',
'featured' => false,
'description' => 'For scaling orgs that need governance.',
'price' => '$199',
'originalPrice' => '$249',
'note' => 'Per seat / month, billed annually.',
'cta' => 'Talk to sales',
'href' => '#',
'features' => ['SSO & SCIM', 'Role-based access', 'Audit logs', 'Usage reporting', '99.9% uptime SLA'],
'footnote' => 'Onboarding support included.',
],
[
'name' => 'Enterprise',
'initial' => 'E',
'tint' => 'primary',
'blobA' => 'primary',
'blobB' => 'accent',
'orb' => 'foreground',
'featured' => true,
'description' => 'For large teams with custom requirements.',
'price' => 'Custom',
'originalPrice' => null,
'note' => 'Tailored to your contract.',
'cta' => 'Request a quote',
'href' => '#',
'features' => ['Everything in Business', 'Dedicated success manager', 'Custom SLAs & DPA', 'On-prem / VPC options', 'Unlimited storage'],
'footnote' => 'Procurement & security review ready.',
],
],
],
];
$tabKeys = array_keys($tabs);
$firstTab = $tabKeys[0] ?? 'individuals';
@endphp
<section
data-slot="pricing-tabbed"
x-data="{
tab: @js($firstTab),
tabs: @js($tabKeys),
select(key) { this.tab = key },
move(dir) {
const i = this.tabs.indexOf(this.tab);
const next = (i + dir + this.tabs.length) % this.tabs.length;
this.tab = this.tabs[next];
this.$nextTick(() => this.$refs[this.tab]?.focus());
},
}"
{{ $attributes->merge(['class' => 'bg-background py-16 text-foreground sm:py-24']) }}
>
<div class="mx-auto max-w-6xl px-4">
<div class="mx-auto max-w-2xl text-center">
<h2 class="text-balance text-3xl font-bold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
<p class="mt-4 text-pretty text-lg text-muted-foreground">{{ __($subheading) }}</p>
{{-- Pill tab switcher --}}
<div
role="tablist"
aria-label="{{ __('Pricing audience') }}"
class="mx-auto mt-8 inline-flex items-center gap-2 rounded-full border border-border bg-muted/60 p-2 backdrop-blur"
@keydown.right.prevent="move(1)"
@keydown.left.prevent="move(-1)"
@keydown.home.prevent="select(tabs[0])"
@keydown.end.prevent="select(tabs[tabs.length - 1])"
>
@foreach ($tabs as $key => $segment)
<button
type="button"
role="tab"
:id="'tab-' + @js($key)"
x-ref="{{ $key }}"
:aria-selected="tab === @js($key) ? 'true' : 'false'"
:tabindex="tab === @js($key) ? 0 : -1"
:aria-controls="'panel-' + @js($key)"
@click="select(@js($key))"
class="rounded-full px-6 py-2 text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
:class="tab === @js($key)
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'"
>
{{ __($segment['label']) }}
</button>
@endforeach
</div>
</div>
@foreach ($tabs as $key => $segment)
@php $plans = $segment['plans']; $cols = count($plans); @endphp
<div
role="tabpanel"
:id="'panel-' + @js($key)"
:aria-labelledby="'tab-' + @js($key)"
x-show="tab === @js($key)"
x-cloak
@if (! $loop->first) tabindex="0" @endif
@class([
'mx-auto mt-12 grid max-w-md grid-cols-1 items-stretch gap-6 sm:max-w-none',
'lg:grid-cols-3' => $cols >= 3,
'lg:grid-cols-2 lg:max-w-4xl' => $cols === 2,
])
>
@foreach ($plans as $plan)
@php
$featured = $plan['featured'] ?? false;
$tintClass = $tints[$plan['tint'] ?? 'muted'] ?? $tints['muted'];
$blobAClass = $blobs[$plan['blobA'] ?? 'primary'] ?? $blobs['primary'];
$blobBClass = $blobs[$plan['blobB'] ?? 'accent'] ?? $blobs['accent'];
$orbClass = $orbs[$plan['orb'] ?? 'primary'] ?? $orbs['primary'];
@endphp
<x-ui.card @class([
'relative flex h-full flex-col overflow-hidden p-0',
'ring-2 ring-primary shadow-lg lg:scale-[1.02]' => $featured,
])>
{{-- Header panel: token tint backdrop + soft blurred blobs --}}
<div class="relative isolate overflow-hidden {{ $tintClass }} px-6 pb-6 pt-8">
<div aria-hidden="true" class="pointer-events-none absolute -start-10 -top-12 -z-10 size-40 rounded-full {{ $blobAClass }} blur-2xl"></div>
<div aria-hidden="true" class="pointer-events-none absolute -end-12 top-8 -z-10 size-32 rounded-full {{ $blobBClass }} blur-2xl"></div>
<div class="flex items-center gap-4">
<div class="flex size-12 shrink-0 items-center justify-center rounded-[30%] text-lg font-semibold shadow-sm {{ $orbClass }}">
<span aria-hidden="true">{{ $plan['initial'] ?? mb_substr($plan['name'], 0, 1) }}</span>
</div>
@if ($featured)
<x-ui.badge class="border-transparent bg-background/60 text-foreground shadow-sm backdrop-blur">
{{ __('Featured') }}
</x-ui.badge>
@endif
</div>
<h3 class="mt-4 text-lg font-semibold tracking-tight">{{ __($plan['name']) }}</h3>
<p class="mt-2 text-sm text-muted-foreground">{{ __($plan['description']) }}</p>
</div>
<x-ui.card.content class="flex flex-1 flex-col gap-6 p-6">
{{-- Price area --}}
<div>
<div class="flex items-baseline gap-2">
<span class="text-3xl font-bold tracking-tight">{{ $plan['price'] }}</span>
@if (! empty($plan['originalPrice']))
<del class="text-sm text-muted-foreground">{{ $plan['originalPrice'] }}</del>
@endif
</div>
@if (! empty($plan['note']))
<p class="mt-2 text-sm text-muted-foreground">{{ __($plan['note']) }}</p>
@endif
</div>
<x-ui.check-list class="flex-1">
@foreach ($plan['features'] as $feature)
<x-ui.check-list.item>{{ __($feature) }}</x-ui.check-list.item>
@endforeach
</x-ui.check-list>
<x-ui.button :href="$plan['href'] ?? '#'" :variant="$featured ? 'secondary' : 'outline'" class="w-full block">
{{ __($plan['cta']) }}
</x-ui.button>
@if (! empty($plan['footnote']))
<div>
<x-ui.separator />
<p class="mt-4 text-center text-sm text-muted-foreground">{{ __($plan['footnote']) }}</p>
</div>
@endif
</x-ui.card.content>
</x-ui.card>
@endforeach
</div>
@endforeach
</div>
</section>
Installation
php artisan ui:add blocks/pricing-tabbed
Registry contract
Install confidence
php artisan ui:add blocks/pricing-tabbed
writes only the generated targets below. The CLI validates each file hash before writing and
prompts before replacing local changes unless --force is used.
- Version
- 1.0.5
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/pricing-tabbed.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.
# Brok UI block: Pricing Tabbed (`pricing-tabbed`)
A tabbed pricing section with a pill switcher between audience segments, each showing plan cards with a blurred-blob header panel, a squircle avatar orb, strikethrough prices, a feature list, and a CTA.
Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.
## Install
```bash
php artisan ui:add blocks/pricing-tabbed
```
## Render it
```blade
<x-blocks.pricing-tabbed />
```
## Props
- `heading` (string, default `Pricing built for the way you work`) — Section headline.
- `subheading` (string, default `Pick the segment that fits, then choose a plan. Switch anytime — no lock-in.`) — Supporting copy under the heading.
- `tabs` (mixed|null, default `null`) — Keyed map of audience segments ({label, plans: [...]}); null falls back to sample Individuals/Enterprise segments.
## Use when
- Segmenting pricing by audience (e.g. individuals vs enterprise) behind a pill tab switcher with roving-tabindex keyboard navigation.
- Showing each plan with a blurred-blob header panel, a squircle avatar orb and a strikethrough original price.
## Avoid when
- Showing all plans at once without an audience split; use a non-tabbed pricing block such as pricing-cards or pricing-tinted.
## Rules
- Render the installed block with `<x-blocks.pricing-tabbed />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.
## Links
- Docs: https://brokui.dev/blocks/pricing-tabbed
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/pricing-tabbed.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Guidance
Use when
- Segmenting pricing by audience (e.g. individuals vs enterprise) behind a pill tab switcher with roving-tabindex keyboard navigation.
- Showing each plan with a blurred-blob header panel, a squircle avatar orb and a strikethrough original price.
Avoid when
- Showing all plans at once without an audience split; use a non-tabbed pricing block such as pricing-cards or pricing-tinted.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
- Anatomy
Usage
Render the block as a component, passing data where useful:
<x-blocks.pricing-tabbed />
Built from primitives
This block composes installed <brok:*> primitives and semantic design
tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by
editing CSS variables; flip the preview to RTL to confirm it mirrors.
Source
The exact, editable file ui:add writes
into your app. The preview above renders this same source — there are no preview-only blocks.
@props([
'heading' => 'Pricing built for the way you work',
'subheading' => 'Pick the segment that fits, then choose a plan. Switch anytime — no lock-in.',
'tabs' => null,
])
@php
// Fixed token-tint maps. NEVER splice a variable into a class — every value
// below is a whole, JIT-visible class string picked by key.
$tints = [
'primary' => 'bg-primary/10',
'accent' => 'bg-accent/10',
'secondary' => 'bg-secondary/20',
'muted' => 'bg-muted',
];
$blobs = [
'primary' => 'bg-primary/20',
'accent' => 'bg-accent/30',
'secondary' => 'bg-secondary/40',
'muted' => 'bg-muted-foreground/20',
];
$orbs = [
'primary' => 'bg-gradient-to-br from-primary to-primary/60 text-primary-foreground',
'accent' => 'bg-gradient-to-br from-accent to-accent/50 text-accent-foreground',
'secondary' => 'bg-gradient-to-br from-secondary to-secondary/60 text-secondary-foreground',
'foreground' => 'bg-gradient-to-br from-foreground to-foreground/70 text-background',
];
$tabs ??= [
'individuals' => [
'label' => 'Individuals & Teams',
'plans' => [
[
'name' => 'Solo',
'initial' => 'S',
'tint' => 'muted',
'blobA' => 'muted',
'blobB' => 'secondary',
'orb' => 'secondary',
'featured' => false,
'description' => 'For makers shipping their first project.',
'price' => '$0',
'originalPrice' => null,
'note' => 'Free forever — no card required.',
'cta' => 'Get started',
'href' => '#',
'features' => ['1 active project', 'Community support', 'Core components', '1 GB storage'],
'footnote' => 'Upgrade whenever you are ready.',
],
[
'name' => 'Pro',
'initial' => 'P',
'tint' => 'primary',
'blobA' => 'primary',
'blobB' => 'accent',
'orb' => 'primary',
'featured' => true,
'description' => 'For teams shipping production apps weekly.',
'price' => '$24',
'originalPrice' => '$29',
'note' => 'Per seat / month, billed annually.',
'cta' => 'Start free trial',
'href' => '#',
'features' => ['Unlimited projects', 'Priority support', 'All components & blocks', 'Advanced analytics', '100 GB storage'],
'footnote' => '14-day trial, cancel anytime.',
],
[
'name' => 'Studio',
'initial' => 'St',
'tint' => 'accent',
'blobA' => 'accent',
'blobB' => 'primary',
'orb' => 'accent',
'featured' => false,
'description' => 'For agencies juggling many clients.',
'price' => '$82',
'originalPrice' => '$99',
'note' => 'Per seat / month, billed annually.',
'cta' => 'Choose Studio',
'href' => '#',
'features' => ['Everything in Pro', 'Client workspaces', 'Brand theming', 'Shared component library', '1 TB storage'],
'footnote' => 'Volume discounts at 10+ seats.',
],
],
],
'enterprise' => [
'label' => 'Enterprise',
'plans' => [
[
'name' => 'Business',
'initial' => 'B',
'tint' => 'secondary',
'blobA' => 'secondary',
'blobB' => 'primary',
'orb' => 'secondary',
'featured' => false,
'description' => 'For scaling orgs that need governance.',
'price' => '$199',
'originalPrice' => '$249',
'note' => 'Per seat / month, billed annually.',
'cta' => 'Talk to sales',
'href' => '#',
'features' => ['SSO & SCIM', 'Role-based access', 'Audit logs', 'Usage reporting', '99.9% uptime SLA'],
'footnote' => 'Onboarding support included.',
],
[
'name' => 'Enterprise',
'initial' => 'E',
'tint' => 'primary',
'blobA' => 'primary',
'blobB' => 'accent',
'orb' => 'foreground',
'featured' => true,
'description' => 'For large teams with custom requirements.',
'price' => 'Custom',
'originalPrice' => null,
'note' => 'Tailored to your contract.',
'cta' => 'Request a quote',
'href' => '#',
'features' => ['Everything in Business', 'Dedicated success manager', 'Custom SLAs & DPA', 'On-prem / VPC options', 'Unlimited storage'],
'footnote' => 'Procurement & security review ready.',
],
],
],
];
$tabKeys = array_keys($tabs);
$firstTab = $tabKeys[0] ?? 'individuals';
@endphp
<section
data-slot="pricing-tabbed"
x-data="{
tab: @js($firstTab),
tabs: @js($tabKeys),
select(key) { this.tab = key },
move(dir) {
const i = this.tabs.indexOf(this.tab);
const next = (i + dir + this.tabs.length) % this.tabs.length;
this.tab = this.tabs[next];
this.$nextTick(() => this.$refs[this.tab]?.focus());
},
}"
{{ $attributes->merge(['class' => 'bg-background py-16 text-foreground sm:py-24']) }}
>
<div class="mx-auto max-w-6xl px-4">
<div class="mx-auto max-w-2xl text-center">
<h2 class="text-balance text-3xl font-bold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
<p class="mt-4 text-pretty text-lg text-muted-foreground">{{ __($subheading) }}</p>
{{-- Pill tab switcher --}}
<div
role="tablist"
aria-label="{{ __('Pricing audience') }}"
class="mx-auto mt-8 inline-flex items-center gap-2 rounded-full border border-border bg-muted/60 p-2 backdrop-blur"
@keydown.right.prevent="move(1)"
@keydown.left.prevent="move(-1)"
@keydown.home.prevent="select(tabs[0])"
@keydown.end.prevent="select(tabs[tabs.length - 1])"
>
@foreach ($tabs as $key => $segment)
<button
type="button"
role="tab"
:id="'tab-' + @js($key)"
x-ref="{{ $key }}"
:aria-selected="tab === @js($key) ? 'true' : 'false'"
:tabindex="tab === @js($key) ? 0 : -1"
:aria-controls="'panel-' + @js($key)"
@click="select(@js($key))"
class="rounded-full px-6 py-2 text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
:class="tab === @js($key)
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'"
>
{{ __($segment['label']) }}
</button>
@endforeach
</div>
</div>
@foreach ($tabs as $key => $segment)
@php $plans = $segment['plans']; $cols = count($plans); @endphp
<div
role="tabpanel"
:id="'panel-' + @js($key)"
:aria-labelledby="'tab-' + @js($key)"
x-show="tab === @js($key)"
x-cloak
@if (! $loop->first) tabindex="0" @endif
@class([
'mx-auto mt-12 grid max-w-md grid-cols-1 items-stretch gap-6 sm:max-w-none',
'lg:grid-cols-3' => $cols >= 3,
'lg:grid-cols-2 lg:max-w-4xl' => $cols === 2,
])
>
@foreach ($plans as $plan)
@php
$featured = $plan['featured'] ?? false;
$tintClass = $tints[$plan['tint'] ?? 'muted'] ?? $tints['muted'];
$blobAClass = $blobs[$plan['blobA'] ?? 'primary'] ?? $blobs['primary'];
$blobBClass = $blobs[$plan['blobB'] ?? 'accent'] ?? $blobs['accent'];
$orbClass = $orbs[$plan['orb'] ?? 'primary'] ?? $orbs['primary'];
@endphp
<x-ui.card @class([
'relative flex h-full flex-col overflow-hidden p-0',
'ring-2 ring-primary shadow-lg lg:scale-[1.02]' => $featured,
])>
{{-- Header panel: token tint backdrop + soft blurred blobs --}}
<div class="relative isolate overflow-hidden {{ $tintClass }} px-6 pb-6 pt-8">
<div aria-hidden="true" class="pointer-events-none absolute -start-10 -top-12 -z-10 size-40 rounded-full {{ $blobAClass }} blur-2xl"></div>
<div aria-hidden="true" class="pointer-events-none absolute -end-12 top-8 -z-10 size-32 rounded-full {{ $blobBClass }} blur-2xl"></div>
<div class="flex items-center gap-4">
<div class="flex size-12 shrink-0 items-center justify-center rounded-[30%] text-lg font-semibold shadow-sm {{ $orbClass }}">
<span aria-hidden="true">{{ $plan['initial'] ?? mb_substr($plan['name'], 0, 1) }}</span>
</div>
@if ($featured)
<x-ui.badge class="border-transparent bg-background/60 text-foreground shadow-sm backdrop-blur">
{{ __('Featured') }}
</x-ui.badge>
@endif
</div>
<h3 class="mt-4 text-lg font-semibold tracking-tight">{{ __($plan['name']) }}</h3>
<p class="mt-2 text-sm text-muted-foreground">{{ __($plan['description']) }}</p>
</div>
<x-ui.card.content class="flex flex-1 flex-col gap-6 p-6">
{{-- Price area --}}
<div>
<div class="flex items-baseline gap-2">
<span class="text-3xl font-bold tracking-tight">{{ $plan['price'] }}</span>
@if (! empty($plan['originalPrice']))
<del class="text-sm text-muted-foreground">{{ $plan['originalPrice'] }}</del>
@endif
</div>
@if (! empty($plan['note']))
<p class="mt-2 text-sm text-muted-foreground">{{ __($plan['note']) }}</p>
@endif
</div>
<x-ui.check-list class="flex-1">
@foreach ($plan['features'] as $feature)
<x-ui.check-list.item>{{ __($feature) }}</x-ui.check-list.item>
@endforeach
</x-ui.check-list>
<x-ui.button :href="$plan['href'] ?? '#'" :variant="$featured ? 'secondary' : 'outline'" class="w-full block">
{{ __($plan['cta']) }}
</x-ui.button>
@if (! empty($plan['footnote']))
<div>
<x-ui.separator />
<p class="mt-4 text-center text-sm text-muted-foreground">{{ __($plan['footnote']) }}</p>
</div>
@endif
</x-ui.card.content>
</x-ui.card>
@endforeach
</div>
@endforeach
</div>
</section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.5 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0