Skip to content
Brok UI

Loading…

No results

Settings

Open source

Account settings screens — a grouped account form, a notification-preferences matrix and a connected-apps integrations list.

Preview

5 of 6

resources/views/blocks/settings-security.blade.php Blade
@props([
    'heading'          => 'Security',
    'subheading'       => 'Manage your password, two-factor authentication, and active sessions.',
    'action'           => '#',
    'twoFactorEnabled' => true,
    'sessions'         => null,
    'headingTag'       => 'h2',
])

@php
    $sessions ??= [
        ['device' => 'desktop', 'browser' => 'Chrome 124', 'os' => 'macOS 14', 'location' => 'Sydney, AU', 'current' => true, 'lastActive' => null],
        ['device' => 'mobile', 'browser' => 'Safari 17', 'os' => 'iOS 17', 'location' => 'Sydney, AU', 'current' => false, 'lastActive' => '2 hours ago'],
        ['device' => 'desktop', 'browser' => 'Firefox 125', 'os' => 'Windows 11', 'location' => 'Melbourne, AU', 'current' => false, 'lastActive' => '3 days ago'],
    ];
@endphp

<x-ui.section space="md" {{ $attributes }}>
    <div class="mx-auto max-w-3xl space-y-6 px-4">
        <div class="mb-2">
            <{{ $headingTag }} class="text-2xl font-bold tracking-tight">{{ __($heading) }}</{{ $headingTag }}>
            <p class="mt-2 text-sm text-muted-foreground">{{ __($subheading) }}</p>
        </div>

        <x-ui.card>
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Password') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('Use a long, random password that you do not use anywhere else.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content>
                <form method="POST" action="{{ $action }}" class="space-y-4">
                    @csrf
                    @method('PUT')
                    <x-ui.field>
                        <x-ui.label for="sec-current-password" required>{{ __('Current password') }}</x-ui.label>
                        <x-ui.input id="sec-current-password" type="password" name="current_password" autocomplete="current-password" required />
                    </x-ui.field>
                    <x-ui.field>
                        <x-ui.label for="sec-new-password" required>{{ __('New password') }}</x-ui.label>
                        <x-ui.input id="sec-new-password" type="password" name="password" autocomplete="new-password" required />
                    </x-ui.field>
                    <x-ui.field>
                        <x-ui.label for="sec-confirm-password" required>{{ __('Confirm new password') }}</x-ui.label>
                        <x-ui.input id="sec-confirm-password" type="password" name="password_confirmation" autocomplete="new-password" required />
                    </x-ui.field>
                    <div class="flex justify-end pt-2">
                        <x-ui.button type="submit">{{ __('Update password') }}</x-ui.button>
                    </div>
                </form>
            </x-ui.card.content>
        </x-ui.card>

        <x-ui.card>
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Two-factor authentication') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('Add an extra layer of security by requiring a one-time code on sign in.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content>
                <x-ui.switch-field
                    name="two_factor_enabled"
                    :label="__('Authenticator app')"
                    :description="$twoFactorEnabled ? __('Two-factor authentication is active on your account.') : __('Protect your account with an authenticator app.')"
                    :checked="$twoFactorEnabled"
                    align="center"
                >
                    <x-slot:badge>
                        <x-ui.badge variant="{{ $twoFactorEnabled ? 'success' : 'outline' }}">{{ $twoFactorEnabled ? __('Enabled') : __('Disabled') }}</x-ui.badge>
                    </x-slot:badge>
                </x-ui.switch-field>
            </x-ui.card.content>
        </x-ui.card>

        <x-ui.card>
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Active sessions') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('These devices are currently signed in. Revoke any you do not recognise.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content class="p-0">
                <ul role="list" class="divide-y divide-border">
                    @foreach ($sessions as $session)
                        <li class="flex items-center justify-between gap-4 px-6 py-4">
                            <div class="shrink-0 text-muted-foreground" aria-hidden="true">
                                @if ($session['device'] === 'mobile')
                                    <svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="2" width="14" height="20" rx="2" /><line x1="12" y1="18" x2="12.01" y2="18" /></svg>
                                @else
                                    <svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" /></svg>
                                @endif
                            </div>
                            <div class="min-w-0 flex-1 space-y-0.5">
                                <p class="text-sm font-medium text-foreground">{{ $session['browser'] }} &mdash; {{ $session['os'] }}</p>
                                <p class="text-xs text-muted-foreground">{{ $session['location'] }}</p>
                            </div>
                            <div class="flex shrink-0 items-center gap-4">
                                @if ($session['current'])
                                    <x-ui.badge variant="secondary" size="sm">{{ __('Current') }}</x-ui.badge>
                                @else
                                    <span class="text-xs text-muted-foreground">{{ __($session['lastActive']) }}</span>
                                    <x-ui.button type="button" variant="ghost" size="sm" class="text-destructive hover:bg-destructive/10 hover:text-destructive">{{ __('Revoke') }}</x-ui.button>
                                @endif
                            </div>
                        </li>
                    @endforeach
                </ul>
            </x-ui.card.content>
        </x-ui.card>

        <x-ui.card class="border-destructive/40">
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Danger zone') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('Permanent and irreversible actions for your account.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content>
                <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                    <div class="space-y-2">
                        <p class="text-sm font-medium text-foreground">{{ __('Delete account') }}</p>
                        <p class="text-sm text-muted-foreground">{{ __('Once you delete your account, all of your data will be permanently removed. This cannot be undone.') }}</p>
                    </div>
                    {{-- Hold-to-confirm: a slip-proof destructive action. A fill sweeps up
                         the button over 2s while pressed (pointer or Space/Enter held);
                         releasing early snaps it back. Completion dispatches a
                         `hold-confirm` event on the button — wire the real deletion there.
                         The bottom-up fill is axis-neutral, so it needs no RTL handling. --}}
                    <div
                        x-data="{
                            holding: false,
                            timer: null,
                            begin() {
                                if (this.timer) return;
                                this.holding = true;
                                this.timer = window.setTimeout(() => {
                                    this.release();
                                    this.$dispatch('hold-confirm');
                                }, 2000);
                            },
                            release() {
                                this.holding = false;
                                window.clearTimeout(this.timer);
                                this.timer = null;
                            },
                            destroy() { window.clearTimeout(this.timer) },
                        }"
                        class="shrink-0"
                    >
                        <x-ui.button
                            type="button"
                            variant="ghost"
                            class="relative w-full text-destructive select-none hover:bg-destructive/10 hover:text-destructive sm:w-auto"
                            aria-label="{{ __('Press and hold to delete your account') }}"
                            @pointerdown="begin()"
                            @pointerup="release()"
                            @pointerleave="release()"
                            @pointercancel="release()"
                            @keydown.enter.prevent="if (! $event.repeat) begin()"
                            @keydown.space.prevent="if (! $event.repeat) begin()"
                            @keyup.enter="release()"
                            @keyup.space="release()"
                        >
                            <span
                                aria-hidden="true"
                                data-slot="hold-confirm-fill"
                                class="pointer-events-none absolute inset-0 bg-destructive/15"
                                :style="holding
                                    ? 'clip-path: inset(0 0 0 0); transition: clip-path 2s linear'
                                    : 'clip-path: inset(100% 0 0 0); transition: clip-path 240ms var(--ease-standard)'"
                            ></span>
                            <span class="relative">{{ __('Hold to delete account') }}</span>
                        </x-ui.button>
                    </div>
                </div>
            </x-ui.card.content>
        </x-ui.card>
    </div>
</x-ui.section>

Installation

terminal
php artisan ui:add blocks/settings-security

Registry contract

Install confidence

php artisan ui:add blocks/settings-security 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.2.5
License
open
Stability
stable
Contract
v1
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/blocks/settings-security.blade.php

Registry dependencies

badge button card field input label section switch-field

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.

settings-security.md
# Brok UI block: Settings Security (`settings-security`)

A security settings screen — a password form, a two-factor toggle, an active-sessions list and a destructive danger zone.

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/settings-security
```

## Render it

```blade
<x-blocks.settings-security />
```

## Props

- `heading` (string, default `Security`) — Section heading.
- `subheading` (string, default `Manage your password, two-factor authentication, and active sessions.`) — Supporting copy under the heading.
- `action` (string, default `#`) — Password form submission endpoint.
- `two-factor-enabled` (bool, default `true`) — Initial state of the two-factor toggle.
- `sessions` (mixed|null, default `null`) — Active session rows with device, browser, location and last-active time; null falls back to sample data.
- `heading-tag` (string, default `h2`) — HTML heading tag rendered for the title.

## Use when

- Building a security settings screen with a password-change form, a two-factor toggle, an active-sessions list and a destructive danger zone.
- Dedicating a page to account security rather than general profile settings.

## Avoid when

- Managing general profile or notification settings; use settings-account or settings-notifications instead.

## Rules

- Render the installed block with `<x-blocks.settings-security />` 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/settings-security
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/settings-security.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

  • Building a security settings screen with a password-change form, a two-factor toggle, an active-sessions list and a destructive danger zone.
  • Dedicating a page to account security rather than general profile settings.

Avoid when

  • Managing general profile or notification settings; use settings-account or settings-notifications instead.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
root hold-confirm-fill

Usage

Render the block as a component, passing data where useful:

resources/views/welcome.blade.php Blade
<x-blocks.settings-security />

Built from primitives

Composes <brok:*> Semantic tokens Re-themeable RTL-ready

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.

resources/views/blocks/settings-security.blade.php Blade
@props([
    'heading'          => 'Security',
    'subheading'       => 'Manage your password, two-factor authentication, and active sessions.',
    'action'           => '#',
    'twoFactorEnabled' => true,
    'sessions'         => null,
    'headingTag'       => 'h2',
])

@php
    $sessions ??= [
        ['device' => 'desktop', 'browser' => 'Chrome 124', 'os' => 'macOS 14', 'location' => 'Sydney, AU', 'current' => true, 'lastActive' => null],
        ['device' => 'mobile', 'browser' => 'Safari 17', 'os' => 'iOS 17', 'location' => 'Sydney, AU', 'current' => false, 'lastActive' => '2 hours ago'],
        ['device' => 'desktop', 'browser' => 'Firefox 125', 'os' => 'Windows 11', 'location' => 'Melbourne, AU', 'current' => false, 'lastActive' => '3 days ago'],
    ];
@endphp

<x-ui.section space="md" {{ $attributes }}>
    <div class="mx-auto max-w-3xl space-y-6 px-4">
        <div class="mb-2">
            <{{ $headingTag }} class="text-2xl font-bold tracking-tight">{{ __($heading) }}</{{ $headingTag }}>
            <p class="mt-2 text-sm text-muted-foreground">{{ __($subheading) }}</p>
        </div>

        <x-ui.card>
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Password') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('Use a long, random password that you do not use anywhere else.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content>
                <form method="POST" action="{{ $action }}" class="space-y-4">
                    @csrf
                    @method('PUT')
                    <x-ui.field>
                        <x-ui.label for="sec-current-password" required>{{ __('Current password') }}</x-ui.label>
                        <x-ui.input id="sec-current-password" type="password" name="current_password" autocomplete="current-password" required />
                    </x-ui.field>
                    <x-ui.field>
                        <x-ui.label for="sec-new-password" required>{{ __('New password') }}</x-ui.label>
                        <x-ui.input id="sec-new-password" type="password" name="password" autocomplete="new-password" required />
                    </x-ui.field>
                    <x-ui.field>
                        <x-ui.label for="sec-confirm-password" required>{{ __('Confirm new password') }}</x-ui.label>
                        <x-ui.input id="sec-confirm-password" type="password" name="password_confirmation" autocomplete="new-password" required />
                    </x-ui.field>
                    <div class="flex justify-end pt-2">
                        <x-ui.button type="submit">{{ __('Update password') }}</x-ui.button>
                    </div>
                </form>
            </x-ui.card.content>
        </x-ui.card>

        <x-ui.card>
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Two-factor authentication') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('Add an extra layer of security by requiring a one-time code on sign in.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content>
                <x-ui.switch-field
                    name="two_factor_enabled"
                    :label="__('Authenticator app')"
                    :description="$twoFactorEnabled ? __('Two-factor authentication is active on your account.') : __('Protect your account with an authenticator app.')"
                    :checked="$twoFactorEnabled"
                    align="center"
                >
                    <x-slot:badge>
                        <x-ui.badge variant="{{ $twoFactorEnabled ? 'success' : 'outline' }}">{{ $twoFactorEnabled ? __('Enabled') : __('Disabled') }}</x-ui.badge>
                    </x-slot:badge>
                </x-ui.switch-field>
            </x-ui.card.content>
        </x-ui.card>

        <x-ui.card>
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Active sessions') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('These devices are currently signed in. Revoke any you do not recognise.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content class="p-0">
                <ul role="list" class="divide-y divide-border">
                    @foreach ($sessions as $session)
                        <li class="flex items-center justify-between gap-4 px-6 py-4">
                            <div class="shrink-0 text-muted-foreground" aria-hidden="true">
                                @if ($session['device'] === 'mobile')
                                    <svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="2" width="14" height="20" rx="2" /><line x1="12" y1="18" x2="12.01" y2="18" /></svg>
                                @else
                                    <svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" /></svg>
                                @endif
                            </div>
                            <div class="min-w-0 flex-1 space-y-0.5">
                                <p class="text-sm font-medium text-foreground">{{ $session['browser'] }} &mdash; {{ $session['os'] }}</p>
                                <p class="text-xs text-muted-foreground">{{ $session['location'] }}</p>
                            </div>
                            <div class="flex shrink-0 items-center gap-4">
                                @if ($session['current'])
                                    <x-ui.badge variant="secondary" size="sm">{{ __('Current') }}</x-ui.badge>
                                @else
                                    <span class="text-xs text-muted-foreground">{{ __($session['lastActive']) }}</span>
                                    <x-ui.button type="button" variant="ghost" size="sm" class="text-destructive hover:bg-destructive/10 hover:text-destructive">{{ __('Revoke') }}</x-ui.button>
                                @endif
                            </div>
                        </li>
                    @endforeach
                </ul>
            </x-ui.card.content>
        </x-ui.card>

        <x-ui.card class="border-destructive/40">
            <x-ui.card.header>
                <x-ui.card.title>{{ __('Danger zone') }}</x-ui.card.title>
                <x-ui.card.description>{{ __('Permanent and irreversible actions for your account.') }}</x-ui.card.description>
            </x-ui.card.header>
            <x-ui.card.content>
                <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                    <div class="space-y-2">
                        <p class="text-sm font-medium text-foreground">{{ __('Delete account') }}</p>
                        <p class="text-sm text-muted-foreground">{{ __('Once you delete your account, all of your data will be permanently removed. This cannot be undone.') }}</p>
                    </div>
                    {{-- Hold-to-confirm: a slip-proof destructive action. A fill sweeps up
                         the button over 2s while pressed (pointer or Space/Enter held);
                         releasing early snaps it back. Completion dispatches a
                         `hold-confirm` event on the button — wire the real deletion there.
                         The bottom-up fill is axis-neutral, so it needs no RTL handling. --}}
                    <div
                        x-data="{
                            holding: false,
                            timer: null,
                            begin() {
                                if (this.timer) return;
                                this.holding = true;
                                this.timer = window.setTimeout(() => {
                                    this.release();
                                    this.$dispatch('hold-confirm');
                                }, 2000);
                            },
                            release() {
                                this.holding = false;
                                window.clearTimeout(this.timer);
                                this.timer = null;
                            },
                            destroy() { window.clearTimeout(this.timer) },
                        }"
                        class="shrink-0"
                    >
                        <x-ui.button
                            type="button"
                            variant="ghost"
                            class="relative w-full text-destructive select-none hover:bg-destructive/10 hover:text-destructive sm:w-auto"
                            aria-label="{{ __('Press and hold to delete your account') }}"
                            @pointerdown="begin()"
                            @pointerup="release()"
                            @pointerleave="release()"
                            @pointercancel="release()"
                            @keydown.enter.prevent="if (! $event.repeat) begin()"
                            @keydown.space.prevent="if (! $event.repeat) begin()"
                            @keyup.enter="release()"
                            @keyup.space="release()"
                        >
                            <span
                                aria-hidden="true"
                                data-slot="hold-confirm-fill"
                                class="pointer-events-none absolute inset-0 bg-destructive/15"
                                :style="holding
                                    ? 'clip-path: inset(0 0 0 0); transition: clip-path 2s linear'
                                    : 'clip-path: inset(100% 0 0 0); transition: clip-path 240ms var(--ease-standard)'"
                            ></span>
                            <span class="relative">{{ __('Hold to delete account') }}</span>
                        </x-ui.button>
                    </div>
                </div>
            </x-ui.card.content>
        </x-ui.card>
    </div>
</x-ui.section>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.2.5
Status
Stable
License
open
Deprecation
Not deprecated
Contract
v1
Foundation
≥ 1.0.0