Simplify Custom JSX v2 authoring

This commit is contained in:
Thomas Camlong committed 2026-07-21 08:19:22 +02:00
1 parent 1faaf29c3b
commit 05dbad8069
177 files changed
+4775 -43426

No files matched your search

+1 -1
View File
@@ -91,7 +91,7 @@ Custom JSX v2 provides four HTTP discovery endpoints:
- `GET /api/custom-widgets/authoring-prompt` returns the current prompt plus MCP and HTTP resource identifiers.
- `GET /api/custom-widgets/schema` returns the canonical widget JSON Schema.
- `GET /api/custom-widgets/components` returns the release-matched static component catalog.
- `GET /api/custom-widgets/skill` returns the complete Agent Skill files, catalog, examples, and skills.sh metadata.
- `GET /api/custom-widgets/skill` returns the concise live `SKILL.md` plus skills.sh, source, and install metadata. Retrieve the schema, component catalog, and examples through their dedicated resources only when needed.
Call `customWidget_secretRequestUser` to create a temporary credential entry URL. Do not construct or save this URL.
@@ -14,18 +14,9 @@ Install the Agent Skill from the verified Homarr repository:
npx skills add https://github.com/homarr-labs/homarr --skill homarr-custom-widget
```
The portable skill gives authoring instructions that do not require a tool. The Homarr MCP prompt gives instructions for a connected workflow.
The portable skill gives concise authoring guidance. The Homarr MCP prompt gives connected agents a retrieval workflow: read the live schema, then fetch only the component documentation needed for the widget.
The offline copy prompt includes the complete portable skill and all eleven release-matched references, including the canonical widget JSON Schema, minified component catalog, and canonical runtime examples. A model can author directly from this bundle without MCP tools or repository access.
The prompt starts with the user's widget request. It ends with:
```text
--- END HOMARR CUSTOM WIDGET OFFLINE BUNDLE ---
Characters: <exact clipboard character count>
```
Homarr computes the total after assembling the prompt, including the digits in the total itself. The sentinel and count make clipboard truncation detectable. A missing sentinel or mismatched length means the bundle is incomplete and should be copied again.
The workbench's **Copy AI prompt** action is separate. It creates a self-contained prompt below 12,000 characters for an ordinary ChatGPT or Claude conversation. It includes the lean contract, bindings, recommended components, and two polished examples. It never tells an offline model to call MCP tools.
The source is in [`.agents/skills/homarr-custom-widget`](https://github.com/homarr-labs/homarr/tree/HEAD/.agents/skills/homarr-custom-widget). `HEAD` follows the repository default branch, so the link is not tied to an unreleased feature branch. Homarr does not provide a ZIP archive.
@@ -43,23 +34,20 @@ Repository code cannot give this attribution.
The authoring-prompt tool description contains one sentence. The prompt contains the workflow instructions and resource links.
`GET /api/custom-widgets/skill` and the MCP tool return the complete release-matched skill discovery payload:
`GET /api/custom-widgets/skill` and the MCP tool return the concise skill discovery payload:
```ts
{
name: "homarr-custom-widget";
version: string;
skillMd: string;
files: Array<{ path: string; content: string }>;
componentCatalog: CustomJsxAuthoringCatalog;
examples: CustomJsxExample[];
skillsShUrl: string;
sourceUrl: string;
installCommand: string;
}
```
The `homarr://custom-widgets/skill` MCP resource returns the same material as one plain-text offline bundle, ending with the truncation sentinel and exact character count.
The full installed component catalog stays available through `homarr://custom-widgets/components` and per-component resources. It is deliberately not embedded in the skill or clipboard prompt.
These interfaces do not return an archive URL or archive hash.
@@ -82,3 +70,17 @@ These interfaces do not return an archive URL or archive hash.
Each credential URL applies to one definition or preview, one source, and the required fields. The agent receives only the request status.
Homarr encrypts the plaintext immediately. Homarr does not return the plaintext.
## Evaluate the copied prompt
Homarr maintainers can run the same self-contained prompt that the workbench copies through the live generation-quality suite:
```sh
OPENROUTER_API_KEY=... pnpm --filter @homarr/custom-widgets test:ai
```
The suite uses DeepSeek V4 Pro to author five complex widgets and DeepSeek V4 Flash to judge only widgets that pass Homarr's real parser and canonical schema. It saves every response, validated `widget.json`, `widget.jsx`, and detailed category scores under `packages/custom-widgets/.ai-evaluations/`.
The run has a hard budget of ten generation attempts across all scenarios. Override the models or budget with `OPENROUTER_GENERATOR_MODEL`, `OPENROUTER_JUDGE_MODEL`, and `CUSTOM_WIDGET_AI_MAX_LOOPS`. Generated artifacts are local and ignored by Git; promote a reviewed, validated result into the bundled-widget source deliberately.
The reference API check uses a real PokeAPI list/detail request and a read-only Portainer container query when `PORTAINER_BASE_URL` and `PORTAINER_API_KEY` are set. Use `pnpm --filter @homarr/custom-widgets test:reference-apis -- --pokeapi-only` for the public PokeAPI check alone. To exercise start, restart, and stop against a disposable container, also set `PORTAINER_TEST_CONTAINER_ID` and the explicit safeguard `PORTAINER_ALLOW_MUTATIONS=1`; the script restores the container's original running state.
@@ -1,150 +1,16 @@
# Create a Custom Widget
import { CanonicalCustomWidgetExample, CustomWidgetCodeExample } from "@site/src/components/custom-widget-code";
import { CanonicalCustomWidgetExample } from "@site/src/components/custom-widget-code";
export const explicitOptionsBindingExample = `{
"pathTemplate": "/api/endpoints/{endpointId}/docker/containers/json",
"parameters": { "endpointId": "string", "showAll": "boolean" },
"queryTemplate": { "all": { "$param": "showAll" } },
"optionsBinding": {
"endpointId": { "$option": "endpointId" },
"showAll": false
}
}`;
The workbench is visual-first: configure the default API connection, add request cards, add option rows, then write JSX. Additional sources and raw manifest JSON remain under Advanced. Code and preview sit side by side on desktop and use tabs on mobile.
The desktop workbench shows the code and preview next to each other. On a mobile device, use the Configure and Preview tabs.
## Manual workflow
The save and preview actions remain available when the form scrolls.
Use CodeMirror to edit requests, widget options, and JSX. The editor gives completions for the installed Mantine components.
The editor also gives completions for identifiers, widget options, temporary inputs, runtime components, and icons.
Use the request toolbar to go to a declaration. The rename action changes all related request references.
The rename operation updates `invalidates`, dynamic widget option, and JSX references as one transaction.
Save errors appear as selectable text. Use **Copy errors** to copy them.
Select **Schema** next to Undo, Redo, and Format. This action shows the generated JSON Schema and two examples.
Homarr generates these references from the canonical Zod schemas. Imports, MCP, Workshop, and saved definitions use the same schemas.
## Create a Custom Widget manually
1. Add the API source.
2. Select its network scope.
3. Select its authentication mode.
4. Enter the credentials.
5. Declare the named requests.
6. Define the widget options.
7. Write JSX with `data`, `status`, `options`, and temporary `inputs`.
8. Select **Test and preview**.
9. Wait for the load requests to stop.
10. Open the Data tab to examine responses and request errors.
11. Preview the compact, standard, and wide layouts.
12. Examine the runtime and accessibility diagnostics.
13. Save the definition.
14. Select the definition and its widget options in the board widget picker.
Homarr does not show existing plaintext credentials. It shows only the configuration status.
The preview action shows a loading state during load requests. The Data tab indicator shows green or red after completion.
The preview renders again when request data becomes available.
## Create a Custom Widget with ChatGPT
Enter a short request. You can also enter an API documentation URL.
Select **Copy AI prompt**. The menu also provides **Copy with embedded skill**, a complete release-matched offline prompt.
Both prompts support offline authoring. They do not require MCP resources, MCP tools, or file-system access.
Paste one prompt into ChatGPT or a different model. Tell the model to return exactly these two blocks:
1. A fenced `json` block with one complete widget and `template: "__HOMARR_TEMPLATE__"`.
2. A fenced `jsx` block with the readable template.
Select **Paste AI response** to fill the workbench. Enter the credentials.
Test the requests. Preview the widget.
If the save operation fails, copy the validation errors. Give the error text to the model.
Homarr does not make a separate correction prompt. Homarr does not assume that the model can read local skill files.
The standard prompt contains fewer than 8,000 characters. It includes the installed Mantine version and the capability rules.
It does not include a complete prop list, MCP workflow, server URL, or credentials. The prompt with the embedded skill is intentionally longer. It starts with your widget request, then includes:
- `SKILL.md` and all eleven release-matched reference files, including the canonical widget JSON Schema
- The Custom Widget, Mantine, skill, and catalog-schema versions
- The compact canonical component catalog
- Canonical runtime examples
The generated Markdown references remain readable offline. The compact JSON catalog gives tools the identical machine-readable component records used by Homarr.
The embedded prompt ends with this sentinel and an exact total character count:
```text
--- END HOMARR CUSTOM WIDGET OFFLINE BUNDLE ---
Characters: <exact clipboard character count>
```
The count includes the whole prompt and the count itself. If the sentinel is missing or the number does not match the clipboard length, copy the prompt again before sending it to a model.
The embedded content explicitly tells a model to author directly from the offline bundle. It does not claim that MCP tools, Homarr resources, repository files, or file-system access exist in the chat.
## Configure widget options
Widget options use a restricted object JSON Schema. Homarr supports the following controls:
- Text
- Text area
- Number
- Switch
- Select
- Multi-select
- Slider
- Date
- Time
- Color
- URL
- Nested group
- Conditional field
- JSON value
Credentials are not widget options. Homarr rejects credential names and static credential values in widget options.
Configure authentication in the API source. Homarr then keeps the values encrypted and excludes them from exports and board configuration.
Dynamic selects use `x-homarr.optionsSource` and a named query. Add `itemsPath` when the response contains the result array in an object.
Set `valuePath` and `labelPath` relative to each item. These requests run only during widget option configuration.
The requests use the same protected server executor as runtime named requests.
### Bind named request parameters
Request parameters map each parameter name to `string`, `number`, or `boolean`. Query and body templates use `{ "$param": "name" }` references.
A parameter declaration does not give a value to the parameter. A matching widget option or temporary input does not create a binding.
For a load query, bind each parameter in `optionsBinding`. Use an option reference or a type-compatible primitive literal:
<CustomWidgetCodeExample
id="explicit-options-binding"
label="Explicit load-query option bindings"
language="json"
code={explicitOptionsBindingExample}
/>
For a manual query or action, supply all parameters with the component `params` prop. Use `SubFetch`, `ActionButton`, or `ToggleSwitch`.
Only this prop can send JSX values to a request. These values can include temporary inputs and loop variables.
Dynamic widget option requests run without a JSX invoker. Add an `optionsBinding` entry for each declared parameter.
1. Configure the default API source, network scope, authentication, and credential.
2. Add query/action cards. Values can be literals, widget options, or invocation parameters.
3. Add widget options with their control and default value.
4. Write responsive JSX using `data`, `status`, `options`, and temporary `inputs`.
5. Test and preview, inspect Data and the redacted request journal, then save.
<CanonicalCustomWidgetExample
id="named-widget-requests"
@@ -153,20 +19,19 @@ Dynamic widget option requests run without a JSX invoker. Add an `optionsBinding
height="420px"
/>
### Define the widget options schema and defaults
<CanonicalCustomWidgetExample id="widget-options" label="Widget options" example="options" height="360px" />
Default values must conform to the widget options schema. Do not put secrets in these documents.
Every option has a default. Supported controls include text, textarea, number, switch, select, multi-select, slider, date, time, color, icon, URL, duration, time zone, and JSON. Dynamic choices use `choicesFrom` with a query ID and value/label paths.
<CanonicalCustomWidgetExample
id="widget-options-schema"
label="Widget options schema"
example="optionsSchema"
height="380px"
/>
## ChatGPT or Claude
<CanonicalCustomWidgetExample
id="widget-default-options"
label="Default option values"
example="defaultOptions"
height="160px"
/>
Enter the desired widget and optional API documentation URL, then select **Copy AI prompt**. The single self-contained prompt is below 12,000 characters, starts with your request, includes the lean contract and two polished examples, and never claims that an offline chat has MCP tools.
The model must return exactly:
1. One fenced `json` block with `template: "__HOMARR_TEMPLATE__"`.
2. One fenced `jsx` block with the readable template.
Select **Paste AI response**. Homarr fills the visual builders and shows all actionable schema or JSX errors inline. Enter credentials separately. The prompt deliberately recommends a focused Mantine component set, visual hierarchy, responsive layout, theme compatibility, and loading, empty, failure, and success states.
Connected agents should use the skills.sh skill and retrieve the live schema and individual component metadata only as needed.
@@ -1,244 +1,44 @@
# Use the Custom JSX runtime
# Use the safe JSX runtime
import { CustomJsxComponentReference } from "@site/src/components/custom-jsx-component-reference";
import { CustomWidgetCodeExample } from "@site/src/components/custom-widget-code";
Templates are JSX expressions interpreted inside a widget-local safety and render budget. They can read:
export const dataRootsExample = `<Stack>
<TextInput bind="search" label="Search TV shows" />
<SubFetch requestId="search-series" params={{ query: inputs.search }} />
{data["search-series"]?.map((series) => (
<Group key={series.tvdbId}>
<Text>{series.title}</Text>
<ActionButton requestId="monitor-series" params={{ tvdbId: series.tvdbId }}>
Monitor
</ActionButton>
</Group>
))}
</Stack>`;
- `data.requestId` — the latest response
- `status.requestId` — `{ loading, ok, status, statusText, error }`
- `options.name` — saved widget configuration
- `inputs.name` — temporary values from declarative `bind`
export const localBindingExample = `{data.pokemon.map((pokemon) => {
const segments = pokemon.url.split("/").filter((part) => part);
const dexId = segments.at(-1);
const isSelected = inputs.selectedPokemon === pokemon.name;
Use normal Mantine layouts, typography, feedback, dates, and charts. Official compound names such as `Card.Section`, `Tabs.List`, and `Radio.Group` are supported. Ordinary safe props pass through; a dangerous capability produces an explicit error.
return (
<Card key={dexId} withBorder={isSelected}>
<Text>#{dexId}</Text>
<Text tt="capitalize">{pokemon.name}</Text>
</Card>
);
})}`;
export const derivedValueExample = `{(() => {
const visiblePokemon = data.pokemon.filter(
(pokemon) => options.types.length === 0 || options.types.includes(pokemon.type),
);
return (
## Temporary inputs
```jsx
<Stack>
{visiblePokemon.map((pokemon) => (
<Text>{pokemon.name}</Text>
))}
<TextInput bind="search" defaultValue="" label="Search" />
<SubFetch requestId="search" params={{ query: inputs.search }}>
{(results) => (
<Stack>
{(results ?? []).map((item) => (
<Text key={item.id}>{item.name}</Text>
))}
</Stack>
)}
</SubFetch>
</Stack>
);
})()}`;
export const recursiveListExample = `<RecursiveList
data={data.evolution.chain}
childrenPath="evolves_to"
keyPath="species.name"
defaultExpandedDepth={4}
maxDepth={16}
showLines
>
{(node, meta) => {
const segments = node.species.url.split("/").filter((part) => part);
const dexId = segments.at(-1);
return (
<Group gap="xs">
<Badge variant="light">#{dexId}</Badge>
<Text tt="capitalize" fw={600}>
{node.species.name}
</Text>
{meta.hasChildren && <Text c="dimmed">{meta.childCount} evolutions</Text>}
</Group>
);
}}
</RecursiveList>`;
export const temporaryInputsExample = `<TextInput bind="search" defaultValue="" />
<DatePicker bind="selectedDate" />
<Switch bind="enabled" defaultChecked={false} />
<MultiSelect bind="selectedEntities" data={options.entities} />
<SubFetch requestId="search" params={{ query: inputs.search }} />`;
Homarr interprets declarative JSX in the error boundary of the widget. A template cannot import a module or run arbitrary JavaScript.
## Use data roots
<CustomWidgetCodeExample
id="custom-jsx-data-roots"
label="Data roots and manual requests"
language="jsx"
code={dataRootsExample}
/>
- `data.<requestId>` contains sanitized JSON or text results.
- `status.<requestId>` contains `{ loading, ok, status, statusText, error }`.
- `options` contains the validated board item configuration.
- `inputs` contains temporary values from controls that use `bind`.
Check `status.list?.loading` while loading, `status.list?.ok === false` on failure, and `status.list?.ok === true` on success. Do not compare `status.list` with `"loading"`, `"success"`, or `"error"`; the status is an object, and validation rejects those comparisons.
Load queries get data automatically. They run at the widget refresh interval.
`SubFetch` runs a manual query. `ActionButton` and `ToggleSwitch` run actions.
## Use Mantine components
Homarr discovers exports from the installed Mantine Core, Dates, and Charts packages. It generates prop metadata from the installed TypeScript definitions.
Homarr passes ordinary serializable props to Mantine. It sanitizes style props, `style`, and `styles`.
Homarr scopes these styles to the widget tile. Safe new Mantine exports become available without a hand-maintained allowlist.
The capability denylist blocks providers, portals, app shells, modals, drawers, file controls, and low-level callback props.
Use the safe Tabler icon resolver. For example, use `<TablerIcon name="server" size={18} />`.
The editor completes installed icon names. `homarr://custom-widgets/components/TablerIcon` gives the same names to MCP agents.
The runtime blocks these capabilities:
- Imports
- Hooks
- Refs
- Raw callback props
- Polymorphic roots
- Provider replacement
- Arbitrary portal targets
- Browser network requests
- npm packages
- Code evaluation
- Prototype-pollution keys
- Unsafe URLs
- Global selectors
- CSS that escapes the widget tile
### Browse the release-matched component API
Search the generated catalog before guessing a component or prop name. Expand a component to see its actual prop types, required props, literal values, temporary-input adapter, subcomponents, accessibility notes, and a JSX usage sketch.
The explorer fetches only the plain, versioned static catalog. It does not import or execute Mantine or Homarr component objects in this documentation page. Select **Open raw JSON** to give the exact same artifact to an offline tool.
<CustomJsxComponentReference />
## Derive values with immutable local bindings
A collection callback can use a restricted block when the returned JSX needs the same derived value more than once:
<CustomWidgetCodeExample
id="custom-jsx-local-bindings"
label="Immutable local bindings"
language="jsx"
code={localBindingExample}
/>
The block must contain one or more `const` declarations followed by exactly one final `return`. A declaration uses one simple identifier and a required initializer. An earlier local can be used by later declarations and the final JSX.
For one page-level derived value, directly invoke a zero-argument inline arrow:
<CustomWidgetCodeExample
id="custom-jsx-derived-iife"
label="Page-level derived value"
language="jsx"
code={derivedValueExample}
/>
This is not general JavaScript execution. The runtime rejects:
- `let`, `var`, assignment, update expressions, and destructuring
- Functions or classes stored in a local
- `if`, loops, `switch`, `try`, `throw`, labels, `new`, async code, and generators
- Early or multiple returns
- Duplicate locals or names that shadow `data`, `status`, `options`, or `inputs`
- IIFEs with parameters or arguments
- Calling functions obtained from API data
Local evaluation shares the widget's operation, depth, collection, string, and rendered-node limits. A block cannot create a new budget.
Relevant diagnostics include `INVALID_LOCAL_DECLARATION`, `LOCAL_BINDING_REQUIRES_INITIALIZER`, `DUPLICATE_LOCAL_BINDING`, `RESERVED_LOCAL_BINDING`, `UNSUPPORTED_BLOCK_STATEMENT`, `BLOCK_REQUIRES_FINAL_RETURN`, and `CALLBACK_VALUE_NOT_ALLOWED`.
## Render arbitrary-depth data with RecursiveList
Use the trusted `RecursiveList` special form for evolution chains, directory trees, nested comments, and other unknown-depth data. Do not write an authored recursive function or pass Mantine a render callback.
<CustomWidgetCodeExample
id="custom-jsx-recursive-list"
label="Trusted recursive list"
language="jsx"
code={recursiveListExample}
height="420px"
/>
The child template receives the current `node` and this metadata:
```ts
interface RecursiveListMetadata {
depth: number;
index: number;
path: number[];
key: string;
hasChildren: boolean;
childCount: number;
isLast: boolean;
}
```
`data` accepts one root object or an array of roots. `childrenPath` and `keyPath` are restricted dotted paths; prototype-related segments are blocked. Keys resolve to primitives. Missing or duplicate keys receive a stable path fallback.
Bound values live only in React memory while the widget is mounted. They are not saved to the database, board options, exports, browser storage, or `localStorage`. Remounting resets them. Components can share a binding when their adapter types agree.
The authorable presentation props are `data`, `childrenPath`, `keyPath`, `maxDepth`, `maxNodes`, `defaultExpandedDepth`, `indent`, `gap`, and `showLines`. Defaults are 16 levels and 500 nodes; hard limits are 32 levels and 2,000 nodes.
## Expression language
Homarr detects cycles per ancestor chain. A malformed branch, render failure, or reached limit stays contained in that branch and does not crash the widget tile. Traversal and child templates use the same parent interpreter budget.
Use expressions, conditionals, optional chaining, arrays, objects, template strings, safe standard helpers, and expression callbacks for `map`, `filter`, `sort`, and trusted runtime slots. Callback blocks, authored `const`, IIFEs, authored recursion, arbitrary functions, imports, hooks, refs, raw events, browser requests, `eval`, and bigint are blocked.
## Use temporary inputs
Bounded regex literals are supported for string matching, splitting, search, and replacement. Patterns with lookbehind, backreferences, nested quantifiers, excessive length, or unsupported flags are rejected.
Bind a control to a literal name. Read its value from `inputs`:
## Runtime components
<CustomWidgetCodeExample
id="custom-jsx-temporary-inputs"
label="Temporary inputs"
language="jsx"
code={temporaryInputsExample}
/>
- `SubFetch` runs a manual query with its `params`.
- `ActionButton` and `ToggleSwitch` run actions.
- `RefreshButton` refreshes load queries.
- `Icon`/`TablerIcon` resolves installed safe icons.
These values exist in React memory while the widget is mounted. Homarr does not save them in any storage location.
A reload or remount resets the values. Use widget options for saved user configuration.
Homarr infers the adapter type. It supports `value/onChange`, `checked/onChange`, `active/onChange`, and `opened/onChange` patterns.
The type follows the component mode. Multiple accordions and chip groups, multi/checkbox tree selects, and multiple/range date pickers expose `string[]`. `DateTimePicker` and `InlineDateTimePicker` support ranges but not multiple selection. A component that does not advertise a temporary input adapter reports `BINDING_UNAVAILABLE`.
Explicit adapters support Mantine components that use different event signatures.
`defaultValue` and `defaultChecked` set the initial temporary value. Homarr does not send them with the controlled prop.
Multiple components can share a name when their adapter types agree. A template cannot pass an authored callback as a component prop. Only the runtime-owned collection callbacks, zero-argument IIFE, and `RecursiveList` child template use the restricted forms described above.
## Read diagnostics
- `BLOCKED_CAPABILITY`: unsafe behavior; saving fails.
- `UNKNOWN_COMPONENT`: the component does not exist; saving fails.
- `UNKNOWN_MANTINE_PROP`: advisory; the prop is passed through, not discarded.
- `INVALID_PROP_VALUE`: warning or error depending on safety.
- `BINDING_UNAVAILABLE`: the component renders, but `bind` is unavailable.
- `BINDING_TYPE_CONFLICT`: components sharing one input name require incompatible value types.
- `RUNTIME_RENDER_ERROR`: rendering failed inside this tile only.
Homarr does not silently remove an ordinary prop.
Always include responsive loading, empty, error, and success states. Runtime failures remain inside the widget tile.
@@ -9,55 +9,17 @@ import { BundledCustomWidgetGallery, CustomWidgetCodeExample } from "@site/src/c
export const completeManifest = `{
"$schema": "homarr-custom-widget-v2",
"name": "Service status",
"description": "Shows the current service status.",
"sources": [{
"id": "default",
"name": "Service API",
"baseUrl": "https://status.example.com",
"networkScope": "public",
"auth": { "type": "none" }
}],
"requests": [{
"id": "status",
"sourceId": "default",
"kind": "query",
"method": "GET",
"pathTemplate": "/api/status",
"parameters": {},
"auth": "inherit",
"minimumBoardPermission": "view",
"trigger": "load"
}],
"optionsSchema": { "type": "object", "properties": {}, "additionalProperties": false },
"defaultOptions": {},
"sources": {
"default": { "name": "Service API", "baseUrl": "https://status.example.com", "networkScope": "public", "auth": "none" }
},
"requests": { "status": { "path": "/api/status" } },
"options": {},
"template": "<Badge color={data.status?.ok ? 'green' : 'red'}>{data.status?.label ?? 'Unknown'}</Badge>"
}`;
# Custom Widgets
Custom JSX v2 is the only Custom Widget format in Homarr. A definition contains metadata, API sources, named requests, widget options, and JSX.
Homarr encrypts source credentials separately. Homarr does not include credentials in an export.
Homarr does not support old display types, reusable connections, widget packs, or compatibility modes.
## Audience and prerequisites
This documentation is for Homarr administrators and Custom Widget authors. You must understand HTTP, JSON, API authentication, and self-hosted network routing.
For a private API source, you must understand DNS, TLS, reverse proxies, and container networking.
To write a template manually, you must understand JSX and Mantine. An AI-generated definition requires the same security and capability review.
## Select a task
- [Create a Custom Widget](./authoring): Use the workbench or an AI copy procedure.
- [Use the JSX runtime](./custom-jsx): Use data, Mantine components, temporary inputs, and diagnostics.
- [Configure requests and security](./requests-and-security): Configure API sources, actions, permissions, and network protection.
- [Use agents and skills.sh](./agent-authoring): Use MCP resources, MCP tools, and the portable Agent Skill.
- [Correct Custom Widget problems](./troubleshooting): Correct validation, request, configuration, and runtime problems.
## Understand the definition
Custom JSX v2 is Homarr's only Custom Widget format. A definition contains keyed API sources, keyed requests, optional widget options, and one safe JSX template. Credentials are encrypted separately and never exported.
```ts
interface HomarrCustomWidgetV2 {
@@ -65,59 +27,40 @@ interface HomarrCustomWidgetV2 {
name: string;
description?: string;
iconUrl?: string;
sources: CustomWidgetSource[];
requests: CustomWidgetRequest[];
optionsSchema: CustomWidgetOptionsSchema;
defaultOptions: Record<string, unknown>;
sources: Record<string, CustomWidgetSource>;
requests: Record<string, CustomWidgetRequest>;
options?: Record<string, CustomWidgetOption>;
template: string;
}
```
<CustomWidgetCodeExample
id="complete-widget-manifest"
label="Complete Custom Widget manifest"
label="Complete manifest"
code={completeManifest}
height="420px"
height="360px"
/>
Exports, imports, the UI, MCP, and Workshop validate this schema. Homarr rejects files that use an old format.
The browser renders JSX. Homarr's server executes named requests, applies board permissions and network protections, and injects credentials. The browser never receives source credentials.
If a definition is missing, its tile shows **Custom widget unavailable**. The other board items continue to operate.
## Authoring paths
The browser renders the safe JSX template. The Homarr server executes named requests and injects encrypted credentials.
- [Create a Custom Widget](./authoring) visually or with one copied AI prompt.
- [Use the JSX runtime](./custom-jsx) for data, Mantine components, temporary inputs, and diagnostics.
- [Configure requests and security](./requests-and-security).
- [Use MCP and the skills.sh skill](./agent-authoring).
- [Correct problems](./troubleshooting).
The browser does not receive API source credentials.
The workbench keeps one continuous layout with visual source, request, and option editors. Advanced raw JSON remains available for experts and AI paste. Invalid values stay inline and only disable the widget preview.
## Create and configure a Custom Widget
Exports, imports, Workshop, MCP, and storage all validate the same schema. Old formats are rejected. A missing definition only renders **Custom widget unavailable** inside its tile.
Open **Management → Custom Widgets → Add custom widget**. The workbench contains sections for information, sources, requests, widget options, JSX, and preview.
## Import and Workshop
Use the fixed navigation to go to a section.
Import review shows origins, authentication modes, network scopes, methods, actions, and permissions. It also lets the user supply each required credential before import. Workshop publishes technically valid widgets immediately; users can vote or report them, and administrators can delete submissions.
Select a definition from the searchable board widget picker. The picker immediately shows the widget options.
## Bundled examples
Invalid widget options prevent board insertion. If an update invalidates existing options, the item opens the configuration repair form.
## Import, export, and publish
An export contains one canonical JSON document without credentials. An import validates the document before Homarr creates the definition.
The import review shows origins, authentication modes, network scopes, methods, actions, and required board permissions. File, clipboard, and Workshop imports use this review.
The review gives credential fields for each authenticated source. Homarr encrypts entered values and excludes them from the imported document.
A definition shows **Credentials missing** when a source needs credentials. You can import a definition without credentials.
The indicator disappears after you configure all required source credentials.
The [Workshop](/docs/management/workshop) publishes valid widgets immediately. Users can vote on a submission or report it.
Workshop administrators can inspect reported posts and delete any submission. This deletion does not remove installed copies.
## Examine bundled examples
Homarr adds four disabled examples during database setup. These examples use the same schema as user definitions.
Homarr does not overwrite an example after a user changes it. Select an example to examine its manifest and JSX.
Homarr seeds five disabled examples without credentials and never overwrites an edited seed. They use the same public contract. The Tautulli example is the highest-scoring widget preserved from Homarr's AI authoring evaluation:
<BundledCustomWidgetGallery />
@@ -1,64 +1,42 @@
# Configure requests and security
Each Custom Widget has one or more API sources. Most definitions use one `default` source.
Most widgets use a single source named `default`. Extra keyed sources are available under Advanced for widgets that combine APIs. Authentication is `none`, `bearer`, `basic`, an API-key header, or an API-key query parameter. Credentials belong to the widget but remain outside its manifest.
Add another API source when the widget must combine APIs. For example, a calendar can combine Sonarr and Radarr.
## Named requests
An API source supports these authentication modes:
Requests are keyed by ID. `source`, `kind`, `method`, `trigger`, `auth`, and `permission` have sensible defaults, so a load query can be as small as:
- No authentication
- Bearer token
- Basic authentication
- API-key header
- API-key query parameter
```json
{ "status": { "path": "/api/status" } }
```
Homarr encrypts credentials for each definition, API source, and credential type. A duplicate or export does not contain credentials.
The request `path` is appended to the source `baseUrl`, including a base path such as `/api/v2`.
## Configure named requests
GET, POST, PUT, PATCH, and DELETE work for queries and actions. Kind—not method—controls behavior. Load queries run automatically, manual queries run through `SubFetch`, and actions run only from `ActionButton` or `ToggleSwitch`.
Named requests support GET, POST, PUT, PATCH, and DELETE. The `kind` value controls runtime behavior.
Bind values directly where they are used:
The HTTP method does not determine whether a named request is a query or action.
```json
{
"containers": {
"path": "/api/endpoints/{option:endpointId}/docker/containers/json",
"query": { "all": { "$option": "showAll" } }
},
"restart": {
"kind": "action",
"method": "POST",
"path": "/api/endpoints/{option:endpointId}/docker/containers/{param:id}/restart",
"invalidates": ["containers"]
}
}
```
- A load query runs automatically.
- A manual query runs with `SubFetch`.
- A query can include a body.
- An action runs after a user action.
- An action can require confirmation.
- An action can invalidate named queries.
`{option:name}` and `$option` read saved widget options. `{param:name}` and `$param` read invocation-time `params`. Load queries cannot use invocation parameters. Homarr infers the exact parameter names and primitive values from these references, rejecting missing or extra invocation params at the request boundary.
A successful action removes each declared query from the browser cache and server cache. Homarr then runs the query again.
Paths and query-string values must resolve to text, numbers, or booleans. JSON bodies can also use structured `json` and `multiSelect` option values, for example `{ "entity_ids": { "$option": "entities" } }`.
Path parameters use `{name}`. Structured query and body templates use `{ "$param": "name" }`.
## Protection
Give each declared parameter an explicit value source. For a load query, use `optionsBinding` with an option reference or a primitive value.
Every request uses Homarr's existing server executor: same-origin source enforcement, network scope, DNS and redirect validation, SSRF protection, board permissions, rate and request limits, body and response limits, timeouts, and encrypted credential injection. Static headers cannot override credentials, cookies, routing, proxy, forwarding, or hop-by-hop headers.
For a manual query or action, send all parameters with the component `params` prop. Homarr does not bind matching names automatically.
## Understand request protection
All named requests use the Homarr server executor. The executor applies these security controls:
- Source origin
- Network scope
- DNS validation
- Redirect validation
- SSRF protection
- Board permissions
- Rate limits
- Request limits
- Parameter limits
- Body limits
- Timeouts
- Response size limits
- Encrypted credential injection
Static headers cannot replace authentication, routing, proxy, cookie, or hop-by-hop headers. Homarr rejects static values that look like credentials.
The `public` network scope permits public destinations. The `private` network scope also permits private LAN destinations.
The `loopback` network scope also permits loopback destinations. The SSRF policy blocks sensitive infrastructure ranges for all network scopes.
Each named request requires view, modify, or full board access. The server checks this permission before it contacts the API source.
A DELETE request always requires full access and confirmation. Homarr does not cache actions, retry actions, or run actions on load.
Queries default to view permission and actions to modify. DELETE always requires full permission and confirmation. Actions are never cached, retried, or run on load.
@@ -20,11 +20,11 @@ Homarr does not migrate old Custom JSX formats.
Read the first schema or JSX diagnostic. Use its path to find the invalid value.
Make sure that each API source ID is unique. Make sure that each request ID is unique.
Make sure keyed API source and request names are valid and unique.
Make sure that each named request references an API source. Declare each request parameter.
Make sure that each named request references an API source. Use explicit `$option` and `$param` references.
Make sure that default values conform to the widget options schema. Use the correct request kind for each runtime component.
Make sure every option default matches its control. Use the correct request kind for each runtime component.
An incomplete API source URL or icon URL causes a form validation error. The workbench keeps the draft open.
@@ -38,11 +38,9 @@ The Diagnostics tab shows the current schema errors.
Check the API source network scope. Check the base URL and credentials.
Check the request method, path, body, parameters, and board permission.
Check the request method, path, body, direct value bindings, and board permission.
For “invalid placeholder,” examine each declared load query parameter. Add an `optionsBinding` option reference or primitive value for each parameter.
A matching parameter name and widget option name do not create a binding. The preview journal does not show credentials.
For “invalid placeholder,” use `{option:name}` or `{param:name}`. Load queries can only reference options. The preview journal does not show credentials.
The journal shows the method, request ID, path template, status, and duration.
@@ -56,6 +54,6 @@ Use safe widget options, temporary inputs, or named requests instead.
## Repair an existing item
The widget options schema changed and the saved values are invalid. Open the item settings.
The widget option definitions changed and the saved values are invalid. Open the item settings.
Correct the marked configuration values. Homarr does not infer widget option migrations.
@@ -53,12 +53,7 @@ const messages: CustomWidgetEditorMessages = {
};
export function CanonicalCustomWidgetExampleClient({ id, label, example, height }: CanonicalCustomWidgetExampleProps) {
const value =
example === "requests"
? CUSTOM_WIDGET_REQUEST_EXAMPLES.full
: example === "optionsSchema"
? CUSTOM_WIDGET_OPTIONS_EXAMPLES.full.schema
: CUSTOM_WIDGET_OPTIONS_EXAMPLES.full.defaults;
const value = example === "requests" ? CUSTOM_WIDGET_REQUEST_EXAMPLES.full : CUSTOM_WIDGET_OPTIONS_EXAMPLES.full;
return <CustomWidgetCodeExampleClient id={id} label={label} code={JSON.stringify(value, null, 2)} height={height} />;
}
+10 -32
View File
@@ -1,5 +1,10 @@
import BrowserOnly from "@docusaurus/BrowserOnly";
import { lazy, Suspense } from "react";
import {
BundledCustomWidgetGalleryClient,
CanonicalCustomWidgetExampleClient,
CustomWidgetCodeExampleClient,
} from "./custom-widget-code-client";
export interface CustomWidgetCodeExampleProps {
id: string;
@@ -12,33 +17,14 @@ export interface CustomWidgetCodeExampleProps {
export interface CanonicalCustomWidgetExampleProps {
id: string;
label: string;
example: "requests" | "optionsSchema" | "defaultOptions";
example: "requests" | "options";
height?: string;
}
const LazyCanonicalCustomWidgetExample = lazy(async () => {
const module = await import("./custom-widget-code-client");
return { default: module.CanonicalCustomWidgetExampleClient };
});
const LazyCustomWidgetCodeExample = lazy(async () => {
const module = await import("./custom-widget-code-client");
return { default: module.CustomWidgetCodeExampleClient };
});
const LazyBundledCustomWidgetGallery = lazy(async () => {
const module = await import("./custom-widget-code-client");
return { default: module.BundledCustomWidgetGalleryClient };
});
export function CanonicalCustomWidgetExample(props: CanonicalCustomWidgetExampleProps) {
return (
<BrowserOnly fallback={<CodeExampleFallback height={props.height} />}>
{() => (
<Suspense fallback={<CodeExampleFallback height={props.height} />}>
<LazyCanonicalCustomWidgetExample {...props} />
</Suspense>
)}
{() => <CanonicalCustomWidgetExampleClient {...props} />}
</BrowserOnly>
);
}
@@ -46,11 +32,7 @@ export function CanonicalCustomWidgetExample(props: CanonicalCustomWidgetExample
export function CustomWidgetCodeExample(props: CustomWidgetCodeExampleProps) {
return (
<BrowserOnly fallback={<CodeExampleFallback height={props.height} />}>
{() => (
<Suspense fallback={<CodeExampleFallback height={props.height} />}>
<LazyCustomWidgetCodeExample {...props} />
</Suspense>
)}
{() => <CustomWidgetCodeExampleClient {...props} />}
</BrowserOnly>
);
}
@@ -58,11 +40,7 @@ export function CustomWidgetCodeExample(props: CustomWidgetCodeExampleProps) {
export function BundledCustomWidgetGallery() {
return (
<BrowserOnly fallback={<CodeExampleFallback height="520px" />}>
{() => (
<Suspense fallback={<CodeExampleFallback height="520px" />}>
<LazyBundledCustomWidgetGallery />
</Suspense>
)}
{() => <BundledCustomWidgetGalleryClient />}
</BrowserOnly>
);
}
@@ -542,20 +542,21 @@ function WidgetCapabilitySummary({ content }: { content: string }) {
<div className={styles.capabilities}>
<section>
<strong>API sources</strong>
{result.data.sources.map((source) => (
<p key={source.id}>
<code>{new URL(source.baseUrl).origin}</code> · {source.networkScope} · {source.auth.type}
{Object.entries(result.data.sources).map(([sourceId, source]) => (
<p key={sourceId}>
<code>{new URL(source.baseUrl).origin}</code> · {source.networkScope} ·{" "}
{typeof source.auth === "string" ? source.auth : source.auth.type}
</p>
))}
</section>
<section>
<strong>Requests and permissions</strong>
{result.data.requests.length === 0 ? (
{Object.keys(result.data.requests).length === 0 ? (
<p>No network requests</p>
) : (
result.data.requests.map((request) => (
<p key={request.id}>
<code>{request.id}</code> · {request.kind} · {request.method} · {request.minimumBoardPermission}
Object.entries(result.data.requests).map(([requestId, request]) => (
<p key={requestId}>
<code>{requestId}</code> · {request.kind} · {request.method} · {request.permission}
</p>
))
)}
+11
View File
@@ -9,6 +9,17 @@
--breakpoint-3xl: 1697px;
}
html,
body,
#__docusaurus {
min-height: 100%;
background-color: var(--ifm-background-color);
}
#__docusaurus {
min-height: 100vh;
}
:root {
--ifm-color-primary: #fa5252;
--ifm-color-primary-dark: #f93232;
+1 -1
View File
@@ -1 +1 @@
{"schemaVersion":1,"mantineVersion":"9.4.1","customWidgetVersion":"2.0.0","types":["\"\" | \"anonymous\" | \"use-credentials\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"auto\" | \"fixed\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"auto\" | \"all\" | \"fill\" | \"stroke\" | \"painted\" | \"visible\" | \"visibleFill\" | \"visiblePainted\" | \"visibleStroke\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"block\" | \"inline\" | \"both\" | \"horizontal\" | \"vertical\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"contain\" | \"cover\" | \"fill\" | \"scale-down\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"wrap\" | \"nowrap\" | \"wrap-reverse\"","\"12h\" | \"24h\"","\"activity\" | \"display-none\"","\"all\" | \"parent\" | \"child\"","\"all\" | \"segment\"","\"always\" | \"hover\" | \"not-hover\" | \"never\"","\"area\" | \"radial\" | \"scatter\"","\"auto\" | \"scroll\" | \"always\" | \"hover\" | \"never\"","\"auto-fit\" | \"auto-fill\"","\"block\" | \"grid\"","\"both\" | \"rightSection\" | \"clear\"","\"bottom\" | \"top\"","\"bottom\" | \"top\" | \"baseline\" | \"middle\"","\"bottom\" | \"top\" | \"middle\"","\"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\" | \"bottom-center\" | \"top-center\" | \"middle-center\" | \"middle-end\" | \"middle-start\"","\"button\" | \"submit\" | \"reset\"","\"center\" | \"bottom\"","\"center\" | \"left\" | \"right\"","\"center\" | \"left\" | \"right\" | \"justify\" | \"char\"","\"center\" | \"side\" | \"merge\"","\"checkbox\" | \"radio\"","\"dashed\" | \"dotted\" | \"solid\"","\"default\" | \"gradient\"","\"default\" | \"responsive\"","\"default\" | \"stacked\" | \"percent\" | \"split\"","\"default\" | \"stacked\" | \"percent\" | \"waterfall\"","\"div\" | \"label\"","\"end\" | \"start\"","\"filled\" | \"light\"","\"fixed\" | \"absolute\"","\"hex\" | \"hexa\" | \"rgba\" | \"rgb\" | \"hsl\" | \"hsla\"","\"horizontal\" | \"vertical\"","\"hover\" | \"click\" | \"click-hover\"","\"inherit\" | \"auto\" | \"baseline\" | \"before-edge\" | \"text-before-edge\" | \"middle\" | \"central\" | \"after-edge\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\"","\"inherit\" | \"auto\" | \"sRGB\" | \"linearRGB\"","\"inherit\" | \"auto\" | \"text-before-edge\" | \"middle\" | \"central\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\" | \"use-script\" | \"no-change\" | \"reset-size\"","\"inherit\" | \"end\" | \"start\" | \"middle\"","\"inherit\" | \"nonzero\" | \"evenodd\"","\"inherit\" | \"round\" | \"butt\" | \"square\"","\"inherit\" | \"round\" | \"miter\" | \"bevel\"","\"initial\" | \"medial\" | \"terminal\" | \"isolated\"","\"inside\" | \"outside\"","\"left\" | \"right\"","\"left\" | \"right\" | \"bottom\" | \"top\" | \"left-end\" | \"left-start\" | \"right-end\" | \"right-start\" | \"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\"","\"left\" | \"right\" | \"inside\"","\"link\" | \"none\" | \"copy\" | \"execute\" | \"move\" | \"popup\"","\"media\" | \"container\"","\"month\" | \"year\" | \"decade\"","\"native\" | \"scrollarea\"","\"none\" | \"all\" | \"rows\" | \"groups\" | \"columns\"","\"none\" | \"all\" | \"with-indicators\"","\"none\" | \"ascending\" | \"descending\" | \"other\"","\"none\" | \"inline\" | \"list\" | \"both\"","\"none\" | \"strict\" | \"blur\"","\"none\" | \"sum\"","\"none\" | \"thousand\" | \"lakh\" | \"wan\"","\"none\" | \"x\" | \"y\" | \"xy\"","\"number\" | RegExp | \"alphanumeric\"","\"off\" | \"assertive\" | \"polite\"","\"off\" | \"on\"","\"ordered\" | \"unordered\"","\"right-to-left\" | \"left-to-right\"","\"search\" | \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"send\"","\"search\" | \"text\" | \"none\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\"","\"step\" | \"bump\" | \"linear\" | \"natural\" | \"monotone\" | \"stepBefore\" | \"stepAfter\"","\"stepInactive\" | \"stepProgress\" | \"stepCompleted\"","\"sum\" | \"replace\"","\"td\" | \"th\"","\"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"all\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\"","\"text\" | \"tel\" | \"password\"","\"time\" | \"duration\"","\"up\" | \"down\"","\"value\" | \"name\" | \"percent\"","\"viewport\"","\"wrap\" | \"nowrap\" | \"balance\" | \"pretty\" | \"stable\"","\"x\" | \"y\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | (string & {})","\"yes\" | \"no\"","'Text' | 'Title' | 'Badge' | 'Code' | 'Image'","'auto' | 'manual'","'json' | 'text'","'xs' | 'sm' | 'md' | 'lg' | 'xl'","(\"input\" | \"label\" | \"description\" | \"error\")[]","(\"next\" | \"previLine truncated
{"schemaVersion":1,"mantineVersion":"9.4.1","customWidgetVersion":"2.0.0","types":["\"\" | \"anonymous\" | \"use-credentials\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"auto\" | \"fixed\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"auto\" | \"all\" | \"fill\" | \"stroke\" | \"painted\" | \"visible\" | \"visibleFill\" | \"visiblePainted\" | \"visibleStroke\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"block\" | \"inline\" | \"both\" | \"horizontal\" | \"vertical\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"contain\" | \"cover\" | \"fill\" | \"scale-down\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"wrap\" | \"nowrap\" | \"wrap-reverse\"","\"12h\" | \"24h\"","\"activity\" | \"display-none\"","\"all\" | \"parent\" | \"child\"","\"all\" | \"segment\"","\"always\" | \"hover\" | \"not-hover\" | \"never\"","\"area\" | \"radial\" | \"scatter\"","\"auto\" | \"scroll\" | \"always\" | \"hover\" | \"never\"","\"auto-fit\" | \"auto-fill\"","\"block\" | \"grid\"","\"both\" | \"rightSection\" | \"clear\"","\"bottom\" | \"top\"","\"bottom\" | \"top\" | \"baseline\" | \"middle\"","\"bottom\" | \"top\" | \"middle\"","\"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\" | \"bottom-center\" | \"top-center\" | \"middle-center\" | \"middle-end\" | \"middle-start\"","\"button\" | \"submit\" | \"reset\"","\"center\" | \"bottom\"","\"center\" | \"left\" | \"right\"","\"center\" | \"left\" | \"right\" | \"justify\" | \"char\"","\"center\" | \"side\" | \"merge\"","\"checkbox\" | \"radio\"","\"dashed\" | \"dotted\" | \"solid\"","\"default\" | \"gradient\"","\"default\" | \"responsive\"","\"default\" | \"stacked\" | \"percent\" | \"split\"","\"default\" | \"stacked\" | \"percent\" | \"waterfall\"","\"div\" | \"label\"","\"end\" | \"start\"","\"filled\" | \"light\"","\"fixed\" | \"absolute\"","\"hex\" | \"hexa\" | \"rgba\" | \"rgb\" | \"hsl\" | \"hsla\"","\"horizontal\" | \"vertical\"","\"hover\" | \"click\" | \"click-hover\"","\"inherit\" | \"auto\" | \"baseline\" | \"before-edge\" | \"text-before-edge\" | \"middle\" | \"central\" | \"after-edge\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\"","\"inherit\" | \"auto\" | \"sRGB\" | \"linearRGB\"","\"inherit\" | \"auto\" | \"text-before-edge\" | \"middle\" | \"central\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\" | \"use-script\" | \"no-change\" | \"reset-size\"","\"inherit\" | \"end\" | \"start\" | \"middle\"","\"inherit\" | \"nonzero\" | \"evenodd\"","\"inherit\" | \"round\" | \"butt\" | \"square\"","\"inherit\" | \"round\" | \"miter\" | \"bevel\"","\"initial\" | \"medial\" | \"terminal\" | \"isolated\"","\"inside\" | \"outside\"","\"left\" | \"right\"","\"left\" | \"right\" | \"bottom\" | \"top\" | \"left-end\" | \"left-start\" | \"right-end\" | \"right-start\" | \"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\"","\"left\" | \"right\" | \"inside\"","\"link\" | \"none\" | \"copy\" | \"execute\" | \"move\" | \"popup\"","\"media\" | \"container\"","\"month\" | \"year\" | \"decade\"","\"native\" | \"scrollarea\"","\"none\" | \"all\" | \"rows\" | \"groups\" | \"columns\"","\"none\" | \"all\" | \"with-indicators\"","\"none\" | \"ascending\" | \"descending\" | \"other\"","\"none\" | \"inline\" | \"list\" | \"both\"","\"none\" | \"strict\" | \"blur\"","\"none\" | \"sum\"","\"none\" | \"thousand\" | \"lakh\" | \"wan\"","\"none\" | \"x\" | \"y\" | \"xy\"","\"number\" | RegExp | \"alphanumeric\"","\"off\" | \"assertive\" | \"polite\"","\"off\" | \"on\"","\"ordered\" | \"unordered\"","\"right-to-left\" | \"left-to-right\"","\"search\" | \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"send\"","\"search\" | \"text\" | \"none\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\"","\"step\" | \"bump\" | \"linear\" | \"natural\" | \"monotone\" | \"stepBefore\" | \"stepAfter\"","\"stepInactive\" | \"stepProgress\" | \"stepCompleted\"","\"sum\" | \"replace\"","\"td\" | \"th\"","\"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"all\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\"","\"text\" | \"tel\" | \"password\"","\"time\" | \"duration\"","\"up\" | \"down\"","\"value\" | \"name\" | \"percent\"","\"viewport\"","\"wrap\" | \"nowrap\" | \"balance\" | \"pretty\" | \"stable\"","\"x\" | \"y\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | (string & {})","\"yes\" | \"no\"","'Text' | 'Title' | 'Badge' | 'Code' | 'Image'","'auto' | 'manual'","'json' | 'text'","'xs' | 'sm' | 'md' | 'lg' | 'xl'","(\"input\" | \"label\" | \"description\" | \"error\")[]","(\"next\" | \"previLine truncated