Integrations
Integration sections — a card grid, a split layout, a marquee and an orbit diagram.
Preview
@props([
'eyebrow' => 'Integrations',
'heading' => 'Connect your entire stack',
'subheading' => 'One platform at the center of your tools. Sync data across the apps your team already uses — no glue code required.',
'brand' => 'Acme',
'innerNodes' => null,
'outerNodes' => null,
])
@php
$innerNodes ??= [
['label' => 'Slack', 'initials' => 'SL'],
['label' => 'Notion', 'initials' => 'NO'],
['label' => 'Linear', 'initials' => 'LI'],
['label' => 'GitHub', 'initials' => 'GH'],
];
$outerNodes ??= [
['label' => 'Stripe', 'initials' => 'ST'],
['label' => 'Figma', 'initials' => 'FI'],
['label' => 'Zapier', 'initials' => 'ZA'],
['label' => 'HubSpot', 'initials' => 'HS'],
['label' => 'Segment', 'initials' => 'SE'],
['label' => 'Vercel', 'initials' => 'VE'],
];
// Evenly space chips around a circle. Returns inline `left/top` percentages
// (position only — no colour — so the raw-colour guard is satisfied), centred
// with -translate-1/2. Radius is a % of the square container's half-size.
$placeOnRing = function (int $count, float $radius, float $startDeg = -90.0): array {
$out = [];
for ($k = 0; $k < $count; $k++) {
$rad = deg2rad($startDeg + $k * (360 / $count));
$out[] = sprintf('left:%.3f%%;top:%.3f%%', 50 + $radius * cos($rad), 50 + $radius * sin($rad));
}
return $out;
};
// Outer chips sit on the outer ring; inner chips offset by half a step so the
// two rings interleave rather than line up radially.
$outerCoords = $placeOnRing(count($outerNodes), 44);
$innerStep = count($innerNodes) > 0 ? 180 / count($innerNodes) : 0;
$innerCoords = $placeOnRing(count($innerNodes), 25, -90 + $innerStep);
@endphp
<section {{ $attributes->merge(['class' => 'relative isolate overflow-hidden bg-gradient-to-b from-primary/5 via-background to-background py-16 text-foreground sm:py-24']) }}>
<div class="mx-auto max-w-3xl px-4 text-center">
<x-ui.badge variant="secondary" class="mb-6">{{ __($eyebrow) }}</x-ui.badge>
<h2 class="text-balance text-3xl font-bold tracking-tight sm:text-4xl">
{{ __($heading) }}
</h2>
<p class="mx-auto mt-4 max-w-2xl text-pretty text-xl text-muted-foreground">
{{ __($subheading) }}
</p>
</div>
<div class="mt-16 flex items-center justify-center px-4">
<div class="relative aspect-square w-full max-w-md sm:max-w-lg">
{{-- Outer ring: dashed border + outer chips, orbiting slowly clockwise.
Each chip gets a nested, inversely-animated wrapper so the ring
rotates while the chip label stays upright. --}}
<div class="absolute inset-0 motion-safe:animate-[spin_60s_linear_infinite]" aria-hidden="true">
<div class="absolute inset-[6%] rounded-full border border-dashed border-border/70"></div>
</div>
{{-- Inner ring: solid border + inner chips, orbiting slowly counter-clockwise.
The direction keyword is folded into the single arbitrary `animate-[…]`
value (rather than a sibling `[animation-direction:reverse]` utility)
because Tailwind groups every `motion-safe:` utility into one trailing
@media block — a separate, unprefixed direction longhand would land
earlier in the stylesheet and be reset back to `normal` by the later
`animation` shorthand it shares an element with. --}}
<div class="absolute inset-0 motion-safe:animate-[spin_40s_linear_infinite_reverse]" aria-hidden="true">
<div class="absolute inset-[25%] rounded-full border border-border"></div>
</div>
{{-- Outer integration chips, evenly spaced on the outer ring --}}
<div class="absolute inset-0 motion-safe:animate-[spin_60s_linear_infinite]">
@foreach ($outerNodes as $i => $node)
<div class="absolute -translate-x-1/2 -translate-y-1/2" style="{{ $outerCoords[$i] }}">
<div class="motion-safe:animate-[spin_60s_linear_infinite_reverse]">
<x-ui.card class="flex size-12 items-center justify-center rounded-full p-0 shadow-sm sm:size-14">
<span class="text-xs font-semibold tracking-tight text-muted-foreground sm:text-sm">{{ $node['initials'] }}</span>
<span class="sr-only">{{ __($node['label']) }}</span>
</x-ui.card>
</div>
</div>
@endforeach
</div>
{{-- Inner integration chips, on the inner ring, orbiting the opposite direction --}}
<div class="absolute inset-0 motion-safe:animate-[spin_40s_linear_infinite_reverse]">
@foreach ($innerNodes as $i => $node)
<div class="absolute -translate-x-1/2 -translate-y-1/2" style="{{ $innerCoords[$i] }}">
<div class="motion-safe:animate-[spin_40s_linear_infinite]">
<x-ui.card class="flex size-11 items-center justify-center rounded-full p-0 shadow-sm sm:size-12">
<span class="text-xs font-semibold tracking-tight text-card-foreground">{{ $node['initials'] }}</span>
<span class="sr-only">{{ __($node['label']) }}</span>
</x-ui.card>
</div>
</div>
@endforeach
</div>
{{-- Central brand node --}}
<div class="absolute inset-0 m-auto flex size-20 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg ring-8 ring-background sm:size-24">
<span class="text-xl font-bold tracking-tight sm:text-2xl">{{ $brand }}</span>
</div>
</div>
</div>
</section>
Installation
php artisan ui:add blocks/integrations-orbit
Registry contract
Install confidence
php artisan ui:add blocks/integrations-orbit
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.1.1
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/integrations-orbit.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: Integrations Orbit (`integrations-orbit`)
A centered integrations diagram with a primary brand node ringed by two concentric orbits of integration logo chips and a slow CSS rotation.
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/integrations-orbit
```
## Render it
```blade
<x-blocks.integrations-orbit />
```
## Props
- `eyebrow` (string, default `Integrations`) — Small label shown above the heading inside a badge.
- `heading` (string, default `Connect your entire stack`) — Primary section heading.
- `subheading` (string, default `One platform at the center of your tools. Sync data across the apps your team already uses — no glue code required.`) — Supporting paragraph under the heading.
- `brand` (string, default `Acme`) — Label shown in the central brand node.
- `inner-nodes` (mixed|null, default `null`) — Array of {label, initials} chips placed on the inner orbit ring; falls back to a 4-item sample list when null.
- `outer-nodes` (mixed|null, default `null`) — Array of {label, initials} chips placed on the outer orbit ring; falls back to a 6-item sample list when null.
## Use when
- Use it when you want a visual 'hub and spoke' diagram with your brand as the central node ringed by two concentric orbits of integration logo chips.
- Reach for it as an illustrative centerpiece rather than a browsable or connectable list of integrations.
## Avoid when
- Avoid it when integrations need to be individually connectable or described; use integrations-grid or integrations-list instead.
## Rules
- Render the installed block with `<x-blocks.integrations-orbit />` 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/integrations-orbit
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/integrations-orbit.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
- Use it when you want a visual 'hub and spoke' diagram with your brand as the central node ringed by two concentric orbits of integration logo chips.
- Reach for it as an illustrative centerpiece rather than a browsable or connectable list of integrations.
Avoid when
- Avoid it when integrations need to be individually connectable or described; use integrations-grid or integrations-list instead.
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.integrations-orbit />
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([
'eyebrow' => 'Integrations',
'heading' => 'Connect your entire stack',
'subheading' => 'One platform at the center of your tools. Sync data across the apps your team already uses — no glue code required.',
'brand' => 'Acme',
'innerNodes' => null,
'outerNodes' => null,
])
@php
$innerNodes ??= [
['label' => 'Slack', 'initials' => 'SL'],
['label' => 'Notion', 'initials' => 'NO'],
['label' => 'Linear', 'initials' => 'LI'],
['label' => 'GitHub', 'initials' => 'GH'],
];
$outerNodes ??= [
['label' => 'Stripe', 'initials' => 'ST'],
['label' => 'Figma', 'initials' => 'FI'],
['label' => 'Zapier', 'initials' => 'ZA'],
['label' => 'HubSpot', 'initials' => 'HS'],
['label' => 'Segment', 'initials' => 'SE'],
['label' => 'Vercel', 'initials' => 'VE'],
];
// Evenly space chips around a circle. Returns inline `left/top` percentages
// (position only — no colour — so the raw-colour guard is satisfied), centred
// with -translate-1/2. Radius is a % of the square container's half-size.
$placeOnRing = function (int $count, float $radius, float $startDeg = -90.0): array {
$out = [];
for ($k = 0; $k < $count; $k++) {
$rad = deg2rad($startDeg + $k * (360 / $count));
$out[] = sprintf('left:%.3f%%;top:%.3f%%', 50 + $radius * cos($rad), 50 + $radius * sin($rad));
}
return $out;
};
// Outer chips sit on the outer ring; inner chips offset by half a step so the
// two rings interleave rather than line up radially.
$outerCoords = $placeOnRing(count($outerNodes), 44);
$innerStep = count($innerNodes) > 0 ? 180 / count($innerNodes) : 0;
$innerCoords = $placeOnRing(count($innerNodes), 25, -90 + $innerStep);
@endphp
<section {{ $attributes->merge(['class' => 'relative isolate overflow-hidden bg-gradient-to-b from-primary/5 via-background to-background py-16 text-foreground sm:py-24']) }}>
<div class="mx-auto max-w-3xl px-4 text-center">
<x-ui.badge variant="secondary" class="mb-6">{{ __($eyebrow) }}</x-ui.badge>
<h2 class="text-balance text-3xl font-bold tracking-tight sm:text-4xl">
{{ __($heading) }}
</h2>
<p class="mx-auto mt-4 max-w-2xl text-pretty text-xl text-muted-foreground">
{{ __($subheading) }}
</p>
</div>
<div class="mt-16 flex items-center justify-center px-4">
<div class="relative aspect-square w-full max-w-md sm:max-w-lg">
{{-- Outer ring: dashed border + outer chips, orbiting slowly clockwise.
Each chip gets a nested, inversely-animated wrapper so the ring
rotates while the chip label stays upright. --}}
<div class="absolute inset-0 motion-safe:animate-[spin_60s_linear_infinite]" aria-hidden="true">
<div class="absolute inset-[6%] rounded-full border border-dashed border-border/70"></div>
</div>
{{-- Inner ring: solid border + inner chips, orbiting slowly counter-clockwise.
The direction keyword is folded into the single arbitrary `animate-[…]`
value (rather than a sibling `[animation-direction:reverse]` utility)
because Tailwind groups every `motion-safe:` utility into one trailing
@media block — a separate, unprefixed direction longhand would land
earlier in the stylesheet and be reset back to `normal` by the later
`animation` shorthand it shares an element with. --}}
<div class="absolute inset-0 motion-safe:animate-[spin_40s_linear_infinite_reverse]" aria-hidden="true">
<div class="absolute inset-[25%] rounded-full border border-border"></div>
</div>
{{-- Outer integration chips, evenly spaced on the outer ring --}}
<div class="absolute inset-0 motion-safe:animate-[spin_60s_linear_infinite]">
@foreach ($outerNodes as $i => $node)
<div class="absolute -translate-x-1/2 -translate-y-1/2" style="{{ $outerCoords[$i] }}">
<div class="motion-safe:animate-[spin_60s_linear_infinite_reverse]">
<x-ui.card class="flex size-12 items-center justify-center rounded-full p-0 shadow-sm sm:size-14">
<span class="text-xs font-semibold tracking-tight text-muted-foreground sm:text-sm">{{ $node['initials'] }}</span>
<span class="sr-only">{{ __($node['label']) }}</span>
</x-ui.card>
</div>
</div>
@endforeach
</div>
{{-- Inner integration chips, on the inner ring, orbiting the opposite direction --}}
<div class="absolute inset-0 motion-safe:animate-[spin_40s_linear_infinite_reverse]">
@foreach ($innerNodes as $i => $node)
<div class="absolute -translate-x-1/2 -translate-y-1/2" style="{{ $innerCoords[$i] }}">
<div class="motion-safe:animate-[spin_40s_linear_infinite]">
<x-ui.card class="flex size-11 items-center justify-center rounded-full p-0 shadow-sm sm:size-12">
<span class="text-xs font-semibold tracking-tight text-card-foreground">{{ $node['initials'] }}</span>
<span class="sr-only">{{ __($node['label']) }}</span>
</x-ui.card>
</div>
</div>
@endforeach
</div>
{{-- Central brand node --}}
<div class="absolute inset-0 m-auto flex size-20 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg ring-8 ring-background sm:size-24">
<span class="text-xl font-bold tracking-tight sm:text-2xl">{{ $brand }}</span>
</div>
</div>
</div>
</section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.1 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0