Handles read-only record detail views in Kinetix: entry types (TextEntry, IconEntry, ImageEntry, ColorEntry), layout components (Section, Grid), server-side state resolution, badges, formatting, and conditional visibility. Activates when building or rendering record 'view' / detail pages.
php artisan boost:add-skill happones/kinetix
Save this content to: .claude/skills/kinetix-infolists/SKILL.md
# Kinetix Infolists Development
## When to Apply
Activate this skill when:
- Building a read-only "View" / "Show" page for a record or resource.
- Displaying record details without editing (the display-only twin of Forms).
- Adding entries like `TextEntry`, `IconEntry`, `ImageEntry`, or `ColorEntry`.
- Grouping details with `Section::make()`, `Grid::make()`, `Fieldset::make()`, or `Tabs::make()->tabs([Tab::make(...)])`. The open tab is kept in `?tab=` by default (same rules and config as form tabs: `Tab::key()`, `Tabs::persistInQueryString('name'|false)`, `kinetix.forms.query_string.tabs`).
- Formatting values for display (`badge()`, `date()`, `money()`, `limit()`, `copyable()`). `date()`/`dateTime()` with no argument localize to the app locale (Carbon `isoFormat`, tokens from `config('kinetix.formats')`); `isoDate()`/`isoDateTime()` take explicit tokens; `->locale()` overrides. `money()` formats via intl in the app locale (`$divideBy` for cents).
- Resolving values with `state()` / `formatStateUsing()` callbacks or conditional visibility.
## Documentation
For full details, reference the [Kinetix Infolists Documentation](https://happones.github.io/kinetix/infolists).
## Localizing labels
Any display string you set is **your app's copy** — wrap it in Laravel's `__()` so
it's translatable: entry `->label()`, section/tab headings, `->placeholder()`
(`TextEntry::make('title')->label(__('posts.fields.title'))`). Entries with no
explicit `->label()` are auto-humanized. See the **kinetix-locale** skill.
## Usage Guide
### 1. Infolist Schema Definition
```php
use Happones\Kinetix\Infolists\Infolist;
use Happones\Kinetix\Infolists\Components\Section;
use Happones\Kinetix\Infolists\Components\TextEntry;
use Happones\Kinetix\Infolists\Components\IconEntry;
use App\Models\User;
$infolist = Infolist::make($user)
->schema([
Section::make('Account')
->icon('user')
->columns(12)
->schema([
TextEntry::make('name')->columnSpan(6),
TextEntry::make('email')->icon('mail')->copyable()->columnSpan(6),
TextEntry::make('status')->badge()
->color(fn ($state) => $state === 'active' ? 'success' : 'gray')
->columnSpan(4),
IconEntry::make('is_verified')->boolean()->columnSpan(4),
TextEntry::make('created_at')->dateTime()->columnSpan(4),
]),
]);
```
### 2. Serialization & Rendering
```php
public function show(User $user)
{
return inertia('Users/Show', [
'infolist' => UserResource::infolist(Infolist::make($user))->toArray(),
]);
}
```
```vue
<KinetixInfolist :infolist="infolist" />
```
---
## Best Practices
- **Server-side state**: Infolists resolve and format every value in PHP via `getState()`. Never recompute display values in Vue — the frontend renders pre-formatted state only.
- **Enum contracts**: Lean on `HasLabel`, `HasColor`, and `HasIcon` enum contracts so one Enum drives the label, badge color, and icon consistently across Tables and Infolists.
- **Relationship attributes**: Use dot-notation (`TextEntry::make('company.name')`) to read related models without a manual `state()` callback.
- **Conditional closures**: Use `->visible(fn ($record) => ...)` and `->color(fn ($state) => ...)` to keep detail views dynamic; hidden entries are stripped before serialization.
- **Role/permission-gated entries**: use `->authorize(string $ability, mixed $subject = null)` (Gate-based, same shorthand as `Action::authorize()`/Forms) alongside `->visible()`/`->hidden()` for sensitive fields (e.g. `TextEntry::make('salary')->authorize('viewFinancials')`). Unauthorized entries are omitted from the serialized payload entirely.
- **Read-only by design**: Do not add inputs, validation, or two-way bindings to infolists — use Kinetix Forms for editable schemas.
## Responsive Layout
Infolist grids use the same responsive system as forms: `columns(int)` means
"N columns from `lg` up, ONE below" (never assume a fixed column count on
mobile), and `columns()` / `columnSpan()` accept breakpoint maps
(`['default' => 1, 'sm' => 2, 'xl' => 3]`, keys default/sm/md/lg/xl/2xl) on
Infolist/Section/Fieldset/Grid/Tab. Breakpoints measure the INFOLIST's own
width (container queries); spans clamp to the available columns per breakpoint.
Activate this skill when:
TextEntry, IconEntry, ImageEntry, or ColorEntry.Section::make(), Grid::make(), Fieldset::make(), or Tabs::make()->tabs([Tab::make(...)]). The open tab is kept in ?tab= by default (same rules and config as form tabs: Tab::key(), Tabs::persistInQueryString('name'|false), kinetix.forms.query_string.tabs).badge(), date(), money(), limit(), copyable()). date()/dateTime() with no argument localize to the app locale (Carbon isoFormat, tokens from config('kinetix.formats')); isoDate()/isoDateTime() take explicit tokens; ->locale() overrides. money() formats via intl in the app locale ($divideBy for cents).state() / formatStateUsing() callbacks or conditional visibility.For full details, reference the Kinetix Infolists Documentation.
Any display string you set is your app's copy — wrap it in Laravel's __() so
it's translatable: entry ->label(), section/tab headings, ->placeholder()
(TextEntry::make('title')->label(__('posts.fields.title'))). Entries with no
explicit ->label() are auto-humanized. See the kinetix-locale skill.
use Happones\Kinetix\Infolists\Infolist;
use Happones\Kinetix\Infolists\Components\Section;
use Happones\Kinetix\Infolists\Components\TextEntry;
use Happones\Kinetix\Infolists\Components\IconEntry;
use App\Models\User;
$infolist = Infolist::make($user)
->schema([
Section::make('Account')
->icon('user')
->columns(12)
->schema([
TextEntry::make('name')->columnSpan(6),
TextEntry::make('email')->icon('mail')->copyable()->columnSpan(6),
TextEntry::make('status')->badge()
->color(fn ($state) => $state === 'active' ? 'success' : 'gray')
->columnSpan(4),
IconEntry::make('is_verified')->boolean()->columnSpan(4),
TextEntry::make('created_at')->dateTime()->columnSpan(4),
]),
]);
public function show(User $user)
{
return inertia('Users/Show', [
'infolist' => UserResource::infolist(Infolist::make($user))->toArray(),
]);
}
<KinetixInfolist :infolist="infolist" />
getState(). Never recompute display values in Vue — the frontend renders pre-formatted state only.HasLabel, HasColor, and HasIcon enum contracts so one Enum drives the label, badge color, and icon consistently across Tables and Infolists.TextEntry::make('company.name')) to read related models without a manual state() callback.->visible(fn ($record) => ...) and ->color(fn ($state) => ...) to keep detail views dynamic; hidden entries are stripped before serialization.->authorize(string $ability, mixed $subject = null) (Gate-based, same shorthand as Action::authorize()/Forms) alongside ->visible()/->hidden() for sensitive fields (e.g. TextEntry::make('salary')->authorize('viewFinancials')). Unauthorized entries are omitted from the serialized payload entirely.Infolist grids use the same responsive system as forms: columns(int) means
"N columns from lg up, ONE below" (never assume a fixed column count on
mobile), and columns() / columnSpan() accept breakpoint maps
(['default' => 1, 'sm' => 2, 'xl' => 3], keys default/sm/md/lg/xl/2xl) on
Infolist/Section/Fieldset/Grid/Tab. Breakpoints measure the INFOLIST's own
width (container queries); spans clamp to the available columns per breakpoint.
How can I help you explore Laravel packages today?