Skip to main content

Screens

Screens define the user interface for your Stack9 application. A screen is a JSON configuration that tells Stack9 how to render data grids, forms, and custom views without writing frontend code.

What is a Screen?

A screen configuration defines:

  • Layout: List views, detail views, drawers, or modals
  • Data source: Which query to execute
  • Columns: What fields to display and how
  • Actions: Buttons and operations available
  • Permissions: Who can view and interact

When you define a screen, Stack9 automatically:

  • ✅ Renders the UI with proper styling
  • ✅ Handles data fetching and pagination
  • ✅ Manages sorting and filtering
  • ✅ Handles form validation
  • ✅ Manages navigation
  • ✅ Applies role-based permissions

Screen Types

Stack9 supports four primary screen types:

1. listView - Data Grids

Display tabular data with sorting, filtering, and pagination.

Use cases: Customer lists, order history, search results

2. detailView - Record Forms

Display and edit a single record with form fields.

Use cases: Customer profile, order details, product editor

3. drawer - Slide-Out Panels

Side panel for quick create/edit operations without leaving the current page.

Use cases: Quick add customer, update status, add comment

4. modal - Pop-Up Forms

Modal dialog for focused interactions.

Use cases: Confirmations, quick edits, wizards

Screen File Structure

Screens are defined in src/screens/{screen_name}.json:

{
"head": {
// Screen metadata
},
"screenType": "listView | detailView | drawer | modal",
"listQuery": "query_key", // For listView
"detailQuery": "query_key", // For detailView
"columnsConfiguration": [], // Column definitions
"actions": [], // Custom actions
"permissions": {} // Access control
}

List View Screens

List views display tabular data with powerful filtering and sorting capabilities.

Basic List View Example

{
"head": {
"title": "Customer List",
"key": "customer_list",
"route": "customer-list",
"app": "crm",
"description": "Browse all customers"
},
"screenType": "listView",
"listQuery": "getactivecustomerlist",
"columnsConfiguration": [
{
"field": "crn",
"label": "CRN",
"value": "{{crn}}",
"renderAs": "Text",
"options": {
"linkProp": "/crm/customer-profile/{{id}}"
}
},
{
"label": "Full Name",
"field": "fullname",
"renderAs": "Text",
"value": "{{[name, last_name].filter(Boolean).join(' ')}}"
},
{
"field": "email_address",
"label": "Email",
"value": "{{email_address}}",
"renderAs": "Text"
},
{
"field": "is_active",
"label": "Active",
"value": "{{is_active}}",
"renderAs": "Boolean"
}
]
}

Head Configuration

PropertyTypeRequiredDescription
titlestringYesScreen title shown in UI
keystringYesUnique identifier
routestringYesURL route (e.g., /crm/customer-list)
appstringYesApp key this screen belongs to
descriptionstringNoHelp text
iconstringNoIcon component name

Column Configuration

Columns define how each field is displayed in the grid:

{
"field": "crn", // Field name (for sorting/filtering)
"label": "CRN", // Column header
"value": "{{crn}}", // Template for cell value
"renderAs": "Text", // Render type
"options": { // Render options
"linkProp": "/customer/{{id}}"
}
}

Render Types

Text - Plain text display

{
"field": "name",
"label": "Name",
"value": "{{name}}",
"renderAs": "Text"
}

Date - Formatted date/datetime

{
"field": "created_at",
"label": "Created",
"value": "{{created_at}}",
"renderAs": "Date",
"options": {
"noTime": true, // Hide time component
"format": "DD/MM/YYYY" // Custom format
}
}

Boolean - Checkmark or badge

{
"field": "is_active",
"label": "Active",
"value": "{{is_active}}",
"renderAs": "Boolean"
}

EnumTags - Colored tags/badges

{
"field": "status",
"label": "Status",
"value": "{{status}}",
"renderAs": "EnumTags",
"options": {
"enumColor": {
"pending": "orange",
"approved": "green",
"rejected": "red"
}
}
}

Currency - Formatted currency

{
"field": "total_amount",
"label": "Total",
"value": "{{total_amount}}",
"renderAs": "Currency",
"options": {
"currency": "AUD",
"decimals": 2
}
}
{
"field": "crn",
"label": "CRN",
"value": "{{crn}}",
"renderAs": "Text",
"options": {
"linkProp": "/customer/{{id}}"
}
}

Image - Image thumbnail

{
"field": "avatar",
"label": "Photo",
"value": "{{avatar_url}}",
"renderAs": "Image",
"options": {
"width": 50,
"height": 50
}
}

Template Expressions

Use template expressions in the value field to transform data:

Concatenate Fields

{
"label": "Full Name",
"value": "{{[name, last_name].filter(Boolean).join(' ')}}"
}

Conditional Display

{
"label": "Location",
"value": "{{suburb ? suburb + ', ' + state : 'N/A'}}"
}

Array Mapping

{
"label": "Attention Flags",
"value": "{{customer_attention_flags.map(item => item.attention_flag.flag)}}"
}
{
"label": "Sales Channel",
"value": "{{sales_channel.name}}"
}

Real-World List View Example

From a production Stack9 app:

{
"head": {
"title": "Customer list",
"key": "customer_list",
"route": "customer-list",
"app": "crm"
},
"screenType": "listView",
"listQuery": "getactivecustomerlist",
"columnsConfiguration": [
{
"field": "crn",
"label": "CRN",
"value": "{{crn}}",
"renderAs": "Text",
"options": {
"linkProp": "/crm/customer-profile/{{id}}"
}
},
{
"label": "Fullname",
"field": "fullname",
"renderAs": "Text",
"value": "{{[name, last_name].filter(Boolean).join(' ')}}"
},
{
"field": "phone",
"label": "Phone",
"value": "{{phone}}",
"renderAs": "Text"
},
{
"field": "dob",
"label": "DOB",
"value": "{{dob}}",
"renderAs": "Date",
"options": {
"noTime": true
}
},
{
"field": "email_address",
"label": "Email address",
"value": "{{email_address}}",
"renderAs": "Text"
},
{
"field": "gos_level",
"label": "GOS level",
"value": "{{gos_level}}",
"renderAs": "EnumTags",
"options": {
"enumColor": {
"Bronze": "orange",
"Silver": "grey",
"Gold": "gold",
"Platinum": "grey",
"Custodian": "cyan"
}
}
},
{
"label": "Attention flags",
"field": "attention_flags",
"renderAs": "EnumTags",
"value": "{{customer_attention_flags.map(item=>item.attention_flag.flag)}}",
"options": {
"enumColor": {
"Deceased": "red",
"Bequestor": "blue"
}
}
}
]
}

Detail View Screens

Detail views display a single record with all fields editable in a form layout.

Basic Detail View Example

{
"head": {
"title": "Customer Profile",
"key": "customer_profile",
"route": "customer-profile/:id",
"app": "crm"
},
"screenType": "detailView",
"entityKey": "customer",
"detailQuery": "getcustomer",
"sections": [
{
"title": "Basic Information",
"fields": [
"customer_type",
"title",
"name",
"last_name",
"email_address",
"phone"
]
},
{
"title": "Address",
"fields": [
"address_line_1",
"address_line_2",
"suburb",
"state",
"post_code"
]
}
],
"tabs": [
{
"title": "Sales Orders",
"type": "grid",
"field": "sales_orders"
},
{
"title": "Subscriptions",
"type": "grid",
"field": "subscriptions"
}
]
}

Sections

Group related fields into collapsible sections:

{
"sections": [
{
"title": "Contact Information",
"collapsed": false,
"fields": [
"email_address",
"phone",
"mobile"
]
},
{
"title": "Address",
"collapsed": true,
"fields": [
"address_line_1",
"suburb",
"state"
]
}
]
}

Tabs

Create tabbed layouts for related data:

{
"tabs": [
{
"title": "Overview",
"type": "fields",
"fields": ["name", "email", "phone"]
},
{
"title": "Orders",
"type": "grid",
"field": "sales_orders",
"listQuery": "getcustomerorders"
},
{
"title": "Activity",
"type": "timeline",
"field": "history"
}
]
}

Drawer Screens

Drawers are slide-out panels for quick create/edit operations.

Basic Drawer Example

{
"head": {
"title": "Create Customer",
"key": "customer_create_drawer",
"app": "crm"
},
"screenType": "drawer",
"entityKey": "customer",
"width": "large",
"fields": [
"customer_type",
"name",
"last_name",
"email_address",
"phone",
"is_active"
],
"onSuccess": {
"action": "redirect",
"route": "/crm/customer-profile/{{id}}"
}
}

Drawer Sizes

{
"width": "small" // 400px
"width": "medium" // 600px
"width": "large" // 900px
"width": "xlarge" // 1200px
}

Custom Actions

Add custom buttons and actions to screens:

List View Actions

{
"screenType": "listView",
"actions": [
{
"key": "create_customer",
"label": "New Customer",
"icon": "PlusIcon",
"type": "openDrawer",
"drawerKey": "customer_create_drawer"
},
{
"key": "export",
"label": "Export",
"icon": "DownloadIcon",
"type": "export",
"format": "csv"
}
],
"rowActions": [
{
"key": "edit",
"label": "Edit",
"icon": "EditIcon",
"type": "redirect",
"route": "/customer/{{id}}"
},
{
"key": "delete",
"label": "Delete",
"icon": "TrashIcon",
"type": "delete",
"confirm": true,
"confirmMessage": "Are you sure you want to delete this customer?"
}
]
}

Action Types

openDrawer - Open a drawer screen

{
"key": "quick_edit",
"label": "Quick Edit",
"type": "openDrawer",
"drawerKey": "customer_edit_drawer"
}

redirect - Navigate to another screen

{
"key": "view_details",
"label": "View",
"type": "redirect",
"route": "/customer/{{id}}"
}

webhook - Trigger an automation

{
"key": "send_welcome_email",
"label": "Send Welcome Email",
"type": "webhook",
"webhookPath": "/send-welcome-email",
"confirm": true,
"confirmMessage": "Send welcome email to this customer?"
}

export - Export data

{
"key": "export_csv",
"label": "Export CSV",
"type": "export",
"format": "csv"
}

Filtering and Sorting

Default Filters

Apply filters when the screen loads:

{
"screenType": "listView",
"defaultFilters": {
"is_active": true,
"state": "QLD"
}
}

Filter Controls

Add filter UI controls:

{
"filterControls": [
{
"field": "state",
"label": "State",
"type": "select",
"options": ["NSW", "VIC", "QLD", "WA", "SA", "TAS", "NT", "ACT"]
},
{
"field": "is_active",
"label": "Active Only",
"type": "checkbox"
},
{
"field": "created_at",
"label": "Created Date",
"type": "dateRange"
}
]
}

Default Sorting

{
"defaultSorting": [
{
"field": "created_at",
"order": "desc"
}
]
}

Permissions

Control who can view and interact with screens:

{
"permissions": {
"view": ["admin", "manager", "staff"],
"create": ["admin", "manager"],
"edit": ["admin", "manager"],
"delete": ["admin"]
}
}

Conditional Display

Show/hide fields based on conditions:

{
"fields": [
"customer_type",
{
"field": "business_abn",
"condition": {
"field": "customer_type",
"operator": "equals",
"value": "Business"
}
}
]
}

Screen Lifecycle Hooks

Execute logic at various screen lifecycle events:

{
"hooks": {
"onLoad": {
"type": "webhook",
"path": "/track-screen-view"
},
"beforeSave": {
"type": "validate",
"validationFunction": "validateCustomer"
},
"afterSave": {
"type": "notification",
"message": "Customer saved successfully"
}
}
}

Best Practices

1. Use Meaningful Routes

{
"route": "customer-list" // ✅ Good
"route": "list1" // ❌ Bad
}
{
"sections": [
{
"title": "Basic Information",
"fields": ["name", "email", "phone"]
},
{
"title": "Address",
"fields": ["address_line_1", "suburb", "state"]
}
]
}

3. Use EnumTags for Status Fields

{
"field": "status",
"renderAs": "EnumTags",
"options": {
"enumColor": {
"active": "green",
"inactive": "red"
}
}
}
{
"field": "customer_name",
"value": "{{customer.name}}",
"renderAs": "Text",
"options": {
"linkProp": "/customer/{{customer_id}}"
}
}

5. Use Drawers for Quick Operations

Use drawers instead of full pages for:

  • Quick create/edit
  • Status updates
  • Adding comments
  • Assigning tasks

6. Provide Clear Action Labels

{
"label": "Send Welcome Email" // ✅ Clear
"label": "Action 1" // ❌ Unclear
}

Common Patterns

Master-Detail Screen

{
"screenType": "detailView",
"sections": [
{
"title": "Customer Info",
"fields": ["name", "email"]
}
],
"tabs": [
{
"title": "Orders",
"type": "grid",
"field": "sales_orders",
"actions": [
{
"label": "New Order",
"type": "openDrawer",
"drawerKey": "order_create_drawer"
}
]
}
]
}

Search Screen with Filters

{
"screenType": "listView",
"filterControls": [
{
"field": "search",
"label": "Search",
"type": "text",
"placeholder": "Search by name, email, or CRN"
},
{
"field": "state",
"label": "State",
"type": "select"
}
],
"defaultSorting": [
{ "field": "created_at", "order": "desc" }
]
}

Bulk Action Screen

{
"screenType": "listView",
"bulkActions": [
{
"key": "bulk_delete",
"label": "Delete Selected",
"type": "webhook",
"webhookPath": "/bulk-delete",
"confirm": true
},
{
"key": "bulk_export",
"label": "Export Selected",
"type": "export"
}
]
}

Next Steps

Now that you understand screens, learn about: