Filtered data view
Preview Available in release assets
Filter arbitrary server-rendered datasets with an enhanced partial-update loop while keeping every committed view addressable by an ordinary GET URL.
Server contract
GET /records?status=<value>&owner=<value>&sort=<value>&page=<positive integer> renders the complete page. Enhanced GET /api/filtered-view reads the same named signals and emits one datastar-patch-elements event replacing #filtered-view-results. Unknown enum values produce a visible recoverable error; page is clamped server-side. Responses are deterministic for a given query.
Without Datastar, Apply filters, Clear filters, result links, and pagination perform ordinary GET navigation and browser history restores the server-rendered URL state.
Filtered data view primary example
<main class="thin-filtered-view" aria-labelledby="records-title" data-signals="{status: '', owner: '', sort: 'updated-desc', page: 1}">
<header>
<h1 id="records-title">Service records</h1>
<p>Filter server-rendered operational records. The Apply button commits filters to the URL so links, reloads, and browser history reproduce this view.</p>
</header>
<form action="/records" method="get" data-filter-form data-on:change__debounce.200ms="@get('/api/filtered-view')" data-indicator:filtering>
<fieldset class="thin-fieldset">
<legend>Filters</legend>
<label class="thin-field">Status<select class="thin-select" name="status" data-bind="status">
<option value="">Any status</option>
<option value="healthy">Healthy</option>
<option value="attention">Needs attention</option>
</select>
</label>
<label class="thin-field">Owner<select class="thin-select" name="owner" data-bind="owner">
<option value="">Any owner</option>
<option value="platform">Platform</option>
<option value="studio">Studio</option>
</select>
</label>
<label class="thin-field">Sort<select class="thin-select" name="sort" data-bind="sort">
<option value="updated-desc">Recently updated</option>
<option value="name-asc">Name</option>
</select>
</label>
<input type="hidden" name="page" value="1">
<button class="thin-button" type="submit">Apply filters</button>
<a href="/records">Clear filters</a>
</fieldset>
</form>
<p data-filter-pending role="status" data-show="$filtering" style="display:none">Updating results…</p>
<section id="filtered-view-results" data-filter-results aria-labelledby="results-title" aria-live="polite" aria-atomic="true">
<header>
<h2 id="results-title">6 records</h2>
<p>Any status · Any owner · Recently updated</p>
</header>
<ul>
<li>
<article>
<h3><a href="/records/gateway">Gateway</a></h3>
<p>
<strong>Healthy</strong> · Platform · Updated 8 minutes ago</p>
</article>
</li>
<li>
<article>
<h3><a href="/records/catalog">Catalog sync</a></h3>
<p>
<strong>Needs attention</strong> · Studio · Updated 21 minutes ago</p>
</article>
</li>
</ul>
<nav aria-label="Results pages">
<a href="/records?sort=updated-desc&page=2" data-on:click__prevent="@get('/api/filtered-view?page=2')">Next page</a>
</nav>
</section>
<noscript>
<p>Filtering uses normal page navigation. Choose filters and use Apply filters.</p>
</noscript>
</main>Show code
<main class="thin-filtered-view" aria-labelledby="records-title" data-signals="{status: '', owner: '', sort: 'updated-desc', page: 1}">
<header>
<h1 id="records-title">Service records</h1>
<p>Filter server-rendered operational records. The Apply button commits filters to the URL so links, reloads, and browser history reproduce this view.</p>
</header>
<form action="/records" method="get" data-filter-form data-on:change__debounce.200ms="@get('/api/filtered-view')" data-indicator:filtering>
<fieldset class="thin-fieldset">
<legend>Filters</legend>
<label class="thin-field">Status<select class="thin-select" name="status" data-bind="status">
<option value="">Any status</option>
<option value="healthy">Healthy</option>
<option value="attention">Needs attention</option>
</select>
</label>
<label class="thin-field">Owner<select class="thin-select" name="owner" data-bind="owner">
<option value="">Any owner</option>
<option value="platform">Platform</option>
<option value="studio">Studio</option>
</select>
</label>
<label class="thin-field">Sort<select class="thin-select" name="sort" data-bind="sort">
<option value="updated-desc">Recently updated</option>
<option value="name-asc">Name</option>
</select>
</label>
<input type="hidden" name="page" value="1">
<button class="thin-button" type="submit">Apply filters</button>
<a href="/records">Clear filters</a>
</fieldset>
</form>
<p data-filter-pending role="status" data-show="$filtering" style="display:none">Updating results…</p>
<section id="filtered-view-results" data-filter-results aria-labelledby="results-title" aria-live="polite" aria-atomic="true">
<header>
<h2 id="results-title">6 records</h2>
<p>Any status · Any owner · Recently updated</p>
</header>
<ul>
<li>
<article>
<h3><a href="/records/gateway">Gateway</a></h3>
<p>
<strong>Healthy</strong> · Platform · Updated 8 minutes ago</p>
</article>
</li>
<li>
<article>
<h3><a href="/records/catalog">Catalog sync</a></h3>
<p>
<strong>Needs attention</strong> · Studio · Updated 21 minutes ago</p>
</article>
</li>
</ul>
<nav aria-label="Results pages">
<a href="/records?sort=updated-desc&page=2" data-on:click__prevent="@get('/api/filtered-view?page=2')">Next page</a>
</nav>
</section>
<noscript>
<p>Filtering uses normal page navigation. Choose filters and use Apply filters.</p>
</noscript>
</main>Anatomy
- Named data view
- Native GET filter form
- Explicit Apply and Clear paths
- Pending status
- Atomic server-rendered result region
- Durable result and pagination links
Variants and sizes
- Fieldset filters
- Toolbar filters
- Sidebar filters
- Paginated or cursor-backed server data
Gallery coverage: No structured variant fixtures are published yet; the primary example above is the only verified production markup.
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
- Initial results
- Filtered results
- Pending
- Empty
- Invalid filter
- Server error
- Disconnected
- Reconnected
- Back or forward restoration
Structured state fixtures are not published for this item yet.
Interaction and keyboard behavior
A GET submission is canonical and commits the complete filter state to the URL. Datastar may request the same server query on change and replace #filtered-view-results, but must not own filtering, sorting, pagination, or result state. Back and Forward restore from the URL through ordinary navigation; reconnect repeats only an idempotent GET.
Accessibility
This is an ordinary semantic filter form, not a search landmark: the controls refine the current records view rather than find a separate resource set. Keep filter focus in place for enhanced updates. Use one concise atomic live region for the server result count or error, avoid announcing every row, and move focus only after full navigation or when the user explicitly follows pagination. Pending text is visible; empty and error responses retain filter controls and recovery links.
- Contract
LIVE- Basis
- Native GET form, select controls, result list, links, and polite status
- Evidence
tests/unit/site/filtered-view.test.ts
API
GET /records?status=<value>&owner=<value>&sort=<value>&page=<positive integer> renders the complete page. Enhanced GET /api/filtered-view reads the same named signals and emits one datastar-patch-elements event replacing #filtered-view-results. Unknown enum values produce a visible recoverable error; page is clamped server-side. Responses are deterministic for a given query.
Behavior tier: datastar-required
Structured host, property, and example tables are not authored for this item yet. Use the summary above and the primary example.
Fallback
Without Datastar, Apply filters, Clear filters, result links, and pagination perform ordinary GET navigation and browser history restores the server-rendered URL state.
Assets and dependencies
/foundation.css/layouts.css/components.css
Dependencies: datastar@1.0.2, field, fieldset, select, status, pagination
Source: packages/ui/src/items/filtered-view/style.ts
Composition
Derived from the examples and style specs, not authored.