TenantAtlas/docs/product/standards/filament-table-ux.md
2026-03-09 00:16:50 +01:00

6.3 KiB

Filament Table UX Standard

Canonical rules for all Filament table/list surfaces in TenantPilot. Every new or modified table must follow this standard. Deviations must be documented in the spec or PR with rationale.

Last reviewed: 2026-03-09


Governing Principle

Filament-native first. Table UX standardization uses native Filament features wherever reasonably possible. Custom helpers, macros, traits, or plugins are only allowed when Filament cannot express the pattern cleanly enough inline.


Column Tier Model

Every production table must classify its columns into one of three tiers. This is a review convention, not a code abstraction.

Primary

Always visible. Not toggle-hidden. Anchors the row identity.

  • name, display_name, title
  • primary status (if truly central)
  • core subject identifier

Context

Visible by default. May be toggleable. Provides operational context needed for a normal scan.

  • environment, type, category, severity
  • key counts / aggregations
  • last sync / last activity
  • tenant/workspace context where needed

Detail

Hidden by default (toggleable(isToggledHiddenByDefault: true)). Technical or low-frequency information.

  • IDs (tenant_id, external_id, entra_id)
  • created_at, updated_at
  • created_by
  • domains, low-frequency secondary statuses

Visible Column Limit

General-purpose tables should expose 7 or fewer columns by default. Denser power-user surfaces may exceed this with documented justification.


Sortability Rules

Mandatory sortable (when present)

  • Primary identifier fields (name, display_name, title)
  • Status / outcome / enum columns
  • All key timestamps used for recency
  • Numeric count and size columns
  • Version / sequence numbers

Default sort

Every production list must define an explicit defaultSort().

List type Default pattern
Time-series / logs / runs / alerts newest first
Named entities name ascending
Versioned entities latest/highest version first
Audit / evidence newest recorded first

Searchability Rules

  • Every non-trivial production table must make its primary identifier searchable().
  • Additional fields only if query-safe and operationally useful.
  • Avoid searchable on relations without proper indexing.
  • Avoid searchable on computed values without clear SQL handling.

Toggleability Rules

Must be hidden by default

  • Technical IDs
  • External IDs
  • Tenant/workspace internal references (unless primary context)
  • created_at, updated_at
  • Secondary technical metadata

Overflow rule

If a table exceeds the calm default surface, lower-value columns must move into Detail via toggleable(isToggledHiddenByDefault: true) before any cosmetic workaround is considered.


Timestamp Standard

Lists

TextColumn::make('created_at')
    ->since()
    ->sortable()
    ->toggleable(isToggledHiddenByDefault: true)
    ->tooltip(fn ($record) => $record->created_at?->toDateTimeString());
  • Primary visual: ->since() for scan-first recency
  • Absolute date/time available via tooltip

Detail / view pages

  • Use absolute ->dateTime() or equivalent explicit formatting.

Exceptions

  • Version or evidence tables may use absolute time when exact chronology is the primary task.

Null / Empty Values

->placeholder('—')

Consistent across all tables. Do not mix blank cells, n/a, unknown, or dash variants.


ID Presentation

  • Hidden by default
  • copyable() when operationally relevant
  • limit() and/or tooltip() for long identifiers
  • Monospaced styling preferred for technical IDs
  • Must not dominate layout width

Status / Badge / Boolean

  • Status and enum columns use native badge() via centralized BadgeCatalog / BadgeRenderer (per BADGE-001).
  • Booleans use native icon or badge-based rendering consistently.

Empty State Standard

Every production table must define a domain-specific empty state:

->emptyStateHeading('No policies found')
->emptyStateDescription('Sync your first tenant to see policies here.')
->emptyStateActions([
    // exactly 1 primary CTA, RBAC-gated
])

Pagination Profiles

Implementation: App\Support\Filament\TablePaginationProfiles

Surface Page sizes Default
Resource 25, 50, 100 25
Relation manager 10, 25, 50 10
Widget 10 10
Picker 25, 50, 100 25
Custom page 25, 50, all 25 unless override documented

Table State Persistence

Resource list tables in the critical set must use:

->persistFiltersInSession()
->persistSearchInSession()
->persistSortInSession()

Critical persistence set

  • TenantResource
  • PolicyResource
  • BackupSetResource
  • BackupScheduleResource
  • ProviderConnectionResource
  • FindingResource
  • OperationRunResource

Documented exceptions

  • Dashboard widgets: no persistence (glance surfaces)
  • Directory pages: no persistence (computed health metrics)
  • Picker tables: no persistence (workflow-local)

Responsive / Overflow Guardrails

  • Long text: use wrap() where appropriate
  • Long IDs / technical strings: use limit() and tooltip()
  • Solve width problems via better default visibility, not manual width hacks
  • No extraAttributes() width tuning as a default pattern
  • No resize plugin as substitute for better defaults

Performance Guardrails

  • No casual sortable() / searchable() on relation or computed columns without query review
  • No row-by-row counts or exists() checks in hot lists unless optimized
  • Prefer eager loading where relation-backed columns are rendered repeatedly
  • No costly default sorts without understanding DB impact

RBAC / Tenancy Safety

  • Technical cross-tenant identifiers must not be more prominent than needed
  • Empty-state actions and row/header actions remain capability-gated
  • Cross-tenant/workspace tables must preserve enough context to avoid ambiguity
  • This standard must not change authorization behavior

What Remains Table-Local

These areas must stay explicit per table unless a later spec proves a shared need: