Minnow

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 input
  • textarea — Multi-line text
  • select — 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_caseTitle 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 icon
  • success — Green tint, check icon
  • warning — Yellow tint, warning icon
  • error — 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

  1. Declare defaults in the page's context property:

    context:
     days: 30
     status: all
  2. Filter blocks write to context keys:

    - type: filter
     filters:
       - key: days
         type: select
         options: [...]
  3. Data blocks read via $context.* in source params:

    source:
     endpoint: dashboard/stats
     params:
       days: "$context.days"       # Resolves to current context value
       status: "$context.status"
  4. 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