# Components: Data display, media and code

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from Generated/*.razor, blazor/mappings, generated.manifest.json and core/dist/elements/api.json. Do not edit: change the source and run the generator.

Every component renders its element with the parameters you set as attributes. An attribute that is not a parameter (`id`, `class`, `data-*`, `aria-*`, ...) is put on the element as it is, and a `class` is added to the component's own (`ExtraClass` on a component that lists it). Names are `Pk` plus the tag in PascalCase.

## `PkAvatar`

`<pk-avatar>`: **Avatar** (Data display). A person or entity as an image or initials, with sizes, shapes, a stable colour per name and an online, away or busy status dot.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Name` | `string?` |  | attribute `name` | The accessible name and the source of the initials. |
| `Src` | `string?` |  | attribute `src` | An image URL; initials (or the placeholder glyph when there is no name) show until it loads or if it fails. |
| `Size` | `AvatarSize?` | `Md` `Sm` `Lg` | attribute `size` | Size. |
| `Shape` | `AvatarShape?` | `Circle` `Square` | attribute `shape` | Shape. |
| `Status` | `AvatarStatus?` | `None` `Online` `Away` `Busy` | attribute `status` | Status dot. |
| `Colour` | `string?` |  | attribute `colour` | A colour slot 1 to 5, or muted; by default a stable slot chosen from the name. |
| `ChildContent` | `RenderFragment?` |  | default slot | Replaces the neutral person glyph shown when there is no name and no image. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-avatar` in the `plainkit-sdk` skill.

## `PkAvatarGroup`

`<pk-avatar-group>`: **AvatarGroup** (Data display). Overlapping avatars with a +N tile for the overflow.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Max` | `double` |  | attribute `max` | Avatars shown before the +N tile. |
| `Label` | `string?` |  | attribute `label` | Accessible name of the group. |
| `ChildContent` | `RenderFragment?` |  | default slot | pk-avatar children. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-avatar-group` in the `plainkit-sdk` skill.

## `PkBadge`

`<pk-badge>`: **Badge** (Data display). A count or short label in a pill, or a status dot with a label; counts cap at a maximum (99+).

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Variant` | `BadgeVariant?` | `Accent` `Muted` `Ok` `Warn` `Danger` `Outline` | attribute `variant` | Colour. |
| `Count` | `string?` |  | attribute `count` | Value to show when the slot is empty, capped by max. |
| `Max` | `double` |  | attribute `max` | Counts above read as max+; 0 disables capping. |
| `Dot` | `bool` |  | attribute `dot` | A status dot with the text as its label instead of a pill. |
| `ChildContent` | `RenderFragment?` |  | default slot | Text; overrides the count. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-badge` in the `plainkit-sdk` skill.

## `PkChart`

`<pk-chart>`: **Chart** (Data display). Bar, line, donut, stacked-bar and sparkline charts in pure SVG, drawn from a slotted data table (which stays as the accessible fallback) or from a data property.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Kind` | `ChartKind?` | `Bar` `Line` `Donut` `Stack` `Spark` | attribute `kind` | Chart type. |
| `Caption` | `string?` |  | attribute `caption` | Title (figcaption). |
| `Height` | `int` |  | attribute `height` | viewBox height of bar and line charts. |
| `Data` | `PkChartData?` |  | attribute `data` | Data as a property instead of a table: { labels: string[], series: { name, values: number[] }[] }. { Labels, Series: [{ Name, Values }] }. It is sent as a JSON attribute (camelCase) and replaces the slotted table. |
| `CaptionContent` | `RenderFragment?` |  | slot `caption` | A rich caption; overrides caption. |
| `ChildContent` | `RenderFragment?` |  | default slot | A table: the first column holds labels, each further column a series. A cell may carry data-value. |

When you set a named fragment (`CaptionContent`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

Element details (parts, CSS custom properties, methods, accessibility): `pk-chart` in the `plainkit-sdk` skill.

## `PkCodeBlock`

`<pk-code-block>`: **Code block** (Code). A block of code with an optional label bar, line numbers, a wrap toggle and a copy button. The code is the element's text content.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Label` | `string?` |  | attribute `label` | File or language shown in the bar; also the region's name. |
| `LineNumbers` | `bool` |  | attribute `line-numbers` | A gutter of line numbers. |
| `Wrap` | `bool` |  | attribute `wrap` | Wrap long lines. |
| `NoCopy` | `bool` |  | attribute `no-copy` | Hide the copy button. |
| `MaxHeight` | `string?` |  | attribute `max-height` | Cap the height, any CSS length. |
| `OnWrapChange` | `EventCallback<PkWrapChangeEventArgs>` |  | event `pk-wrap-change` | The wrap toggle was pressed; wrap already has the new value. |
| `OnCopy` | `EventCallback<PkCopyEventArgs>` |  | event `pk-copy` | After a copy attempt. |
| `ChildContent` | `string?` |  | text content | The content. |

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-copy` | `PkCopyEventArgs` | Ok: bool? |
| `pk-wrap-change` | `PkWrapChangeEventArgs` | Wrap: bool? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-code-block` in the `plainkit-sdk` skill.

## `PkCodeView`

`<pk-code-view>`: **Code view** (Code). A read-only, line-numbered listing for code or text: sticky gutter numbers a selection leaves out, highlighted lines, coloured segments, sideways scrolling.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Lines` | `IReadOnlyList<string>?` |  | attribute `lines` | Lines to show; the text content when empty. |
| `Segments` | `IReadOnlyList<IReadOnlyList<PkCodeSegment>>?` |  |  | Optional colouring: one list of runs per line of Lines, each { text, kind, match, word }. The runs of a line should join to the line's text. |
| `Start` | `int` |  | attribute `start` | First line number. |
| `Highlight` | `string?` |  | attribute `highlight` | Lines to highlight, such as 12 or 3, 5-7; the first is scrolled to the middle. |
| `Label` | `string?` |  | attribute `label` | Accessible name. |
| `MaxHeight` | `string?` |  | attribute `max-height` | Cap the height, any CSS length. |
| `Fill` | `bool` |  | attribute `fill` | Fill the container; scrolls inside. Ignores maxHeight. |
| `Header` | `bool` |  | attribute `header` | Show the header bar. |
| `Copy` | `bool` |  | attribute `copy` | Header Copy button; copies all lines. |
| `Language` | `string?` |  | attribute `language` | Language tag in the header. |
| `HeaderStart` | `RenderFragment?` |  | slot `header-start` | After the title. |
| `Actions` | `RenderFragment?` |  | slot `actions` | Right side. |
| `OnLineClick` | `EventCallback<PkLineClickEventArgs>` |  | event `pk-line-click` | A click left a caret in a line's code: its line and zero-based column. |
| `OnCopy` | `EventCallback<PkCopyEventArgs>` |  | event `pk-copy` | After a copy attempt. |
| `ChildContent` | `string?` |  | text content | The content. |

When you set a named fragment (`HeaderStart`, `Actions`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-copy` | `PkCopyEventArgs` | Ok: bool? |
| `pk-line-click` | `PkLineClickEventArgs` | Line: double?, Column: double? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-code-view` in the `plainkit-sdk` skill.

## `PkDataTable`

`<pk-data-table>`: **Data table** (Data display). A paged, searchable, sortable table that owns its data flow: a pk-table (manual), a pk-table-filters search/filter panel and a pk-pagination footer, with the query, the load and the states in one place. Set the load(query) callback as a property (business logic, never config data): it receives { page, pageSize, sort, sortDir, search, filters } and, as a second argument, { signal }, an AbortSignal that aborts when a newer request replaces it or the element leaves the page (pass it to fetch), and returns (or resolves to) { rows, total, filterOptions? } for that page (filterOptions: { filterKey: [{ value, label }] } replaces those select filters' options, e.g. labels with counts). A loading state shows while in flight, a rejection shows the error state with Retry, zero rows the empty state, and a stale response is dropped. With selectable set, the selection survives paging and searching: selected holds the chosen ids, selectScope is 'page' (those ids) or 'all' (Select all N rows: the ids stay the loaded page). Each change raises pk-select with the ids, the scope and the current query, so for scope 'all' the host runs its bulk action against that query on the server. A search or filter change narrows scope 'all' back to 'page'. rowHref(row), also a callback property, makes rows clickable and is called with the row. pk-list-page and the lookup picker are built on this element.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Load` | `Func<PkListRequest, Task<PkListResult<TItem>>>` |  |  | Loads one page. It gets the search, the sort, the 1-based page and the page size (PkListRequest) and returns the items of that page and the total of the whole searched list (PkListResult{T}). Called for the first page, and again for every search, sort, page or page-size change and for ReloadAsync; a request that a newer one replaces has its CancellationToken cancelled and its result ignored. |
| `Columns` | `IReadOnlyList<PkTableColumn<TItem>>` |  |  | The columns. A column Cell template fills the element's cell-<rowId>-<key> slot of each loaded row. |
| `IdOf` | `Func<TItem, string>` |  |  | The id of an item: names the row for click, current-row, selection and the cell slots. Ids must not contain a double quote. |
| `PageSize` | `int` |  | attribute `page-size` | The page size to start with (initial only; the user changes it in the pager). Default 25. Two-way: @bind-PageSize follows the reader's page size (pk-query-change). |
| `PageSizeChanged` | `EventCallback<int>` |  | two-way pair of `PageSize` | Raised when PageSize changes (two-way binding: `@bind-PageSize`). |
| `PageSizeOptions` | `int[]?` |  | attribute `page-size-options` | The page sizes the pager offers. Default 10, 25, 50, 100; an empty array hides the choice. |
| `SortKey` | `string?` |  | attribute `sort` | The key of the column to sort by first (initial only). Null loads the default order. Two-way: @bind-SortKey follows the reader's sort (pk-query-change). |
| `SortKeyChanged` | `EventCallback<string?>` |  | two-way pair of `SortKey` | Raised when SortKey changes (two-way binding: `@bind-SortKey`). |
| `Descending` | `bool` |  | attribute `sort-dir` | Sort SortKey descending first (initial only). Two-way: @bind-Descending. |
| `DescendingChanged` | `EventCallback<bool>` |  | two-way pair of `Descending` | Raised when Descending changes (two-way binding: `@bind-Descending`). |
| `Search` | `string?` |  | attribute `search` | The search term to start with, for example from a query string: it fills the search box and the first Load request, and typing replaces it. Changing it later replaces the term and loads page 1. Two-way: @bind-Search. |
| `SearchChanged` | `EventCallback<string?>` |  | two-way pair of `Search` | Raised when Search changes (two-way binding: `@bind-Search`). |
| `Searchable` | `bool` |  | attribute `hide-search` | Show the search box. Default true. |
| `SearchPlaceholder` | `string?` |  | attribute `search-placeholder` | The text shown in the empty search box. It is also the accessible name unless SearchLabel is set. |
| `SearchLabel` | `string?` |  | attribute `search-label` | The accessible name of the search box; defaults to SearchPlaceholder, then "Search". |
| `SearchDebounceMs` | `int` |  | attribute `search-debounce` | Milliseconds to wait after the last keystroke before searching (the element's own timer). Default 300. |
| `EmptyText` | `string` |  | attribute `empty` | The text shown when the list is empty and there is no search. |
| `NoResultsText` | `string` |  | attribute `no-results` | The text shown when a search matched nothing. |
| `EmptyContent` | `RenderFragment?` |  | slot `empty` | Your own empty state (for example a PkEmptyState), shown instead of the text when the list is empty and there is no search. |
| `LoadErrorText` | `string` |  | attribute `load-error` | The heading of the failed-load message. The element shows it with a Retry button when Load throws. |
| `OnLoadError` | `EventCallback<Exception>` |  |  | Raised when Load throws (not for a cancelled request), so you can log it. |
| `AddLabel` | `string?` |  | attribute `add-label` | The label of the primary action button after the search box (for example "+ Add customer"). Empty shows no button. |
| `OnAdd` | `EventCallback` |  | event `pk-add` | The add button (addLabel) was pressed. |
| `OnRowClick` | `EventCallback<TItem>` |  |  | A row was activated (a click, or Enter or Space on the focused row): the item of that row. Setting it makes the rows clickable. |
| `CurrentRow` | `string?` |  | attribute `current-row` | The id (IdOf) of the row whose record is open in a detail pane: it is tinted and marked aria-current. Set it from the route; the list never changes it. |
| `Expandable` | `bool` |  | attribute `expandable` | Rows get a toggle that shows their DetailTemplate under them (the detail is rendered for the loaded page's rows only). Expanding is not a row click. |
| `Expander` | `string?` |  | attribute `expander` | Set to none to leave out the toggle column: the host opens rows by setting expanded, as pk-table's expander. "none" leaves out the built-in toggle column; the host then opens rows through Expanded. |
| `Expanded` | `IReadOnlyList<string>?` |  | attribute `expanded` | The ids (IdOf) of the expanded rows (two-way: @bind-Expanded; needs Expandable and a DetailTemplate). |
| `ExpandedChanged` | `EventCallback<IReadOnlyList<string>?>` |  | two-way pair of `Expanded` | Raised with the new value of Expanded (two-way: Expanded follows it). |
| `DetailTemplate` | `RenderFragment<TItem>?` |  |  | The detail of one loaded row, shown under it when the user expands it (the element's detail-<rowId> slot). Needs Expandable and IdOf. The slot name is dynamic: the component renders one slot="detail-<rowId>" per loaded row when Expandable is set, and only those rows get a toggle. |
| `DetailPanels` | `IReadOnlyDictionary<string, RenderFragment<TItem>>?` |  |  | Further named detail panels of a loaded row, by panel name (the element's detail-<rowId>.<name> slots), next to or instead of DetailTemplate. Needs Expandable. A named panel has no toggle: the host opens it by putting "<rowId>.<name>" in Expanded. The slot name is dynamic: one slot="detail-<rowId>.<name>" per panel and loaded row when Expandable is set. |
| `OnRowExpand` | `EventCallback<PkTableRowExpandArgs<TItem>>` |  | event `pk-row-expand` | The user expanded or collapsed a row: its id, item and the new state. Expanded already follows it. Not a row click. |
| `Selectable` | `bool` |  | attribute `selectable` | A checkbox column with select-all for the page, and "Select all N rows" when the searched list has more. The selection is kept by id across pages and searches (see Selected). |
| `SelectPageOnly` | `bool` |  | attribute `select-page-only` | Never offer "Select all N rows": the header checkbox selects the loaded page and SelectScope stays page. For a list whose bulk action works only on explicit ids. Default false. |
| `Selected` | `IReadOnlyList<string>?` |  | attribute `selected` | The ids (IdOf) of the selected rows, across every page and search the user visited (two-way: @bind-Selected). The order carries no meaning. After "Select all N rows" (SelectScope all) it holds only the loaded page: the rest is the query. |
| `SelectedChanged` | `EventCallback<IReadOnlyList<string>?>` |  | two-way pair of `Selected` | Raised with the new value of Selected (two-way). |
| `SelectScope` | `string?` |  | attribute `select-scope` | page or all: all after the user chose "Select all N rows", when every row of the query counts as selected. It follows the element and goes back to page when the selection changes. |
| `OnSelect` | `EventCallback<PkSelectEventArgs>` |  |  | The selection changed: Selected holds the ids (every selected id across pages, or the loaded page for scope all), Scope is page or all and Query the search and sort it refers to (read it with ToRequest). For scope all run the bulk action against that query on the server: the ids of the other pages are never sent. |
| `BulkContent` | `RenderFragment?` |  | slot `bulk` | Actions shown while rows are selected (the element's bulk slot). |
| `ToolbarContent` | `RenderFragment?` |  | slot `actions` | Extra toolbar content, after the search box and before the add button (the element's actions slot). |
| `Cards` | `bool` |  | attribute `cards` | Each row becomes a card below 640px, columns marked HidePhone dropped. Default true. |
| `Striped` | `bool` |  | attribute `striped` | Alternate row tint. |
| `Density` | `PkTableDensity?` |  | attribute `density` | The cell padding step; null leaves the element's default. |
| `MaxHeight` | `string?` |  | attribute `max-height` | Bounds the table's height (a CSS length); with StickyHeader the header stays while the rows scroll. |
| `StickyHeader` | `bool` |  | attribute `sticky-header` | Pin the header row (needs MaxHeight). |
| `Label` | `string?` |  | attribute `label` | The accessible name of the table. Default "List"; give each list its own. |
| `PagerLabel` | `string?` |  | attribute `pager-label` | The accessible name of the pager. Default "List pages". |
| `Caption` | `string?` |  | attribute `caption` | The table caption, as text. |
| `RowHrefTemplate` | `string?` |  | attribute `row-href-template` | Row links: an address with {id} (and any {field}, URL-encoded), like /orders/{id}. The first cell of each row becomes a real link; a plain click goes through the router. |
| `Filters` | `IReadOnlyList<PkFilterField>?` |  | attribute `filters` | The filter fields beside the search box; each value reaches Load in Filters (a multiselect in MultiFilters). |
| `Navigate` | `string?` |  | attribute `navigate` | Who follows a RowHrefTemplate link on a plain click: "router" (default, or leave it unset) lets the app router handle the cancelable pk-navigate; "host" raises OnNavigate as a notification the router ignores and nothing navigates, so your handler navigates (NavigationManager); "none" raises nothing and the browser follows the address. |
| `OnNavigate` | `EventCallback<PkNavigateEventArgs>` |  | event `pk-navigate` | A click on a row link (pk-table's event, passed through). The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |

Two-way binding: `@bind-PageSize`, `@bind-SortKey`, `@bind-Descending`, `@bind-Search`, `@bind-Expanded`, `@bind-Selected`.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-load-error` | `PkLoadErrorEventArgs` | Error: JsonElement? |
| `pk-navigate` | `PkNavigateEventArgs` | To: string?, Href: string?, Id: string?, Anchor: string?, Replace: bool? |
| `pk-query-change` | `PkQueryChangeEventArgs` | SortKey: string?, Descending: bool?, Search: string?, Page: double?, PageSize: double?, Filters: JsonElement? |
| `pk-row-click` | `PkRowClickEventArgs` | Id: string?, Row: JsonElement? |
| `pk-row-expand` | `PkRowExpandEventArgs` | Id: string?, Index: double?, Expanded: bool? |
| `pk-select` | `PkSelectEventArgs` | Item: JsonElement?, Value: string?, Checked: bool?, Id: string?, Selected: string[]?, Scope: string?, Query: JsonElement? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-data-table` in the `plainkit-sdk` skill.

## `PkFieldList`

`<pk-field-list>`: **Field list** (Data display). A titled description list: term and value pairs as slotted dt and dd, or a data-driven items property, beside each other, stacked or run in; with dividers and a dense mode. Before it upgrades (or with JavaScript off) its slotted content shows as plain markup, not hidden.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Level` | `int` |  | attribute `level` | Heading level exposed to assistive technology. |
| `Title` | `string?` |  | attribute `heading` | Section title. |
| `Layout` | `FieldListLayout?` | `Grid` `Stacked` | attribute `layout` | Term beside value, or above it. |
| `Dividers` | `bool` |  | attribute `dividers` | A rule under each value. |
| `Dense` | `bool` |  | attribute `dense` | Tighter rhythm and smaller text. |
| `Items` | `IReadOnlyList<PkFieldListItem>?` |  | attribute `items` | Data-driven rows, rendered as dt/dd pairs alongside any slotted ones: { label, value, hidden?, href? }[]. value is text only (no HTML); a rich value still uses the slot form. A row with an empty value (null, undefined or '') hides itself unless showEmpty is set; hidden always hides it. href renders value as a link. |
| `ShowEmpty` | `bool` |  | attribute `show-empty` | Shows rows whose value is empty instead of hiding them: items rows with no value, and slotted dt/dd pairs whose dd has no element and no text (issue 801). |
| `ChildContent` | `RenderFragment?` |  | default slot | dt and dd pairs; a dd may hold chips, links or code. Renders alongside any items rows. A pair whose dd is empty (no element, no text) hides itself unless showEmpty is set, so an optional value needs no wrapper. |
| `HeadingContent` | `RenderFragment?` |  | slot `heading` | A rich heading; overrides heading. |

When you set a named fragment (`HeadingContent`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

Element details (parts, CSS custom properties, methods, accessibility): `pk-field-list` in the `plainkit-sdk` skill.

## `PkFrame`

`<pk-frame>`: **Frame** (Media). A sandboxed iframe (an inline srcdoc document or a same-site src) with named width presets and a light/dark theme hand-off.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Title` | `string?` |  | attribute `title` | The framed document's accessible name; warns once when empty. |
| `Html` | `string?` |  | attribute `html` | An inline document, written as the frame's srcdoc. Ignored when src is set. |
| `Src` | `string?` |  | attribute `src` | A same-site path or an http(s) address to load; takes priority over html. |
| `Sandbox` | `string?` |  | attribute `sandbox` | Space-separated sandbox tokens. allow-same-origin is dropped when allow-scripts is also present, unless allowSameOrigin opts in. |
| `AllowSameOrigin` | `bool` |  | attribute `allow-same-origin` | Adds allow-same-origin to sandbox even alongside allow-scripts; only for a trusted, cooperating page. |
| `Preset` | `FramePreset?` | `Phone` `Tablet` `Desktop` `Full` | attribute `preset` | A named width cap from tokens/breakpoints.json (phone, tablet, desktop -> the wide breakpoint) or full (no cap). |
| `Height` | `string?` |  | attribute `height` | auto keeps a fallback height until a cooperating document posts pk-frame-resize; any other value is a CSS length. |
| `Theme` | `FrameTheme?` | `Light` `Dark` `Inherit` | attribute `theme` | Handed to a cooperating document as ?pk-theme= on a same-origin src and as a postMessage; inherit sends nothing. |
| `Loading` | `FrameLoading?` | `Lazy` `Eager` | attribute `loading` | The native iframe loading mode. |
| `OnFrameLoad` | `EventCallback` |  |  | The framed document finished loading. |
| `OnFrameResize` | `EventCallback<PkFrameResizeEventArgs>` |  |  | A cooperating document reported its height (height="auto" only). |

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-frame-resize` | `PkFrameResizeEventArgs` | Height: double? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-frame` in the `plainkit-sdk` skill.

## `PkGallery`

`<pk-gallery>`: **Gallery** (Data display). The SDK gallery inside your page: every control, element, layout and template with live samples, narrowed by attributes to just what you want to show. Loads on demand in its own frame.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Kind` | `PkGalleryKind` | `All` `Foundations` `Controls` `Elements` `Layouts` `Templates` | attribute `kind` | What to show: foundations, controls, elements, layouts or templates. Empty shows the controls when a group or control is named, otherwise everything. |
| `Group` | `string?` |  | attribute `group` | One control group by name ("Forms & inputs") or slug (forms-inputs); with kind layouts or templates, that samples group. |
| `Control` | `string?` |  | attribute `control` | One control, element or sample id, or a comma separated list ("button,input"); elements also match by tag (pk-tabs). |
| `Theme` | `PkTheme` | `Auto` `Dark` `Light` | attribute `theme` | The theme of the gallery and its samples. auto keeps the gallery's own default (dark). |
| `Width` | `PkWidth` | `Desktop` `Phone` | attribute `width` | Render the samples at desktop width or in a 375px phone frame. |
| `Filter` | `string?` |  | attribute `filter` | Search text: only controls whose name (or group) contains it are shown. |
| `Chrome` | `PkChrome` | `None` `Full` | attribute `chrome` | none shows only the content, sized to fit it; full keeps the contents nav, the viewport and text size toolbar and the element inspector (Details: an element's API and live markup). |
| `Height` | `int?` |  | attribute `height` | A fixed height in pixels. 0 fits the content when chrome is none and uses the default height when chrome is full. |
| `Src` | `string?` |  | attribute `src` | Address of the gallery's embed.html when it is not next to the element module (the npm package leaves dist/gallery out), for example the Pages site or a CDN prefix; without it a frame that never answers shows a notice that says so. A relative address resolves against the document's base URI (the base URI), so it works on a routed page. |
| `Sections` | `IReadOnlyList<PkGallerySection>?` |  | attribute `sections` | Sections to add to the gallery's Details drawer (chrome full), as JSON text: a list of { tag?, title, open?, lines?, columns?, rows?, code? }, all text. The element sends them to its frame by message once the gallery is ready and whenever the text changes; tag limits one to an element (pk-button), none shows it for every element. Text only, cut to limits; see js/gallery-sections.js. Sent as JSON text ([{ tag, title, open, lines, columns, rows, code }]); the gallery frame draws it in the Details drawer as text. PkGallerySection.ForBlazor() is the Blazor section, computed from the package's mappings. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-gallery` in the `plainkit-sdk` skill.

## `PkHeatMap`

`<pk-heat-map>`: **Heat map** (Data display). A weighted treemap: a fixed-height area filled with boxes whose area is each item's value and whose colour is its status (neutral, info, success, warning, danger). Boxes are real links or buttons with one tab stop and arrow-key movement, a data table is always kept beside the map as the accessible fallback, and boxes narrower than min-box merge into one "Other" box. Data is a property, not child elements.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Items` | `IReadOnlyList<PkHeatItem>?` |  | attribute `items` | The boxes: [{ id, label, value, severity?, intensity?, href?, children? }]. value above 0 sets the area; severity is neutral (default), info, success, warning or danger; href makes the box a link. An item with no usable value is skipped and said so once in the log. Instead of this property, a plain <table> as the direct child is read (first cell the name, a link in it the href, column 2 the value unless a header says value, count, total, size or amount, headers severity or status and intensity pick those columns, a cell's data-value beats its text, a row's data-id is the id; a table edit redraws); this property wins when it has items. children is a list of items of the same shape (one level, used with group-by); a group takes the sum of its children as its value and the most serious of their severities. { Id, Label, Value, Severity, Href }. It is sent as a JSON attribute (camelCase). |
| `Label` | `string?` |  | attribute `label` | The accessible name of the map (and the caption of its data table). |
| `MinBox` | `int` |  | attribute `min-box` | The smallest side of a box in pixels. Boxes below it merge into one "Other (n)" box (never dropped; the data table still lists every item); a box that cannot be merged is given extra area until it reaches the minimum, so no tile is a thin strip. Other takes the most serious severity of its members. 0 never merges. |
| `Layout` | `PkHeatMapLayout?` | `Squarified` `Rows` | attribute `layout` | squarified sorts by value into near-square boxes; rows keeps the input order (weighted rows), so positions stay put while values change: use it for live data. |
| `Scale` | `PkHeatMapScale?` | `Status` `Intensity` | attribute `scale` | status colours a box by its severity; intensity uses one hue by each item's intensity (0 to 1), shown as a percentage and carried in the box's name. |
| `MaxItems` | `int` |  | attribute `max-items` | 0 is no cap; otherwise the top N items by value are boxes and the rest merge into Other. |
| `ValueFormat` | `PkHeatMapValueFormat?` | `Number` `Compact` `Share` | attribute `value-format` | How a value reads: number (locale grouping), compact (1.2K) or share (percent of the total). |
| `GroupBy` | `bool` |  | attribute `group-by` | One level of grouping: an item with children is drawn as a framed area (its name and total in a header) whose area is the sum of its children and which holds them as boxes. Without group-by such an item is one plain box. A child's name carries its group ("Payments: Invoices"). |
| `ShowTable` | `bool` |  | attribute `show-table` | Show the data table in place of the map (also the print and narrow-screen fallback you choose). The table is always in the element for assistive technology. |
| `EmptyContent` | `RenderFragment?` |  | slot `empty` | Shown when there is no usable item. Default text: No data. |
| `OnSelect` | `EventCallback<PkHeatSelectEventArgs>` |  | event `pk-heat-select` | A box was activated (click, Enter or Space; a box with an href is a real link). detail.id is the item id, detail.item the item itself; for the merged box id is "__other" and item.members lists the merged items. Cancel it to stop a link navigating. Activating the merged Other box also opens its members as a table over the map (Escape or Close returns focus to the box); cancel the event to stop that. The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |
| `OnFocusItem` | `EventCallback<PkHeatFocusItemEventArgs>` |  | event `pk-heat-focus-item` | Focus moved to a box (arrow keys, Tab or a click). detail.id is the item id, detail.item the item. |

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-heat-focus-item` | `PkHeatFocusItemEventArgs` | Id: string? |
| `pk-heat-select` | `PkHeatSelectEventArgs` | Id: string? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-heat-map` in the `plainkit-sdk` skill.

## `PkIcon`

`<pk-icon>`: **Icon** (Media). A symbol from the SDK sprite, drawn in the surrounding text colour and size. The sprite is found next to the element module, so it works wherever dist is copied.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Name` | `string?` |  | attribute `name` | The symbol id in the sprite (search, settings, orders, ...). |
| `Size` | `IconSize?` | `Sm` `Md` `Lg` `Xl` | attribute `size` | md is 1.25em and follows the text; sm, lg and xl are fixed steps of the space scale. |
| `Label` | `string?` |  | attribute `label` | The accessible name. Without it the icon is decorative and hidden from assistive technology. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-icon` in the `plainkit-sdk` skill.

## `PkImageGallery`

`<pk-image-gallery>`: **Image gallery** (Data display). A grid of square thumbnails with one marked primary (a badge and an accent frame). Clicking a thumbnail opens the lightbox. Editable, it adds a make-primary and a remove button under each image, a drag handle (or Alt+Up/Alt+Down) to reorder them, and an add tile that picks files.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Images` | `IReadOnlyList<PkGalleryImage>?` |  | attribute `images` | The images: [{ src, alt, primary?, status? }]. status is a short warning badge such as Staged. Property or a JSON attribute. Only same-site paths, http(s) and raster data URLs are shown. Sent as a JSON attribute ([{ src, alt, primary, status }], camelCase). |
| `Primary` | `int` |  | attribute `primary` | The index of the primary image; -1 uses the first image flagged primary in images, or none. |
| `PrimaryChanged` | `EventCallback<int>` |  | two-way pair of `Primary` | Raised when Primary changes (two-way binding: `@bind-Primary`). |
| `Columns` | `int` |  | attribute `columns` | A fixed number of equal columns (1 to 12). 0 fits as many as min allows. |
| `Min` | `string?` |  | attribute `min` | The narrowest a thumbnail may get, as a css length in px, rem or em. Used when columns is 0. |
| `Editable` | `bool` |  | attribute `editable` | Show make-primary and remove buttons under each image and the add tile. |
| `HideAdd` | `bool` |  | attribute `hide-add` | Editable, keep make-primary, remove and reorder but skip drawing the add tile, so the host supplies its own add control elsewhere (a real Blazor InputFile, say) instead of reading pk-add, which only carries file name/size/type, never the bytes. |
| `AddLabel` | `string?` |  | attribute `add-label` | Text on the add tile. |
| `Accept` | `string?` |  | attribute `accept` | The file types the add tile's picker offers. |
| `OnPrimaryChange` | `EventCallback<PkPrimaryChangeEventArgs>` |  | event `pk-primary-change` | Make primary was pressed. Cancelable: preventDefault keeps the current primary (a controlled gallery updates images and primary itself). |
| `OnRemove` | `EventCallback<PkRemoveEventArgs>` |  | event `pk-remove` | Remove was pressed. Cancelable: preventDefault keeps the image. Otherwise it is taken out of images, and if it was the primary the first image becomes the primary. |
| `OnAdd` | `EventCallback<PkAddEventArgs>` |  | event `pk-add` | Files were chosen on the add tile's own picker (not one made on a slotted input slot="input" input, where the host's own change/OnChange is the read path instead). The gallery adds nothing itself: read the files (a data URL) and set images. |
| `OnReorder` | `EventCallback<PkReorderEventArgs>` |  | event `pk-reorder` | A tile was dragged by its handle to a new position, or moved with Alt+Up/Alt+Down (built on pk-sortable, core/elements/sortable/). order is every image's src in the new order; item is the moved image's src; from and to are its old and new index. Cancelable: preventDefault keeps the current order (a controlled gallery reorders images itself). |
| `InputContent` | `RenderFragment?` |  | slot `input` | An existing file input (a Blazor InputFile) that keeps doing the reading: the add tile then only draws the target, and pk-add does not fire for a pick made on it. In plain HTML put slot="input" on the input itself; the generated Blazor InputContent parameter sets it for you (<InputContent><InputFile OnChange=... /></InputContent>). |

Two-way binding: `@bind-Primary`.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-add` | `PkAddEventArgs` | Files: PkFileInfo[]?, Names: string[]? |
| `pk-primary-change` | `PkPrimaryChangeEventArgs` | Index: double?, Src: string?, Previous: double? |
| `pk-remove` | `PkRemoveEventArgs` | Index: double?, Src: string?, Value: string? |
| `pk-reorder` | `PkReorderEventArgs` | Order: string[]?, From: double?, To: double?, Item: string?, External: bool?, Payload: JsonElement? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-image-gallery` in the `plainkit-sdk` skill.

## `PkListGroup`

`<pk-list-group>`: **List group** (Data display). A bordered list of rows: plain, actionable (links and buttons), with badges at the end, or a checklist.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Variant` | `ListGroupVariant?` | `Plain` `Action` `Flush` | attribute `variant` | Row behaviour and frame. |
| `Checklist` | `bool` |  | attribute `checklist` | Rows are labels with a checkbox. |
| `Dense` | `bool` |  | attribute `dense` | Compact rows for long lists of results or an outline: rows at text height with the children laid out inline from the start, and no 44px minimum on desktop (an actionable row still gets the touch target on a phone). A row can carry data-depth="1" to "3" to indent. |
| `Label` | `string?` |  | attribute `label` | Accessible name of the list. |
| `ChildContent` | `RenderFragment?` |  | default slot | Rows: div, a, button or label elements. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-list-group` in the `plainkit-sdk` skill.

## `PkLocalTime`

`<pk-local-time>`: **Local time** (Data display). A UTC instant written in the reader's own locale and time zone, as a date, a time, both, or relative to now (3 hours ago). Keeps a real time element inside, so the machine value stays readable.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Value` | `string?` |  | attribute `datetime` | The instant: an ISO 8601 string (2026-09-19T14:30:00Z). A date-only value is a calendar day and is never shifted by the time zone. |
| `Format` | `LocalTimeFormat?` | `Datetime` `Date` `Time` `Relative` | attribute `format` | What to show. relative reads 3 hours ago and refreshes every 30 seconds while connected; its title holds the full date. |
| `Length` | `LocalTimeLength?` | `Short` `Medium` `Long` | attribute `length` | How much of the date to spell out (9/19/26, Sep 19, 2026, September 19, 2026). |
| `Locale` | `string?` |  | attribute `locale` | A BCP 47 tag such as de-DE; empty uses the reader's browser locale. |
| `TimeZone` | `string?` |  | attribute `time-zone` | An IANA zone such as Europe/Berlin; empty uses the reader's own zone. |
| `ChildContent` | `RenderFragment?` |  | default slot | Fallback text, shown only while the datetime is empty or not a date. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-local-time` in the `plainkit-sdk` skill.

## `PkLog`

`<pk-log>`: **Log** (Data display). A streaming log viewer: monospace rows in a scroller that sticks to the newest row until the user scrolls up, with a button to jump back, and a cap that drops the oldest rows. Rows are appended from script without redrawing the ones already there.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Label` | `string?` |  | attribute `label` | The log's accessible name (aria-label). |
| `Max` | `int` |  | attribute `max` | Most rows kept: once there are more, the oldest are removed. 0 keeps every row. |
| `Rows` | `IReadOnlyList<string>?` |  | attribute `rows` | Replaces every row at once with an array of strings or { text, level, time } objects (level is debug, info, warn or error; time is text, a Date or milliseconds). For a fixed list or a Blazor page; a stream should use append(), which draws only the new rows. Strings only. To stream rows, or to give a level and time, call the element's append() from script (CallAsync("append", ...) on an @ref). |
| `Paused` | `bool` |  | attribute `paused` | True while auto-scroll is paused because the user scrolled up; new rows are still added but the view stays where it is. Scrolling back to the bottom or pressing the resume button clears it. The element owns it while the user scrolls; after pk-pause the host does. Setting it to false jumps to the newest row, setting it to true stops following. |
| `PausedChanged` | `EventCallback<bool>` |  | two-way pair of `Paused` | Raised when Paused changes (two-way binding: `@bind-Paused`). |
| `Live` | `LogLive` | `Polite` `Off` | attribute `live` | Whether a screen reader announces new rows (aria-live). Use off for a fast stream; the rows stay readable by moving through the log. |
| `ResumeLabel` | `string?` |  | attribute `resume-label` | Text of the button shown while auto-scroll is paused. |
| `ChildContent` | `RenderFragment?` |  | default slot | Text shown while the log has no rows (default: No entries yet.). |

Two-way binding: `@bind-Paused`.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-pause` | `PkPauseEventArgs` | Paused: bool? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-log` in the `plainkit-sdk` skill.

## `PkMarkdown`

`<pk-markdown>`: **Markdown** (Data display). A read-only view of Markdown text, drawn with the toolkit's own parser and renderer (the one behind pk-markdown-editor's preview): DOM calls only, so raw HTML is shown as text, a link must be http, https, mailto or a relative address, and nothing from the source becomes markup. The Markdown subset is paragraphs, bold, italic, code, fenced code, headings 1 to 3, quotes, lists and links. A click on a relative link raises pk-link-click, so the app routes or rewrites it. Use it instead of writing rendered HTML into the page yourself.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Markdown` | `string?` |  | attribute `markdown` | The Markdown text to show. Text longer than 200000 characters is cut and a note is added. |
| `InterceptLinks` | `bool` |  | attribute `intercept-links` | Cancel the click on a relative link (not an #anchor, not an absolute address) so the page does not navigate; pk-link-click still reports its address. Without it the link navigates unless a pk-link-click listener calls preventDefault. A click with a modifier key or a middle click is left to the browser. |
| `OnLinkClick` | `EventCallback<PkLinkClickEventArgs>` |  | event `pk-link-click` | A relative link was clicked: href is its address as written. Cancel it (preventDefault), or set interceptLinks, to route or rewrite it yourself. The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-link-click` | `PkLinkClickEventArgs` | Href: string? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-markdown` in the `plainkit-sdk` skill.

## `PkMedia`

`<pk-media>`: **Media** (Media). An image or video in a fixed aspect-ratio box with an optional figure caption and lightbox-ready behaviour.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Ratio` | `string?` |  | attribute `ratio` | Aspect ratio as width/height: 16/9, 4/3, 1/1, 3/4. |
| `Fit` | `MediaFit?` | `Cover` `Contain` | attribute `fit` | How media fills the box. |
| `Caption` | `string?` |  | attribute `caption` | Figure caption. |
| `Lightbox` | `bool` |  | attribute `lightbox` | The box becomes a button that fires pk-open. |
| `Square` | `bool` |  | attribute `square` | Square corners. |
| `OnOpen` | `EventCallback<PkOpenEventArgs>` |  | event `pk-open` | A lightbox request. Cancel if you handle it yourself. The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |
| `ChildContent` | `RenderFragment?` |  | default slot | An img, picture, video or svg. |
| `CaptionContent` | `RenderFragment?` |  | slot `caption` | A rich caption; overrides caption. |

When you set a named fragment (`CaptionContent`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-open` | `PkOpenEventArgs` | X: double?, Y: double?, Target: JsonElement?, Context: string?, Src: string?, Alt: string?, Caption: string?, Reason: string? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-media` in the `plainkit-sdk` skill.

## `PkQuote`

`<pk-quote>`: **Quote** (Data display). A testimonial or pull quote: your own blockquote, with a caption for the portrait, name, role and rating. The element draws a figure and figcaption around your markup and never changes it; before it upgrades (or with JavaScript off) the plain blockquote shows.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Cite` | `string?` |  | attribute `cite` | The source URL (a same-site path or http(s) address). Renders one Source link in the caption; your own blockquote cite attribute is untouched. |
| `Size` | `QuoteSize?` | `Default` `Large` | attribute `size` | large sets bigger type for a lone hero quote. |
| `ChildContent` | `RenderFragment?` |  | default slot | The author's blockquote. |
| `PortraitContent` | `RenderFragment?` |  | slot `portrait` | A pk-avatar or image beside the name. |
| `NameContent` | `RenderFragment?` |  | slot `name` | Who said it. |
| `RoleContent` | `RenderFragment?` |  | slot `role` | Their role or context, under the name. |
| `RatingContent` | `RenderFragment?` |  | slot `rating` | A read-only pk-rating. |

When you set a named fragment (`PortraitContent`, `NameContent`, `RoleContent`, `RatingContent`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

Element details (parts, CSS custom properties, methods, accessibility): `pk-quote` in the `plainkit-sdk` skill.

## `PkSpecimen`

`<pk-specimen>`: **Specimen** (Data display). A design-token sample for spacing, size, radius, shadow and layer tokens, drawn at its resolved value with the token name and value as text.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Token` | `string?` |  | attribute `token` | A CSS custom property to sample, for example "--space-4", "--radius-md", "--shadow-card" or "--z-sticky"; resolved against this element's computed style, so it follows the theme and density in effect. |
| `Kind` | `string?` |  | attribute `kind` | One of space, radius, shadow, size or layer. How to draw the token: a bar of the resolved length (space, size), a corner sample (radius), an elevated tile (shadow) or a chip with the number (layer). Inferred from the token prefix (--space-, --radius-, --shadow-, --z-, --touch-) when empty. |
| `ChildContent` | `RenderFragment?` |  | default slot | Optional label shown instead of the token name. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-specimen` in the `plainkit-sdk` skill.

## `PkStat`

`<pk-stat>`: **Stat tile** (Data display). A headline number with a label, an optional change indicator (arrow, sign and a spoken form), a sparkline and a tone. With an href the whole tile is one link. The tile prop gives the compact dashboard look; interactive makes a tile with no href a button.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `DeltaDirection` | `StatDeltaDirection?` | `Auto` `Up` `Down` `Flat` | attribute `delta-direction` | Override the direction derived from delta. |
| `Label` | `string?` |  | attribute `label` | What the number is. |
| `Value` | `string?` |  | attribute `value` | The headline number, already formatted. |
| `Subtext` | `string?` |  | attribute `subtext` | A line under the value. |
| `Tone` | `PkStatTone?` | `Neutral` `Positive` `Warning` `Critical` | attribute `tone` | Value colour. |
| `Href` | `string?` |  | attribute `href` | Makes the tile a link. |
| `Delta` | `string?` |  | attribute `delta` | The change, for example 12.5 or -3; shows an arrow and a signed number, a percentage unless deltaUnit says points. |
| `DeltaUnit` | `StatDeltaUnit?` | `Percent` `Points` | attribute `delta-unit` | What delta measures: percent shows +12.5%, points shows +4 pts (a score or rate that moved by an absolute amount) and is spoken as points. |
| `Invert` | `bool` |  | attribute `invert` | Down is good news (costs, returns): swaps the trend colours. |
| `Series` | `IReadOnlyList<double>?` |  | attribute `values` | Points for the sparkline (needs at least two). |
| `SubtextContent` | `RenderFragment?` |  | default slot | The subtext; overrides subtext. |
| `IconContent` | `RenderFragment?` |  | slot `icon` | An icon before the value. |
| `OnClick` | `EventCallback<PkActivateEventArgs>` |  | event `pk-activate` | The tile link was pressed. Cancel to stop navigation (a Blazor OnClick handler navigates itself). The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |
| `Tile` | `bool` |  | attribute `tile` | The compact dashboard tile look: less padding, a 600-weight larger value, a panel background. |
| `Interactive` | `bool` |  | attribute `interactive` | Without an href, make the tile a button (Enter and Space) that fires pk-activate. |

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-activate` | `PkActivateEventArgs` | Href: string? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-stat` in the `plainkit-sdk` skill.

## `PkSwatch`

`<pk-swatch>`: **Swatch** (Data display). A colour or design-token sample with its resolved value and a WCAG AA contrast label against a comparison colour.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Token` | `string?` |  | attribute `token` | A CSS custom property to sample, for example "--color-accent"; resolved against this element's computed style. Takes precedence over value. |
| `Value` | `string?` |  | attribute `value` | A literal colour to sample when token is not set, for example "#4e93e3"; useful for showing what a literal would look like (the scorecard and docs tooling, for example). |
| `Compare` | `string?` |  | attribute `compare` | A token or literal colour the swatch is contrasted against; a token is resolved the same way as token. Defaults to the page background. |
| `Large` | `bool` |  | attribute `large` | Grade against the WCAG AA large-text threshold (3:1) instead of normal text (4.5:1). |

Element details (parts, CSS custom properties, methods, accessibility): `pk-swatch` in the `plainkit-sdk` skill.

## `PkTable`

`<pk-table>`: **Table** (Data display). A data table. Two modes: hand it a raw table in the default slot (rows and cells exactly as authored) and it supplies the scroll frame, toolbar, bulk, empty and footer slots; or give it columns and rows and it renders the table itself, with sorting, filtering, selection, loading and phone cards. The data-driven mode: striped, hover, bordered, density, sticky header and first column, sortable headers, filter row, row selection with a bulk bar, loading and empty states, expandable detail rows and a card layout on a phone. Custom cell content goes in slots named cell-<rowId>-<key>; expandable rows show the slot detail-<rowId>. With manual set, the host owns sorting, filtering and paging and the element only renders and reports. At 500 rows or more (a fixed threshold; js/table-vw.js), a non-expandable, non-slotted table windows: only the rows near the scroll frame's viewport are drawn, and sort, filter and selection still act on every row. In data-driven mode, each row carries data-pk-context="<rowId>", so wrapping the whole table in a pk-context-menu can serve different menu content per row (see that element's context field); js/context-actions.js's wireContextMenu(menuEl, { items, run }) is the shared plumbing for that: it paints the { action, label, shortcut?, disabled?, danger? } list a row's own state calls for and dispatches a choice through the same run(action) an existing toolbar or keyboard shortcut already uses (issue #586). With editable set, cells of columns with an editor edit in place (pk-cell-edit, cellErrors); an editable table windows too (#1474): the active row and the row being edited stay drawn wherever they are, arrow keys, PageUp/PageDown and Ctrl+Home/End move by row id, and paste and undo reach rows that are not drawn. Rows must keep a fixed height, so an expandable table never windows (host-slotted cells stay in the light DOM; the card layout on a phone is not measured).

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Striped` | `bool` |  | attribute `striped` | Alternate row tint. |
| `Hover` | `bool` |  | attribute `hover` | Tint the row under the pointer. |
| `Bordered` | `bool` |  | attribute `bordered` | Border on every cell. |
| `Density` | `PkTableDensity?` |  | attribute `density` | The cell padding step; null leaves the element's default. |
| `StickyHeader` | `bool` |  | attribute `sticky-header` | Pin the header row (give the table a MaxHeight). |
| `StickyColumn` | `bool` |  | attribute `sticky-column` | Pin the first column. |
| `MaxHeight` | `string?` |  | attribute `max-height` | Bounds the scrolling frame (a CSS length, for example `24rem`); sticky header and column need it. |
| `Caption` | `string?` |  | attribute `caption` | The table caption, as text. |
| `Label` | `string?` |  | attribute `label` | The accessible name of the table's scrolling region. Give every table one. |
| `Flow` | `bool` |  | attribute `flow` | No scroll frame, for a table inside a container that already scrolls. |
| `ToolbarContent` | `RenderFragment?` |  | slot `toolbar` | Search, filters and buttons above the table (the element's `toolbar` slot; rendered in a `pk-cluster`). |
| `CaptionContent` | `RenderFragment?` |  | slot `caption` | A caption with markup (the `caption` slot). |
| `FooterContent` | `RenderFragment?` |  | slot `footer` | Below the table (the `footer` slot): put a `PkPagination` here. |
| `Columns` | `IReadOnlyList<PkTableColumn<TItem>>?` |  |  | The columns: what the header says, how it sorts, and what fills the cells. A key names an item property in any casing (or is any name, when the column has a Text or a Cell) and is used exactly as given: in the column definition, the row, the cell slot and the sort and filter keys the table reports (Sort, OnSort, Filters). Sent as a JSON attribute ([{ key, label, type, align, sortable, hidePhone, shed, noWrap, title, editor, options, free, required, min, max, maxLength }], camelCase). A column with a Cell template also fills the slot cell-<rowId>-<key> of each row; a column with a Header fragment fills the slot header-<key> beside the title. |
| `Items` | `IReadOnlyList<TItem>?` |  |  | The rows, one item each. In manual mode these are the rows the server returned for the current page. They are sent to the element as JSON (camelCase), so a row holds the item's public properties; a cell template can use the whole item. Sent as a JSON attribute (camelCase, one object per item, plus the row key "id" from IdOf). An item property named Tone (warning, positive, accent or critical) or Indent (1 or 2) marks the row, tint and leading rule included, as in the element. |
| `IdOf` | `Func<TItem, string>?` |  |  | The id of an item: the row key the element reports (pk-row-click, selection, expansion) and the name of the row's slots. Without it the row's index is the id, which is only stable while the list keeps its order. Ids must not contain a double quote. Wrapper parameter: writes the row key (the element is always given rowKey "id") and names each row's slots; without it the row index is the id. |
| `Manual` | `bool` |  | attribute `manual` | The host sorts, filters and pages: the element shows Items exactly as given and only reports the user's sort and filter (OnSort, OnFilter). Set it whenever you load one page at a time. |
| `Sort` | `string?` |  |  | The key of the column the table is sorted by (two-way: @bind-Sort). Null or empty means no column. |
| `SortDirection` | `PkSortDirection?` |  |  | The sort direction; null leaves the element's default (ascending). |
| `Filters` | `Dictionary<string, string>?` |  |  | Text per column key that the rows are filtered by (two-way: @bind-Filters; the filter row needs Filterable). |
| `Selected` | `IReadOnlyList<string>?` |  |  | The ids of the selected rows (two-way: @bind-Selected; the checkbox column needs Selectable). |
| `Expanded` | `IReadOnlyList<string>?` |  |  | The ids of the expanded rows (two-way: @bind-Expanded; needs Expandable and a DetailTemplate). |
| `Selectable` | `bool` |  | attribute `selectable` | A checkbox column with select-all. |
| `SelectAllTotal` | `int` |  |  | How many rows match the current query across all pages (the total attribute). Once every loaded row is selected the table offers "Select all N rows" and raises OnSelectAll. 0 (the default) means not paged: no button. Selected keeps the ids of other pages when you replace Items in manual mode. Rows matching the query across all pages (0 = not paged, no button). Once every loaded row is selected the bulk bar offers Select all N rows and OnSelectAll is raised; Selected keeps the ids of other pages when Items is replaced in manual mode. |
| `SelectScope` | `string?` |  | attribute `select-scope` | "page" or "all" (the select-scope attribute): "all" after the user chose "Select all N rows", when every row of the query counts as selected though Selected holds only the loaded ids. It follows the table and goes back to "page" when the selection changes. page or all: all after the user chose Select all N rows (every row of the query counts as selected; Selected holds only the loaded ids). It follows the element and returns to page when the selection changes. |
| `SelectPageOnly` | `bool` |  | attribute `select-page-only` | Never offer "Select all N rows" (the select-page-only attribute): the header checkbox selects the loaded page and SelectScope stays "page". For a list whose bulk action works only on explicit ids. Never offer Select all N rows (it needs SelectAllTotal): the header checkbox selects the loaded page and SelectScope stays page. |
| `Clickable` | `bool` |  | attribute `clickable` | Rows are activatable: they show a pointer and raise OnRowClick. Each row is a keyboard stop (Enter or Space activates it). |
| `RowHrefTemplate` | `string?` |  | attribute `row-href-template` | Row links: an address with {id} (and any {field}, URL-encoded), like /orders/{id} or #/orders/{id}. The first cell of each row becomes a real link; a plain click goes through the router (pk-navigate). |
| `CurrentRow` | `string?` |  | attribute `current-row` | The id of the row whose record is open in a detail pane: it is tinted and marked aria-current. Set it from the route; the table never changes it (there is no event for it). The id of the row whose record is open in a detail pane: it is tinted and gets aria-current. Set it from the route; the table never changes it. |
| `Filterable` | `bool` |  | attribute `filterable` | A filter input under each header. |
| `Expandable` | `bool` |  | attribute `expandable` | Rows get a toggle that shows their DetailTemplate under them. |
| `Expander` | `string?` |  | attribute `expander` | Set to none to leave out the toggle column of an expandable table: the host opens rows from its own control (a disclosure inside a cell) by setting expanded, which is still honoured, and the detail-<rowId> slots are still shown for the ids in it. "none" leaves out the built-in toggle column; the host then opens rows through Expanded. |
| `Editable` | `bool` |  | attribute `editable` | Cells of columns that have an Editor edit in place (Enter, F2, or a second tap), and OnCellEdit reports each accepted value. Update your item in the handler (then Refresh, or pass a new list): the element already shows the value. An editable table of 500 or more rows windows like a read-only one (the active and edited rows stay drawn). Inline cell editing. A column gets an Editor (Text, Number, Select with Options, Date: the item's value is an ISO date string such as 2026-01-07) and optionally Required, Min, Max, MaxLength; a column without an Editor, or with a Cell template, is read-only. For a switch in a cell put a PkSwitch with tabindex="-1" and its own name (its text, in a u-sr-only span) in the column's Cell template and update your item from its CheckedChanged. Update the item in OnCellEdit. |
| `Resizable` | `bool` |  | attribute `resizable` | Columns get a resize handle (OnColumnResize). |
| `Reorderable` | `bool` |  | attribute `reorderable` | Columns can be moved (OnColumnReorder). |
| `CellErrors` | `Dictionary<string, string>?` |  |  | Cells to mark invalid, by "<rowId>:<columnKey>", with the message to show (server-side validation of an edit). The table never changes it. Cells to mark invalid, keyed "<rowId>:<columnKey>", with the message. The table never changes it. |
| `Loading` | `bool` |  | attribute `loading` | Show a placeholder row instead of the rows, announce Loading and mark the table busy. |
| `EmptyText` | `string?` |  | attribute `empty-text` | The text shown when there are no rows; EmptyContent replaces it. Null leaves the element's own text. |
| `Cards` | `bool` |  | attribute `cards` | Each row becomes a card below 640px. |
| `BulkContent` | `RenderFragment?` |  | slot `bulk` | Actions shown while rows are selected (the bulk slot; raw mode has no rows to select, so it stays here rather than in PkTableBase). |
| `EmptyContent` | `RenderFragment?` |  | slot `empty` | The empty state, for example a PkEmptyState (the empty slot). It replaces EmptyText (raw mode has no empty state of its own, so this stays here rather than in PkTableBase). |
| `FootContent` | `RenderFragment?` |  | slot `foot` | The tfoot (not FooterContent, the slot below the table). Raw mode: the tfoot rows. Data-driven: a footer or summary row, one cell spanning every column (a trailing card on a phone). Data-driven: a footer or summary row (one cell spanning every column; a trailing card on a phone). Raw mode (ChildContent set) renders it as the tfoot of the authored table instead. |
| `DetailTemplate` | `RenderFragment<TItem>?` |  |  | The detail of one row, shown under it when the user expands it (the element's detail-<rowId> slot). Needs Expandable. The slot name is dynamic: the component renders one slot="detail-<rowId>" per row when Expandable is set, and only those rows get a toggle. |
| `DetailPanels` | `IReadOnlyDictionary<string, RenderFragment<TItem>>?` |  |  | Further named detail panels of a row, by panel name (the element's detail-<rowId>.<name> slots), next to or instead of DetailTemplate. Needs Expandable. A named panel has no toggle: the host opens it by putting "<rowId>.<name>" in Expanded (Expander "none" drops the toggle column), and several can be open at once. The slot name is dynamic: the component renders one slot="detail-<rowId>.<name>" per panel and row when Expandable is set; the element shows the ones named in expanded. |
| `ChildContent` | `RenderFragment?` |  |  | Raw mode: the table's tbody rows (typically a @foreach over your own collection). Set, the table is not data-driven: Columns and Items are ignored, and use TItem="object". |
| `HeadContent` | `RenderFragment?` |  |  | Raw mode: the thead, e.g. one tr of th cells. Left off, no header row. |
| `TableClass` | `string?` |  |  | Raw mode: a class for the inner table (the frame takes class and other attributes). |
| `IsEmpty` | `bool` |  |  | Raw mode: no rows. With EmptyContent or EmptyText set, that is shown instead of the table. |
| `OnSort` | `EventCallback<PkSortEventArgs>` |  | event `pk-sort` | The user chose a sortable header: Key and Direction (ascending or descending). Sort and SortDirection already follow it. In manual mode, load the rows in that order. A third activation of the same header clears the sort: the event then carries a null key and direction, and the wrapper restores the data source's natural order. |
| `OnFilter` | `EventCallback<PkFilterEventArgs>` |  | event `pk-filter` | A filter input changed (debounced by the element): Filters holds the text per column key. |
| `OnSelect` | `EventCallback<PkSelectEventArgs>` |  | event `pk-select` | The selection changed: Selected holds the ids. |
| `OnSelectAll` | `EventCallback<PkSelectAllEventArgs>` |  | event `pk-select-all` | The user selected every loaded row (Scope "page", Count the rows on the page) or chose "Select all N rows" (Scope "all", Count the total; no ids are sent: run the action from your query). Needs SelectAllTotal. Scope (page or all) and Count. No ids are sent for all: run the action from the query you hold. |
| `OnRowClick` | `EventCallback<PkTableRowClickArgs<TItem>>` |  | event `pk-row-click` | A row of a Clickable table was activated: its id and item. Raised by a click and, on a clickable row, by Enter or Space on the focused row (each row is a tab stop). |
| `Navigate` | `string?` |  | attribute `navigate` | Who follows a row link: router (default, empty), host or none. Who follows a RowHrefTemplate link on a plain click: "router" (default, or leave it unset) lets the app router handle the cancelable pk-navigate; "host" raises OnNavigate as a notification the router ignores and nothing navigates, so your handler navigates (NavigationManager); "none" raises nothing and the browser follows the address. |
| `OnNavigate` | `EventCallback<PkNavigateEventArgs>` |  | event `pk-navigate` |  |
| `OnRowExpand` | `EventCallback<PkTableRowExpandArgs<TItem>>` |  | event `pk-row-expand` | The user expanded or collapsed a row: its id, item and the new state. Expanded already follows it. |
| `OnCellEdit` | `EventCallback<PkTableCellEditArgs<TItem>>` |  | event `pk-cell-edit` | The user edited a cell of an Editable table: the row's id and item, the column key, and the new and previous values. The event cannot be cancelled from Blazor (the browser does not wait); to refuse a value, set CellErrors and reset your item. An edited cell was accepted. Blazor cannot cancel the event (the browser does not wait for the server): to refuse a value, put a message in CellErrors and put the previous value back in your item. A paste from a spreadsheet or a Ctrl+D fill-down raises it once per accepted cell (Source is paste or fill) (OnCellsPaste summarises the paste). |
| `OnCellsPaste` | `EventCallback<PkCellsPasteEventArgs>` |  | event `pk-cells-paste` | A paste into an editable table finished. cells lists the cells that took a value ({ id, key, value }); rejected lists those that did not ({ row, col, reason }: outside the grid, readonly, a failed check message, or refused by a cancelled pk-cell-edit). Each accepted cell already raised pk-cell-edit with source paste. A paste into an Editable table finished: Cells lists the cells that took a value ({ id, key, value }), Rejected those that did not ({ row, col, reason }). Each accepted cell has already raised OnCellEdit (Source is paste). |
| `OnColumnResize` | `EventCallback<PkColumnResizeEventArgs>` |  | event `pk-column-resize` | A column was resized. Key and Width in px; the table keeps the width itself. |
| `OnColumnReorder` | `EventCallback<PkColumnReorderEventArgs>` |  | event `pk-column-reorder` |  |
| `SortChanged` | `EventCallback<string?>` |  | two-way pair of `Sort` | Raised with the new value of Sort (two-way: Sort follows it). |
| `SortDirectionChanged` | `EventCallback<PkSortDirection?>` |  | two-way pair of `SortDirection` | Raised with the new value of SortDirection (two-way: SortDirection follows it). |
| `FiltersChanged` | `EventCallback<Dictionary<string, string>?>` |  | two-way pair of `Filters` | Raised with the new value of Filters (two-way: Filters follows it). |
| `SelectedChanged` | `EventCallback<IReadOnlyList<string>?>` |  | two-way pair of `Selected` | Raised with the new value of Selected (two-way: Selected follows it). |
| `ExpandedChanged` | `EventCallback<IReadOnlyList<string>?>` |  | two-way pair of `Expanded` | Raised with the new value of Expanded (two-way: Expanded follows it). |

Two-way binding: `@bind-Sort`, `@bind-SortDirection`, `@bind-Filters`, `@bind-Selected`, `@bind-Expanded`.

When you set a named fragment (`ToolbarContent`, `CaptionContent`, `FooterContent`, `BulkContent`, `EmptyContent`, `FootContent`, `DetailTemplate`, `HeadContent`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-cell-edit` | `PkCellEditEventArgs` | Id: string?, Index: double?, Row: JsonElement?, Key: string?, Value: JsonElement?, Previous: JsonElement?, Source: string?, Wait: JsonElement? |
| `pk-cell-query` | `PkCellQueryEventArgs` | Id: string?, Key: string?, Query: string? |
| `pk-cells-paste` | `PkCellsPasteEventArgs` | Cells: JsonElement?, Rejected: JsonElement? |
| `pk-column-reorder` | `PkColumnReorderEventArgs` | Key: string?, From: double?, To: double?, Order: string[]? |
| `pk-column-resize` | `PkColumnResizeEventArgs` | Key: string?, Width: double? |
| `pk-filter` | `PkFilterEventArgs` | Filters: Dictionary<string, string>? |
| `pk-navigate` | `PkNavigateEventArgs` | To: string?, Href: string?, Id: string?, Anchor: string?, Replace: bool? |
| `pk-row-click` | `PkRowClickEventArgs` | Id: string?, Row: JsonElement? |
| `pk-row-expand` | `PkRowExpandEventArgs` | Id: string?, Index: double?, Expanded: bool? |
| `pk-select-all` | `PkSelectAllEventArgs` | Scope: string?, Count: double? |
| `pk-select` | `PkSelectEventArgs` | Item: JsonElement?, Value: string?, Checked: bool?, Id: string?, Selected: string[]?, Scope: string?, Query: JsonElement? |
| `pk-sort` | `PkSortEventArgs` | Key: string?, Direction: string? |

**Not available as parameters** (they do not exist; do not use them)

| Parameter | Why |
|---|---|
| `Bordered` | declared by the base class PkTableBase |
| `Caption` | declared by the base class PkTableBase |
| `CaptionContent` | declared by the base class PkTableBase |
| `Density` | declared by the base class PkTableBase |
| `Flow` | declared by the base class PkTableBase |
| `FooterContent` | declared by the base class PkTableBase |
| `Hover` | declared by the base class PkTableBase |
| `Label` | declared by the base class PkTableBase |
| `MaxHeight` | declared by the base class PkTableBase |
| `StickyColumn` | declared by the base class PkTableBase |
| `StickyHeader` | declared by the base class PkTableBase |
| `Striped` | declared by the base class PkTableBase |
| `ToolbarContent` | declared by the base class PkTableBase |

Element details (parts, CSS custom properties, methods, accessibility): `pk-table` in the `plainkit-sdk` skill.

## `PkTableFilters`

`<pk-table-filters>`: **Table filters** (Data display). A debounced global search box plus a Filters trigger (with an active-count badge) that opens a panel around your own filter fields, so a data table or list gets consistent search/filter UI without every page rebuilding the trigger-button-plus-badge-plus-panel shell by hand. Slot your own filter fields (selects, date ranges, whatever the fields are) into the default slot; put the element itself in pk-table's toolbar slot. It never searches, filters or clears anything itself: it only reports pk-search, pk-toggle and pk-clear-filters for the host to act on, the same as pk-table's own pk-sort and pk-filter.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Label` | `string?` |  | attribute `label` | The search box's accessible name, and its placeholder unless placeholder is set. |
| `FilterCount` | `int` |  | attribute `filter-count` | Active-filter count shown as a badge on the Filters trigger. The host counts its own slotted fields; 0 hides the badge. |
| `Debounce` | `int` |  | attribute `debounce` | Milliseconds between the last keystroke and pk-search. |
| `Open` | `bool` |  | attribute `open` | Whether the filter panel is open. The user opens and closes it with the Filters trigger and the panel's close button, and the element raises pk-toggle; a change the host makes raises nothing. |
| `OpenChanged` | `EventCallback<bool>` |  | two-way pair of `Open` | Raised when Open changes (two-way binding: `@bind-Open`). |
| `OnSearch` | `EventCallback<PkSearchEventArgs>` |  | event `pk-search` | The search box changed (debounced 250 ms). The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |
| `OnClearFilters` | `EventCallback` |  | event `pk-clear-filters` | Clear filters was activated in the panel. The host owns the slotted fields; it clears their own state. |
| `ChildContent` | `RenderFragment?` |  | default slot | Your own filter fields (selects, date ranges, text inputs), shown in the filter panel. The Filters trigger only appears once this slot has content. |

Two-way binding: `@bind-Open`.

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-search` | `PkSearchEventArgs` | Value: string?, Query: string? |
| `pk-toggle` | `PkToggleEventArgs` | Open: bool?, Expanded: bool?, Pressed: bool?, Value: string?, Id: string? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-table-filters` in the `plainkit-sdk` skill.

## `PkTag`

`<pk-tag>`: **Tag** (Data display). A neutral label that can carry a remove button.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Removable` | `bool` |  | attribute `removable` | Show the remove button. |
| `Value` | `string?` |  | attribute `value` | Sent in the remove event (the identifier, not state); defaults to the text. |
| `Disabled` | `bool` |  | attribute `disabled` | Dim the tag and disable the button. |
| `Controlled` | `bool` |  | attribute `controlled` | The host removes the tag: a press only raises pk-remove and never removes the element (the host renders the list, so it removes the tag from its own state). Used by the Blazor wrapper. |
| `ChildContent` | `RenderFragment?` |  | default slot | The label. |
| `OnRemove` | `EventCallback<PkRemoveEventArgs>` |  | event `pk-remove` | The remove button was pressed. preventDefault keeps the tag; when nobody cancels it and controlled is off, the tag removes itself after the event. With controlled the host removes it. The event can be cancelled in the browser (preventDefault); a callback cannot cancel it. |

**Event args**

| Event | Args class | Fields |
|---|---|---|
| `pk-remove` | `PkRemoveEventArgs` | Index: double?, Src: string?, Value: string? |

Element details (parts, CSS custom properties, methods, accessibility): `pk-tag` in the `plainkit-sdk` skill.

## `PkTimeline`

`<pk-timeline>`: **Timeline** (Data display). A vertical activity feed of timeline items joined by a line.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Label` | `string?` |  | attribute `label` | Accessible name of the list. |
| `ChildContent` | `RenderFragment?` |  | default slot | pk-timeline-item children, in the order to show them. |

Element details (parts, CSS custom properties, methods, accessibility): `pk-timeline` in the `plainkit-sdk` skill.

## `PkTimelineItem`

`<pk-timeline-item>`: **Timeline item** (Data display). One event on a timeline: a marker, a heading, a time and a body.

**Parameters**

| Parameter | Type | Enum values | Sets | Description |
|---|---|---|---|---|
| `Heading` | `string?` |  | attribute `heading` | Title of the event. |
| `Time` | `string?` |  | attribute `time` | An ISO date or any text; shown in a time element. |
| `Status` | `TimelineItemStatus?` | `Pending` `Done` `Active` `Danger` | attribute `status` | Marker colour and fill. |
| `ChildContent` | `RenderFragment?` |  | default slot | The body. |
| `HeadingContent` | `RenderFragment?` |  | slot `heading` | A rich heading; overrides heading. |
| `TimeContent` | `RenderFragment?` |  | slot `time` | A rich time; overrides time. |

When you set a named fragment (`HeadingContent`, `TimeContent`), write the body as an explicit `<ChildContent>` tag too: Razor does not allow an implicit body next to a named fragment.

Element details (parts, CSS custom properties, methods, accessibility): `pk-timeline-item` in the `plainkit-sdk` skill.
