Composite Blocks
Composite Blocks — Minnow UI Reference
Build rich admin pages entirely in YAML using composable block primitives. No custom Vue components needed.
Page Types
| Type | Description |
|---|---|
list |
Entity list with table, search, filters, pagination |
editor |
Create/edit form with field types and layouts |
settings |
Plugin settings grouped by section |
form-builder |
Drag-and-drop form builder |
composite |
Grid of blocks — dashboards, tools, custom layouts |
placeholder |
Static message (for unfinished pages) |
Composite Page Structure
- slug: my-dashboard
title: Dashboard
type: composite
api:
namespace: my-plugin/v1 # Base API namespace for all blocks
context: # Reactive context defaults (see Context System)
days: 30
blocks:
- type: stat-card
width: quarter
# ... block-specific config
Grid System
All blocks live in a 12-column grid. The width property maps to Tailwind col-span classes with mobile breakpoints:
| Width | Desktop | Mobile |
|---|---|---|
full |
12 cols | 12 cols |
two-thirds |
8 cols | 12 cols |
half |
6 cols | 12 cols |
third |
4 cols | 12 cols |
quarter |
3 cols | 12 cols |
Omitting width defaults to full.
Block Types
stat-card — Single Metric Display
Displays a single number from an API endpoint with icon, color, and formatting.
- type: stat-card
width: quarter
label: Total Users # Card label
icon: users # Icon name (see Icon Library)
color: blue # Color theme
subtitle: "Last 30 days" # Optional subtitle below the value
format: number # number | currency | percentage
currency: USD # For format: currency (default: USD)
source:
endpoint: dashboard/stats
field: totals.users # Dot-path to extract value from response
params: # Query params (supports $context.*)
days: "$context.days"
Colors: blue, green, red, yellow, purple, orange, accent
Formats:
number— locale-formatted integer (e.g.,1,234)currency— currency-formatted (e.g.,$1,234.00)percentage— percentage with 1 decimal (e.g.,45.2%)
chart — Chart.js Visualizations
Renders line, bar, doughnut, or pie charts. Chart.js is lazy-loaded on first use.
- type: chart
width: two-thirds
title: Daily Volume
chartType: line # line | bar | doughnut | pie
height: 300 # Canvas height in pixels (default: 300)
source:
endpoint: dashboard/stats
field: daily # Must resolve to an array
params:
days: "$context.days"
xField: date # Array item field for x-axis labels
# Multi-series (datasets array):
datasets:
- { field: sent, label: Sent, color: green }
- { field: failed, label: Failed, color: red }
# Single-series alternative:
# yField: value
# color: blue
Chart colors: blue, green, red, yellow, purple, orange, accent, white, or any CSS color string.
table — Embedded Data Table
Compact data table without pagination. Handles both arrays and key-value objects.
- type: table
width: third
title: Top Recipients
emptyMessage: No recipients found.
maxRows: 10 # Limit displayed rows
source:
endpoint: dashboard/stats
field: top_recipients # Can be array or {key: value} object
params:
days: "$context.days"
columns:
- { field: email, label: Email, format: email }
- { field: count, label: Count, format: number }
badges: # For format: badge columns
active: { color: green }
inactive: { color: gray }
Column formats: badge, number, email (accent colored), or plain text (default).
Object handling: If source.field resolves to an object like {"user@test.com": 20}, the table auto-converts it to rows using the first two column field names as keys.
recent-list — Compact Item List
Fetches a list of items and displays them with optional badges and a "View all" link.
- type: recent-list
width: half
title: Recent Emails
viewAllRoute: my-plugin-log # Links to another plugin page
source:
endpoint: events
limit: 5 # Sets per_page query param
params:
days: "$context.days"
primaryField: subject # Main text for each row
secondaryField: service # Optional secondary text
badgeField: status # Optional badge on each row
badges:
sent: { color: green }
failed: { color: red }
pending: { color: gray }
Badge colors: green, red, yellow, orange, blue, purple, gray, accent
filter — Filter Bar
Provides controls that write to the reactive context. Other blocks read context values via $context.* params and re-fetch when values change.
- type: filter
width: full
filters:
- key: days # Context key to write to
type: select # select | text
label: Time Period
options:
- { value: 7, label: "Last 7 days" }
- { value: 30, label: "Last 30 days" }
- { value: 90, label: "Last 90 days" }
- key: search
type: text
label: Search
placeholder: "Filter..."
Important: Filter options with numeric values preserve their type (not coerced to strings).
form — Submit Form
Renders a form with multiple field types and submits to an API endpoint. Displays success/error feedback inline.
- type: form
width: half
title: Send a Test Email
description: Verify your SMTP configuration. # Optional subtitle
submitLabel: Send Test Email # Button text
submittingLabel: Sending... # Button text while submitting
action:
endpoint: tools/send-test # Relative to api.namespace
method: POST # GET | POST | PUT | DELETE (default: POST)
resetOnSuccess: true # Reset form after success (default: true)
fields:
- name: send_to
label: Send To
type: email # text | email | textarea | select | toggle | checkbox
placeholder: user@example.com
description: Email address to send the test to.
- name: send_from
label: Send From
type: email
placeholder: admin@yoursite.com
- name: service
label: Send With
type: select
default: php_mail
options:
- { value: php_mail, label: PHP Mail }
- { value: smtp, label: SMTP }
- name: html
label: Format
type: toggle
toggleLabel: Send as HTML # Text next to the toggle
default: false
Field types:
text,email— Standard inputtextarea— Multi-line textselect— Dropdown (options as{value, label}objects or plain strings)toggle,checkbox— Boolean toggle switch
API contract: The form submits all field values as JSON. The response should include { success: bool, message: string }.
key-value — Structured Key-Value Display
Displays label-value pairs, optionally grouped into sections. Can fetch from API or use static data.
# Static items
- type: key-value
width: half
title: System Info
copyable: true # Shows "Copy" button
items:
- { label: PHP Version, value: "8.3.0" }
- { label: Database, value: MySQL 8.0 }
# Grouped sections (static)
- type: key-value
width: full
title: System Report
copyable: true
sections:
- label: Environment
items:
- { label: PHP Version, value: "8.3.0" }
- { label: Memory Limit, value: 256M }
- label: Database
items:
- { label: Version, value: MySQL 8.0 }
- { label: Charset, value: utf8mb4 }
# From API (auto-formats object keys)
- type: key-value
width: half
title: Server Info
copyable: true
source:
endpoint: system/info
field: server # Dot-path to extract
params:
days: "$context.days"
Auto-formatting: API objects are converted to pairs — keys become labels (snake_case → Title Case), values become strings. Nested objects become sections automatically.
Copy: When copyable: true, a "Copy" button copies all pairs as formatted text.
alert — Notice / Banner
Displays a contextual message with optional action links.
- type: alert
width: full
variant: info # info | success | warning | error
title: Getting Started # Optional bold heading
content: "Configure your SMTP settings to start sending emails."
actions: # Optional action buttons
- label: Go to Settings
route: my-plugin-settings # Plugin page slug
- label: View Docs
route: my-plugin-docs
Variants:
info— Blue tint, info iconsuccess— Green tint, check iconwarning— Yellow tint, warning iconerror— Red tint, error icon
html — Static Content
Renders static HTML or Markdown content.
- type: html
width: full
title: Welcome # Optional heading
content: "<p>Get started by configuring your settings.</p>"
markdown: true # Parse content as Markdown (uses markdown-it)
When markdown: true, the content is parsed using the globally available markdown-it library. HTML is disabled in Markdown mode for security.
links — Quick Links Panel
Displays a list of clickable links with icons and descriptions.
- type: links
width: half
title: Resources
links:
- label: Documentation
description: Learn how to configure the plugin.
href: https://docs.example.com/
icon: book # Icon name (see below)
- label: Support
description: Get help from the community.
href: https://support.example.com/
icon: chat
- label: Debug Log
description: View detailed logs.
href: /admin/#/admin/plugin/my-debug-page
icon: code
external: false # Opens in same tab (default: true)
Link icons: book, help, chat, code, globe, video, or omit for a default link icon.
Context System
The context system enables cross-block communication. Filter blocks write values, data blocks read them.
How It Works
-
Declare defaults in the page's
contextproperty:context: days: 30 status: all -
Filter blocks write to context keys:
- type: filter filters: - key: days type: select options: [...] -
Data blocks read via
$context.*in source params:source: endpoint: dashboard/stats params: days: "$context.days" # Resolves to current context value status: "$context.status" -
Reactivity: When a filter changes a context value, all blocks referencing that key automatically re-fetch their data.
Auto-Refresh
Any block can declare refreshInterval (in seconds) for periodic data reload:
- type: stat-card
refreshInterval: 30 # Re-fetch every 30 seconds
source:
endpoint: dashboard/stats
field: totals.active
Icon Library
Available icons for stat-card and block components:
| Name | Description |
|---|---|
mail |
Envelope |
check |
Checkmark |
x-circle |
Error/failed circle |
shield |
Shield/security |
database |
Database stack |
users |
People group |
trending-up |
Upward trend arrow |
clock |
Clock/time |
zap |
Lightning bolt |
star |
Star |
For links blocks: book, help, chat, code, globe, video
API Data Fetching
All data-fetching blocks follow the same pattern:
source:
endpoint: dashboard/stats # Appended to api.namespace → /api/my-plugin/v1/dashboard/stats
field: totals.sent # Optional dot-path to extract from response
limit: 10 # For recent-list: sets per_page param
params: # Query string parameters
days: "$context.days" # $context.* values are resolved reactively
status: active # Static values passed as-is
Authentication: All requests include the X-WP-Nonce header automatically.
Error handling: Blocks display loading states during fetch and gracefully handle errors (empty data, error messages).
Full Example: Plugin Dashboard
name: My Analytics Plugin
namespace: MyAnalytics
slug: my-analytics
version: 1.0.0
api:
namespace: my-analytics/v1
routes:
- path: /stats
method: GET
controller: StatsController::index
admin:
menu:
- title: Analytics
slug: my-analytics
icon: trending-up
items:
- title: Dashboard
slug: my-analytics
- title: Settings
slug: my-analytics-settings
pages:
- slug: my-analytics
title: Dashboard
type: composite
api:
namespace: my-analytics/v1
context:
days: 30
blocks:
- type: filter
width: full
filters:
- key: days
type: select
label: Period
options:
- { value: 7, label: "7 days" }
- { value: 30, label: "30 days" }
- { value: 90, label: "90 days" }
- type: stat-card
width: third
label: Page Views
icon: trending-up
color: blue
format: number
source: { endpoint: stats, field: totals.views, params: { days: "$context.days" } }
- type: stat-card
width: third
label: Visitors
icon: users
color: green
format: number
source: { endpoint: stats, field: totals.visitors, params: { days: "$context.days" } }
- type: stat-card
width: third
label: Bounce Rate
icon: zap
color: orange
format: percentage
source: { endpoint: stats, field: totals.bounce_rate, params: { days: "$context.days" } }
- type: chart
width: two-thirds
title: Traffic Over Time
chartType: line
height: 300
source: { endpoint: stats, field: daily, params: { days: "$context.days" } }
xField: date
datasets:
- { field: views, label: Views, color: blue }
- { field: visitors, label: Visitors, color: green }
- type: table
width: third
title: Top Pages
maxRows: 10
source: { endpoint: stats, field: top_pages, params: { days: "$context.days" } }
columns:
- { field: path, label: Page }
- { field: views, label: Views, format: number }
- type: recent-list
width: half
title: Recent Events
source: { endpoint: events, limit: 5, params: { days: "$context.days" } }
primaryField: name
secondaryField: url
badgeField: type
badges:
pageview: { color: blue }
click: { color: green }
error: { color: red }
- type: alert
width: half
variant: info
title: Need Help?
content: "Check the documentation for setup guides and API reference."
actions:
- label: View Docs
route: my-analytics-docs
- slug: my-analytics-settings
title: Settings
type: settings
Full Example: Tools Page
- slug: my-plugin-tools
title: Tools
type: composite
api:
namespace: my-plugin/v1
blocks:
- type: form
width: half
title: Send Test Notification
description: Verify your notification configuration.
submitLabel: Send Test
action:
endpoint: tools/test-notify
method: POST
fields:
- name: recipient
label: Recipient
type: email
placeholder: user@example.com
- name: channel
label: Channel
type: select
options:
- { value: email, label: Email }
- { value: slack, label: Slack }
- type: links
width: half
title: Resources
links:
- label: Getting Started
href: https://docs.example.com/setup
icon: book
- label: API Reference
href: https://docs.example.com/api
icon: code
- type: key-value
width: full
title: System Information
copyable: true
source:
endpoint: tools/system-info