-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin manifest specification
Version: Draft 1.2 Status: Working Draft Purpose: Define declarative user interfaces for Griffon plugins using TOML.
The Griffon UI Manifest Specification defines a declarative format for describing plugin user interfaces using TOML.
A Griffon UI manifest describes:
- plugin metadata
- tabs
- sections
- UI elements contained within sections
The manifest is intentionally presentation-focused. It describes what should be rendered, not how application logic is executed.
Interactive behavior, state mutation, and relationships between UI elements are expected to be defined in a separate logic or binding configuration.
The Griffon UI system follows these principles:
- Declarative: manifests describe UI structure, not imperative behavior
- Modular: components are generic and reusable across plugin types
- Separation of concerns: UI structure is distinct from state and logic
- Extensible: new component types can be added without changing the overall structure
- Predictable: component fields use consistent naming and semantics
A Griffon UI manifest is composed of three major parts:
- Plugin metadata
- Section definitions
- Section contents
[plugin]
# plugin metadata
[[ui.sections]]
# section definition
[[ui.sections.contents]]
# UI element definitionThe UI manifest is strictly responsible for presentation.
It defines:
- what elements exist
- where they appear
- how they are labeled
- which semantic actions they emit
It does not define:
- business logic
- state transitions
- computed values
- cross-component data flow
- event handler implementation
These concerns are handled by a separate logic/binding configuration.
This separation allows:
- reusable UI manifests
- testable interaction systems
- independent rendering and execution layers
- cleaner plugin architecture
The following example demonstrates a realistic and practical UI: a simple button counter.
This interface renders:
- a title
- a grouped layout using rows and columns
- a card-like display area
- a text element that displays the current count
- an increment button
- a reset button
The buttons emit semantic actions. The value updates are expected to be handled by a separate binding system.
# Griffon Plugin Manifest: Counter Demo
[plugin]
name = "Counter Demo"
id = "org.griffon.counterdemo"
version = "1.0.0"
author = "Raphael"
description = "Demonstrates a simple counter interface"
tabs = ["main"]
[[ui.sections]]
id = "counter_main"
tab = "main"
[[ui.sections.contents]]
type = "text"
id = "counter_title"
name = "Button Counter"
variant = "title"
[[ui.sections.contents]]
type = "group"
id = "counter_group"
name = "Counter Controls"
[[ui.sections.contents.children]]
type = "card"
id = "counter_card"
name = "Current Count"
description = "Press the button to increase the counter"
[[ui.sections.contents.children]]
type = "column"
id = "counter_column"
gap = "md"
[[ui.sections.contents.children.children]]
type = "text"
id = "counter_value"
name = "0"
variant = "title"
tone = "info"
align = "center"
[[ui.sections.contents.children.children]]
type = "divider"
id = "counter_divider"
[[ui.sections.contents.children.children]]
type = "row"
id = "counter_actions"
gap = "sm"
justify = "center"
[[ui.sections.contents.children.children.children]]
type = "button"
id = "increment_button"
name = "Increment"
variant = "primary"
size = "lg"
action = "counter.increment"
[[ui.sections.contents.children.children.children]]
type = "button"
id = "reset_button"
name = "Reset"
variant = "secondary"
action = "counter.reset"The following example illustrates how a separate configuration can connect actions and state to UI elements.
# griffon.bindings.toml
[state]
counter = 0
[[bindings]]
on = "counter.increment"
update = "state.counter = state.counter + 1"
[[bindings.targets]]
id = "counter_value"
property = "name"
value = "state.counter"
[[bindings]]
on = "counter.reset"
update = "state.counter = 0"
[[bindings.targets]]
id = "counter_value"
property = "name"
value = "state.counter"This example is illustrative only. The binding file format may vary depending on the Griffon runtime design.
The [plugin] table defines the plugin identity and high-level navigation.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable plugin name |
id |
string | Yes | Unique plugin identifier |
version |
string | Yes | Plugin version string |
author |
string | Yes | Plugin author name |
description |
string | No | Short plugin description |
tabs |
array of strings | Yes | Ordered list of available tab names |
[plugin]
name = "Antivirus"
id = "org.griffon.antivirus"
version = "1.2.0"
author = "Raphael"
description = "Antivirus protection and threat management"
tabs = ["overview", "threats", "settings"]A section is a container that groups UI elements under a specific tab.
Multiple sections may belong to the same tab.
[[ui.sections]]
id = "overview_main"
tab = "overview"| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique section identifier |
tab |
string | Yes | Name of the tab this section belongs to |
- Section
idvalues must be unique within the manifest. - The
tabvalue must exist inplugin.tabs. - Each section may contain zero or more
[[ui.sections.contents]]entries. - Section contents are rendered in declaration order unless overridden by layout rules.
Each [[ui.sections.contents]] entry defines a single UI element.
Elements are declared sequentially and belong to the most recently declared section.
Container elements may define nested children through children.
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Component type |
name |
string | Usually | Display label, title, or primary visible text |
id |
string | Recommended | Unique element identifier |
-
typeis mandatory for all elements. -
nameis the primary display string for most components. -
idis strongly recommended for any interactive or bindable element. - Element
idvalues should be unique within the manifest. - Additional fields are interpreted according to the
type. - Container components may contain nested
children.
This section defines the base set of generic UI elements supported by the Griffon UI system.
Displays static text, headings, descriptions, or status messages.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"text" |
Yes | Component type |
name |
string | Yes | Text content |
id |
string | Recommended | Element identifier |
variant |
string | No |
body, title, subtitle, caption, status
|
tone |
string | No |
neutral, info, success, warning, danger
|
align |
string | No |
left, center, right
|
[[ui.sections.contents]]
type = "text"
id = "status_label"
name = "Your device is protected"
variant = "title"
tone = "success"Displays a clickable action button.
A button emits an action identifier through the action field.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"button" |
Yes | Component type |
name |
string | Yes | Button label |
id |
string | Recommended | Element identifier |
variant |
string | No |
primary, secondary, ghost, danger, success
|
size |
string | No |
sm, md, lg
|
disabled |
bool | No | Disabled state |
full_width |
bool | No | Expands to container width |
icon |
string | No | Optional icon identifier |
action |
string | Recommended | Semantic action emitted on click |
[[ui.sections.contents]]
type = "button"
id = "run_scan_button"
name = "Run Quick Scan"
variant = "primary"
size = "lg"
action = "scan.quick"Displays a single-choice dropdown list.
A select emits an action identifier when its value changes.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"select" |
Yes | Component type |
name |
string | Yes | Label |
id |
string | Recommended | Element identifier |
options |
array of strings | Yes | Selectable options |
value |
string | No | Initial/current selected value |
placeholder |
string | No | Placeholder text |
disabled |
bool | No | Disabled state |
logo |
string | No | Optional visual marker |
action |
string | Recommended | Semantic action emitted on change |
[[ui.sections.contents]]
type = "select"
id = "scan_type_select"
name = "Scan Type"
options = ["Quick", "Full", "Custom"]
value = "Quick"
action = "scan.type.change"Displays a text input field.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"input" |
Yes | Component type |
name |
string | Yes | Label |
id |
string | Recommended | Element identifier |
value |
string | No | Initial/current value |
placeholder |
string | No | Placeholder text |
input_type |
string | No |
text, search, password, number
|
disabled |
bool | No | Disabled state |
action |
string | Recommended | Semantic action emitted on change or submit |
[[ui.sections.contents]]
type = "input"
id = "threat_search"
name = "Search Threats"
placeholder = "Search by name or path..."
input_type = "search"
action = "threats.search"Displays a binary on/off toggle.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"switch" |
Yes | Component type |
name |
string | Yes | Label |
id |
string | Recommended | Element identifier |
value |
bool | Yes | Current state |
description |
string | No | Secondary explanatory text |
disabled |
bool | No | Disabled state |
action |
string | Recommended | Semantic action emitted on toggle |
[[ui.sections.contents]]
type = "switch"
id = "realtime_protection"
name = "Real-time Protection"
value = true
description = "Automatically scans files as they are accessed"
action = "protection.realtime.toggle"Displays a checkable boolean option.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"checkbox" |
Yes | Component type |
name |
string | Yes | Label |
id |
string | Recommended | Element identifier |
value |
bool | Yes | Checked state |
description |
string | No | Helper text |
disabled |
bool | No | Disabled state |
action |
string | Recommended | Semantic action emitted on toggle |
[[ui.sections.contents]]
type = "checkbox"
id = "include_archives"
name = "Include archived files"
value = false
action = "scan.include_archives.toggle"Displays internal tab-like navigation within a section.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"tabs" |
Yes | Component type |
name |
string | Yes | Label or internal name |
id |
string | Recommended | Element identifier |
items |
array of strings | Yes | Tab labels |
value |
string | No | Active tab |
action |
string | Recommended | Semantic action emitted on change |
[[ui.sections.contents]]
type = "tabs"
id = "threat_tabs"
name = "Threat Views"
items = ["Active", "Quarantined", "Resolved"]
value = "Active"
action = "threats.view.change"Displays a small status indicator or label.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"badge" |
Yes | Component type |
name |
string | Yes | Badge text |
id |
string | Optional | Element identifier |
tone |
string | No |
neutral, info, success, warning, danger
|
variant |
string | No |
solid, soft, outline
|
[[ui.sections.contents]]
type = "badge"
name = "Protected"
tone = "success"
variant = "soft"Displays progress for long-running operations.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"progress" |
Yes | Component type |
name |
string | Yes | Label |
id |
string | Recommended | Element identifier |
value |
integer | Yes | Progress value from 0 to 100 |
show_label |
bool | No | Whether to show numeric progress |
status |
string | No |
default, success, warning, danger
|
animated |
bool | No | Animated progress indicator |
[[ui.sections.contents]]
type = "progress"
id = "scan_progress"
name = "Current Scan"
value = 42
show_label = true
animated = trueDisplays a grouped visual container.
A card is primarily a presentational grouping component.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"card" |
Yes | Component type |
name |
string | Yes | Card title |
id |
string | Recommended | Element identifier |
description |
string | No | Subtitle or secondary text |
tone |
string | No |
neutral, info, success, warning, danger
|
[[ui.sections.contents]]
type = "card"
id = "status_card"
name = "Protection Status"
description = "All protection systems are active"
tone = "success"Defines a generic logical container for grouping related elements.
A group is the primary generic container for nesting UI components. It can be used for semantic grouping without enforcing a specific layout direction.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"group" |
Yes | Component type |
id |
string | Recommended | Element identifier |
name |
string | No | Optional semantic label |
title |
string | No | Optional visible title |
description |
string | No | Optional helper text |
gap |
string | No |
none, xs, sm, md, lg, xl
|
children |
array | Yes | Nested child components |
[[ui.sections.contents]]
type = "group"
id = "scan_controls"
name = "Scan Controls"
[[ui.sections.contents.children]]
type = "button"
id = "quick_scan_button"
name = "Quick Scan"
action = "scan.quick"
[[ui.sections.contents.children]]
type = "button"
id = "full_scan_button"
name = "Full Scan"
action = "scan.full"Defines a horizontal layout container.
A row arranges child elements horizontally.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"row" |
Yes | Component type |
id |
string | Recommended | Element identifier |
name |
string | No | Optional semantic label |
gap |
string | No |
none, xs, sm, md, lg, xl
|
align |
string | No |
start, center, end, stretch
|
justify |
string | No |
start, center, end, space-between, space-around
|
wrap |
bool | No | Allow child wrapping |
children |
array | Yes | Nested child components |
[[ui.sections.contents]]
type = "row"
id = "header_actions"
gap = "sm"
justify = "space-between"
align = "center"
[[ui.sections.contents.children]]
type = "text"
id = "header_title"
name = "Threats"
variant = "title"
[[ui.sections.contents.children]]
type = "button"
id = "refresh_button"
name = "Refresh"
variant = "secondary"
action = "threats.refresh"Defines a vertical layout container.
A column arranges child elements vertically.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"column" |
Yes | Component type |
id |
string | Recommended | Element identifier |
name |
string | No | Optional semantic label |
gap |
string | No |
none, xs, sm, md, lg, xl
|
align |
string | No |
start, center, end, stretch
|
justify |
string | No |
start, center, end, space-between, space-around
|
children |
array | Yes | Nested child components |
[[ui.sections.contents]]
type = "column"
id = "status_stack"
gap = "md"
[[ui.sections.contents.children]]
type = "text"
id = "status_title"
name = "Protection Status"
variant = "title"
[[ui.sections.contents.children]]
type = "badge"
id = "status_badge"
name = "Protected"
tone = "success"Displays a visual separator between elements.
A divider is a purely presentational component used to break up sections of content.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"divider" |
Yes | Component type |
id |
string | Optional | Element identifier |
orientation |
string | No |
horizontal, vertical
|
label |
string | No | Optional divider label |
tone |
string | No |
neutral, info, success, warning, danger
|
[[ui.sections.contents]]
type = "divider"
id = "section_break"
orientation = "horizontal"Displays structured tabular data.
A table is intended for read-only or semi-interactive data presentation. Row-level actions should be handled through separate bindings or explicit action columns.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"table" |
Yes | Component type |
id |
string | Recommended | Element identifier |
name |
string | Yes | Table label or title |
columns |
array of strings | Yes | Ordered list of column labels |
rows |
array of arrays | No | Initial row data |
striped |
bool | No | Alternate row styling |
compact |
bool | No | Reduced spacing |
selectable |
bool | No | Allow row selection |
action |
string | Recommended | Semantic action emitted on row selection or interaction |
[[ui.sections.contents]]
type = "table"
id = "threats_table"
name = "Detected Threats"
columns = ["Name", "Severity", "Location", "Status"]
rows = [
["Trojan.Agent", "High", "C:/Downloads/file.exe", "Quarantined"],
["PUA.Toolbar", "Low", "C:/Program Files/App", "Ignored"]
]
striped = true
compact = false
selectable = true
action = "threats.table.select"Container components support nested child declarations.
Supported container types:
grouprowcolumn
Nested child components are declared using children.
[[ui.sections.contents]]
type = "row"
id = "actions_row"
[[ui.sections.contents.children]]
type = "button"
id = "save_button"
name = "Save"
[[ui.sections.contents.children]]
type = "button"
id = "cancel_button"
name = "Cancel"Nested containers may themselves contain children.
[[ui.sections.contents]]
type = "group"
id = "panel"
[[ui.sections.contents.children]]
type = "column"
id = "panel_stack"
[[ui.sections.contents.children.children]]
type = "text"
id = "panel_title"
name = "Settings"
[[ui.sections.contents.children.children]]
type = "row"
id = "panel_actions"
[[ui.sections.contents.children.children.children]]
type = "button"
id = "save_button"
name = "Save"Interactive elements should emit semantic action identifiers rather than implementation-specific handler names.
Examples:
counter.incrementcounter.resetscan.quickscan.type.changeprotection.realtime.togglequarantine.delete
This convention improves portability, maintainability, and binding clarity.
Element identifiers (id) are strongly recommended for:
- interactive elements
- dynamic text values
- values updated by bindings
- elements referenced by automation or tests
Use lowercase snake_case:
counter_valueincrement_buttonscan_progressrealtime_protection
Tones communicate semantic meaning across the UI.
| Tone | Meaning | Typical Usage |
|---|---|---|
neutral |
Default state | Standard UI elements |
info |
Informational state | Active process, syncing |
success |
Healthy or completed state | Protected, connected |
warning |
Attention required | Outdated, partial issue |
danger |
Critical or destructive state | Threat found, delete action |
titlesubtitlebodycaptionstatus
primarysecondaryghostdangersuccess
solidsoftoutline
[plugin]
name = "Antivirus"
id = "org.griffon.antivirus"
version = "1.0.0"
author = "Raphael"
description = "Device protection and threat management"
tabs = ["overview", "threats", "settings"]
[[ui.sections]]
id = "overview_main"
tab = "overview"
[[ui.sections.contents]]
type = "column"
id = "overview_layout"
gap = "md"
[[ui.sections.contents.children]]
type = "row"
id = "overview_header"
justify = "space-between"
align = "center"
[[ui.sections.contents.children.children]]
type = "text"
id = "overview_title"
name = "Your device is protected"
variant = "title"
tone = "success"
[[ui.sections.contents.children.children]]
type = "badge"
id = "protection_badge"
name = "Protected"
tone = "success"
variant = "soft"
[[ui.sections.contents.children]]
type = "divider"
id = "overview_divider"
[[ui.sections.contents.children]]
type = "group"
id = "scan_controls"
name = "Scan Controls"
[[ui.sections.contents.children.children]]
type = "row"
id = "scan_actions"
gap = "sm"
[[ui.sections.contents.children.children.children]]
type = "button"
id = "quick_scan_button"
name = "Run Quick Scan"
variant = "primary"
size = "lg"
action = "scan.quick"
[[ui.sections.contents.children.children.children]]
type = "button"
id = "full_scan_button"
name = "Run Full Scan"
variant = "secondary"
action = "scan.full"
[[ui.sections.contents.children]]
type = "select"
id = "scan_type_select"
name = "Scan Type"
options = ["Quick", "Full", "Custom"]
value = "Quick"
action = "scan.type.change"
[[ui.sections.contents.children]]
type = "progress"
id = "scan_progress"
name = "Current Scan"
value = 42
show_label = true
animated = true
[[ui.sections.contents.children]]
type = "switch"
id = "realtime_protection"
name = "Real-time Protection"
value = true
description = "Automatically scans files as they are opened or downloaded"
action = "protection.realtime.toggle"
[[ui.sections]]
id = "threats_main"
tab = "threats"
[[ui.sections.contents]]
type = "table"
id = "threats_table"
name = "Detected Threats"
columns = ["Name", "Severity", "Location", "Status"]
rows = [
["Trojan.Agent", "High", "C:/Downloads/file.exe", "Quarantined"],
["PUA.Toolbar", "Low", "C:/Program Files/App", "Ignored"]
]
striped = true
selectable = true
action = "threats.table.select"The following validation rules are recommended for Griffon UI manifests.
-
[plugin]must exist exactly once -
plugin.tabsmust contain at least one value -
[[ui.sections]]may appear zero or more times - each
ui.sections.idmust be unique - each
ui.sections.tabmust exist inplugin.tabs
- each
[[ui.sections.contents]]must appear after a[[ui.sections]] - each element must define
type - most elements should define
name - interactive elements should define
action - bindable elements should define
id -
progress.valueshould be clamped or validated to0..100 -
select.valueshould match one ofoptionswhen provided -
tabs.valueshould match one ofitemswhen provided -
table.rowsshould match the length oftable.columns -
dividerdoes not requirename -
group,row, andcolumnmay omitname -
group,row, andcolumnshould contain at least one child - nested
childrendeclarations must belong to a container element
Avoid domain-specific component types such as:
threat_cardscan_buttonvpn_status_box
Prefer:
cardbuttonbadgeprogressswitchgrouprowcolumntable
Preferred:
action = "scan.quick"Avoid:
action = "runQuickScanEngineV2"Any element whose visible value may change should have an id.
Examples:
- counter values
- progress indicators
- status labels
- result fields
- table selections
Recommended layout strategy:
- use
columnfor vertical page flow - use
rowfor horizontal action groups - use
groupfor semantic grouping - use
dividerfor visual separation - use
tablefor structured datasets
Do not embed logic semantics directly into UI structure beyond action identifiers and initial values.
The following component types are recommended as future additions:
gridlistmodaldrawertoasttimelinemetricchart
These additions would enable richer layouts while preserving the same declarative architecture.
A minimal yet practical Griffon UI implementation should support:
textbuttonselectinputswitchcheckboxbadgeprogresscardgrouprowcolumndividertable
This set is sufficient to build:
- counters
- antivirus dashboards
- updater interfaces
- VPN controls
- cleanup tools
- settings panels
- backup modules
The Griffon UI Manifest Specification defines a generic, reusable, and declarative UI format for plugin-based applications.
The core architecture is based on:
- TOML-defined UI
- generic reusable components
- semantic actions
- nested layout containers
- structured data components
- separate binding/logic configuration
- modular sections organized by tabs
This model supports both simple interfaces (such as a counter) and more complex modules (such as antivirus or system utilities) without requiring domain-specific UI types.