Master-Detail Screens
Learn how to build master-detail screen patterns in Stack9 - a common UI pattern where a list of records (master) is displayed alongside detailed information about a selected record (detail). This guide covers split views, drill-down navigation, and interactive detail panels.
What You'll Learn
- Creating side-by-side master-detail layouts
- Implementing drill-down navigation patterns
- Building interactive detail panels
- Loading and displaying related data
- Managing state between master and detail views
- Optimizing performance for large datasets
- Mobile-responsive master-detail patterns
Time Required: 45 minutes
Prerequisites
- Completed Building a CRUD Application guide
- Understanding of screens and queries
- Basic knowledge of entity relationships
Understanding Master-Detail Patterns
Master-detail is a UI pattern that displays two related views:
Master View (List)
- Shows a collection of records
- Allows filtering and searching
- Provides overview information
- Enables record selection
Detail View (Details)
- Shows full information for selected record
- Displays related data
- Allows editing and actions
- Shows contextual information
Common Use Cases
| Scenario | Master | Detail |
|---|---|---|
| Email Client | Email list | Email content |
| Customer Management | Customer list | Customer profile |
| Order Management | Order list | Order details with items |
| Product Catalog | Product list | Product specifications |
| Document Library | Document list | Document preview |
Pattern 1: Split View (Side-by-Side)
The split view shows master and detail side-by-side on the same screen.
Example: Customer Master-Detail
Step 1: Create the Master-Detail Screen
File: src/screens/customer_master_detail.json
{
"head": {
"title": "Customers",
"key": "customer_master_detail",
"route": "customer-master-detail",
"app": "crm",
"icon": "UserOutlined"
},
"screenType": "masterDetailView",
"masterConfig": {
"listQuery": "getcustomerlist",
"entityKey": "customer",
"columns": [
{
"field": "name",
"label": "Name",
"value": "{{name}} {{last_name}}",
"renderAs": "Text"
},
{
"field": "email",
"label": "Email",
"value": "{{email}}",
"renderAs": "Text"
},
{
"field": "phone",
"label": "Phone",
"value": "{{phone}}",
"renderAs": "Text"
},
{
"field": "status",
"label": "Status",
"value": "{{status}}",
"renderAs": "EnumTags",
"options": {
"enumColor": {
"Active": "green",
"Inactive": "red"
}
}
}
],
"searchable": true,
"paginate": true,
"pageSize": 20
},
"detailConfig": {
"detailQuery": "getcustomerdetail",
"entityKey": "customer",
"sections": [
{
"title": "Contact Information",
"fields": ["name", "last_name", "email", "phone"]
},
{
"title": "Address",
"fields": ["address_line1", "address_line2", "city", "state", "postal_code"]
},
{
"title": "Additional Details",
"fields": ["status", "customer_type", "date_joined"]
}
],
"relatedLists": [
{
"title": "Orders",
"query": "getordersbycustomer",
"columns": [
{
"field": "order_number",
"label": "Order #",
"value": "{{order_number}}",
"renderAs": "Text"
},
{
"field": "order_date",
"label": "Date",
"value": "{{order_date}}",
"renderAs": "Date"
},
{
"field": "total",
"label": "Total",
"value": "{{total}}",
"renderAs": "Currency"
}
]
}
]
},
"layout": "split",
"splitRatio": "40:60"
}
Step 2: Create Master Query
File: src/query-library/getcustomerlist.json
{
"key": "getcustomerlist",
"name": "getCustomerList",
"connector": "stack9_api",
"queryTemplate": {
"method": "post",
"path": "/customer/search",
"bodyParams": "{\n \"$select\": [\"id\", \"name\", \"last_name\", \"email\", \"phone\", \"status\"],\n \"$where\": {\n \"_is_deleted\": false\n },\n \"$orderBy\": [{\"column\": \"name\", \"order\": \"asc\"}],\n \"$limit\": 20,\n \"$offset\": {{offset}}\n}",
"queryParams": {}
},
"userParams": {
"offset": "0"
}
}
Step 3: Create Detail Query
File: src/query-library/getcustomerdetail.json
{
"key": "getcustomerdetail",
"name": "getCustomerDetail",
"connector": "stack9_api",
"queryTemplate": {
"method": "post",
"path": "/customer/search",
"bodyParams": "{\n \"$select\": [\"*\"],\n \"$where\": {\n \"id\": {{customerId}}\n },\n \"$withRelated\": [\"orders(notDeleted)\"]\n}",
"queryParams": {}
},
"userParams": {
"customerId": ""
}
}
Step 4: Create Orders Query
File: src/query-library/getordersbycustomer.json
{
"key": "getordersbycustomer",
"name": "getOrdersByCustomer",
"connector": "stack9_api",
"queryTemplate": {
"method": "post",
"path": "/order/search",
"bodyParams": "{\n \"$select\": [\"id\", \"order_number\", \"order_date\", \"total\", \"status\"],\n \"$where\": {\n \"customer_id\": {{customerId}},\n \"_is_deleted\": false\n },\n \"$orderBy\": [{\"column\": \"order_date\", \"order\": \"desc\"}]\n}",
"queryParams": {}
},
"userParams": {
"customerId": ""
}
}
What This Creates
- Left Panel (40%): Scrollable customer list
- Right Panel (60%): Customer details with related orders
- Click a customer in the list to see their details
- Detail panel updates without page reload
- Related orders shown in expandable section
Pattern 2: Drill-Down Navigation
Drill-down navigates from list to full-page detail view.
Example: Order Management
Step 1: Create Order List Screen
File: src/screens/order_list.json
{
"head": {
"title": "Orders",
"key": "order_list",
"route": "order-list",
"app": "sales",
"icon": "ShoppingCartOutlined"
},
"screenType": "listView",
"listQuery": "getorderlist",
"entityKey": "order",
"columnsConfiguration": [
{
"field": "order_number",
"label": "Order #",
"value": "{{order_number}}",
"renderAs": "Text",
"options": {
"linkProp": "/sales/order-detail/{{id}}"
}
},
{
"field": "customer",
"label": "Customer",
"value": "{{customer.name}} {{customer.last_name}}",
"renderAs": "Text"
},
{
"field": "order_date",
"label": "Date",
"value": "{{order_date}}",
"renderAs": "Date",
"options": {
"format": "DD MMM YYYY"
}
},
{
"field": "total",
"label": "Total",
"value": "{{total}}",
"renderAs": "Currency",
"options": {
"currency": "USD"
}
},
{
"field": "status",
"label": "Status",
"value": "{{status}}",
"renderAs": "EnumTags",
"options": {
"enumColor": {
"Draft": "grey",
"Pending": "orange",
"Processing": "blue",
"Shipped": "cyan",
"Delivered": "green",
"Cancelled": "red"
}
}
}
],
"filters": [
{
"field": "status",
"label": "Status",
"type": "dropdown",
"options": ["Draft", "Pending", "Processing", "Shipped", "Delivered", "Cancelled"]
},
{
"field": "customer_id",
"label": "Customer",
"type": "dropdown",
"entityKey": "customer"
}
],
"actions": [
{
"key": "create",
"label": "New Order",
"type": "drawer",
"drawerKey": "order_create"
}
]
}
Step 2: Create Order Detail Screen
File: src/screens/order_detail.json
{
"head": {
"title": "Order Details",
"key": "order_detail",
"route": "order-detail/:id",
"app": "sales"
},
"screenType": "detailView",
"detailQuery": "getorderdetail",
"entityKey": "order",
"sections": [
{
"title": "Order Information",
"layout": "grid",
"columns": 2,
"fields": [
{
"key": "order_number",
"renderAs": "text",
"readOnly": true
},
{
"key": "order_date",
"renderAs": "date"
},
{
"key": "customer_id",
"renderAs": "reference",
"linkTo": "/crm/customer-detail/{{customer_id}}"
},
{
"key": "status",
"renderAs": "tag"
}
]
},
{
"title": "Order Items",
"type": "relatedList",
"query": "getorderitems",
"entityKey": "order_item",
"columns": [
{
"field": "product",
"label": "Product",
"value": "{{product.name}}",
"renderAs": "Text"
},
{
"field": "quantity",
"label": "Quantity",
"value": "{{quantity}}",
"renderAs": "Number"
},
{
"field": "unit_price",
"label": "Unit Price",
"value": "{{unit_price}}",
"renderAs": "Currency"
},
{
"field": "line_total",
"label": "Total",
"value": "{{line_total}}",
"renderAs": "Currency"
}
],
"editable": true,
"deletable": true
},
{
"title": "Totals",
"layout": "grid",
"columns": 2,
"fields": [
{
"key": "subtotal",
"renderAs": "currency",
"readOnly": true
},
{
"key": "tax_amount",
"renderAs": "currency",
"readOnly": true
},
{
"key": "total",
"renderAs": "currency",
"readOnly": true,
"highlight": true
}
]
},
{
"title": "Payment Information",
"layout": "grid",
"columns": 2,
"fields": [
{
"key": "payment_status",
"renderAs": "tag"
},
{
"key": "payment_method",
"renderAs": "text"
},
{
"key": "payment_transaction_id",
"renderAs": "text"
}
]
},
{
"title": "Notes",
"fields": ["notes"]
}
],
"actions": [
{
"key": "edit",
"label": "Edit Order",
"type": "drawer",
"drawerKey": "order_edit"
},
{
"key": "process_payment",
"label": "Process Payment",
"type": "custom",
"actionType": "process_payment",
"confirmMessage": "Process payment for this order?",
"conditions": [
{
"field": "payment_status",
"operator": "equals",
"value": "Pending"
}
]
},
{
"key": "mark_shipped",
"label": "Mark as Shipped",
"type": "custom",
"actionType": "mark_order_shipped",
"conditions": [
{
"field": "status",
"operator": "equals",
"value": "Processing"
}
]
},
{
"key": "delete",
"label": "Delete",
"type": "delete",
"confirmMessage": "Are you sure you want to delete this order?",
"conditions": [
{
"field": "status",
"operator": "in",
"value": ["Draft", "Pending"]
}
]
}
],
"breadcrumbs": [
{
"label": "Sales",
"link": "/sales"
},
{
"label": "Orders",
"link": "/sales/order-list"
},
{
"label": "{{order_number}}",
"current": true
}
]
}
Step 3: Create Order Detail Query
File: src/query-library/getorderdetail.json
{
"key": "getorderdetail",
"name": "getOrderDetail",
"connector": "stack9_api",
"queryTemplate": {
"method": "post",
"path": "/order/search",
"bodyParams": "{\n \"$select\": [\n \"*\",\n \"customer.name\",\n \"customer.last_name\",\n \"customer.email\"\n ],\n \"$where\": {\n \"id\": {{orderId}}\n },\n \"$withRelated\": [\n \"customer(notDeleted)\",\n \"order_items(notDeleted)\",\n \"order_items(notDeleted).product(notDeleted)\"\n ]\n}",
"queryParams": {}
},
"userParams": {
"orderId": ""
}
}
Pattern 3: Tabbed Detail View
Display related information in tabs within the detail view.
Example: Customer Profile with Tabs
File: src/screens/customer_profile.json
{
"head": {
"title": "Customer Profile",
"key": "customer_profile",
"route": "customer-profile/:id",
"app": "crm"
},
"screenType": "detailView",
"detailQuery": "getcustomerprofile",
"entityKey": "customer",
"layout": "tabbed",
"tabs": [
{
"key": "overview",
"label": "Overview",
"icon": "UserOutlined",
"sections": [
{
"title": "Contact Information",
"layout": "grid",
"columns": 2,
"fields": ["name", "last_name", "email", "phone"]
},
{
"title": "Address",
"layout": "grid",
"columns": 2,
"fields": ["address_line1", "address_line2", "city", "state", "postal_code", "country"]
}
]
},
{
"key": "orders",
"label": "Orders",
"icon": "ShoppingCartOutlined",
"type": "relatedList",
"query": "getordersbycustomer",
"entityKey": "order",
"columns": [
{
"field": "order_number",
"label": "Order #",
"value": "{{order_number}}",
"renderAs": "Text",
"options": {
"linkProp": "/sales/order-detail/{{id}}"
}
},
{
"field": "order_date",
"label": "Date",
"value": "{{order_date}}",
"renderAs": "Date"
},
{
"field": "total",
"label": "Total",
"value": "{{total}}",
"renderAs": "Currency"
},
{
"field": "status",
"label": "Status",
"value": "{{status}}",
"renderAs": "EnumTags"
}
],
"actions": [
{
"key": "create_order",
"label": "New Order",
"type": "drawer",
"drawerKey": "order_create",
"defaultValues": {
"customer_id": "{{id}}"
}
}
]
},
{
"key": "subscriptions",
"label": "Subscriptions",
"icon": "SyncOutlined",
"type": "relatedList",
"query": "getsubscriptionsbycustomer",
"entityKey": "subscription"
},
{
"key": "communications",
"label": "Communications",
"icon": "MailOutlined",
"sections": [
{
"title": "Email History",
"type": "relatedList",
"query": "getemailsbycustomer"
},
{
"title": "Notes",
"type": "relatedList",
"query": "getnotesbycustomer",
"editable": true,
"createInline": true
}
]
},
{
"key": "activity",
"label": "Activity",
"icon": "HistoryOutlined",
"type": "timeline",
"query": "getcustomeractivity",
"timelineConfig": {
"dateField": "created_at",
"titleField": "activity_type",
"descriptionField": "description",
"iconField": "icon"
}
}
],
"actions": [
{
"key": "edit",
"label": "Edit",
"type": "drawer",
"drawerKey": "customer_edit"
},
{
"key": "send_email",
"label": "Send Email",
"type": "drawer",
"drawerKey": "send_customer_email"
}
]
}
Pattern 4: Modal Detail View
Show detail in a modal/drawer overlay.
Example: Quick View Order Details
Screen Configuration:
{
"head": {
"title": "Orders",
"key": "order_list_quick_view",
"route": "order-list-quick",
"app": "sales"
},
"screenType": "listView",
"listQuery": "getorderlist",
"entityKey": "order",
"columnsConfiguration": [],
"rowActions": [
{
"key": "quick_view",
"label": "Quick View",
"icon": "EyeOutlined",
"type": "drawer",
"drawerConfig": {
"width": "60%",
"detailQuery": "getorderdetail",
"sections": [
{
"title": "Order Summary",
"fields": ["order_number", "customer_id", "order_date", "status"]
},
{
"title": "Items",
"type": "relatedList",
"query": "getorderitems"
}
],
"actions": [
{
"key": "full_details",
"label": "View Full Details",
"type": "link",
"link": "/sales/order-detail/{{id}}"
}
]
}
},
{
"key": "edit",
"label": "Edit",
"icon": "EditOutlined",
"type": "drawer",
"drawerKey": "order_edit"
}
]
}
Advanced Patterns
Pattern 5: Hierarchical Master-Detail
Display hierarchical data with expandable rows.
Example: Product Categories and Products
{
"head": {
"title": "Products",
"key": "product_hierarchy",
"route": "product-hierarchy",
"app": "products"
},
"screenType": "hierarchyView",
"masterQuery": "getcategorylist",
"detailQuery": "getproductsbycategory",
"hierarchyConfig": {
"expandable": true,
"defaultExpanded": false,
"masterColumns": [
{
"field": "name",
"label": "Category",
"value": "{{name}}",
"renderAs": "Text"
},
{
"field": "product_count",
"label": "Products",
"value": "{{product_count}}",
"renderAs": "Number"
}
],
"detailColumns": [
{
"field": "sku",
"label": "SKU",
"value": "{{sku}}",
"renderAs": "Text"
},
{
"field": "name",
"label": "Product",
"value": "{{name}}",
"renderAs": "Text"
},
{
"field": "price",
"label": "Price",
"value": "{{price}}",
"renderAs": "Currency"
},
{
"field": "stock_quantity",
"label": "Stock",
"value": "{{stock_quantity}}",
"renderAs": "Number"
}
]
}
}
Pattern 6: Comparison View
Compare multiple records side-by-side.
{
"head": {
"title": "Product Comparison",
"key": "product_comparison",
"route": "product-comparison",
"app": "products"
},
"screenType": "comparisonView",
"listQuery": "getproductlist",
"maxCompare": 3,
"compareFields": [
{
"key": "name",
"label": "Product Name",
"type": "text"
},
{
"key": "price",
"label": "Price",
"type": "currency",
"highlight": "lowest"
},
{
"key": "stock_quantity",
"label": "Stock",
"type": "number"
},
{
"key": "rating",
"label": "Rating",
"type": "rating",
"highlight": "highest"
},
{
"key": "description",
"label": "Description",
"type": "richtext"
}
]
}
Mobile-Responsive Patterns
Responsive Layout Configuration
{
"layout": "split",
"splitRatio": "40:60",
"responsive": {
"mobile": {
"layout": "stacked",
"showMasterFirst": true
},
"tablet": {
"layout": "split",
"splitRatio": "50:50"
},
"desktop": {
"layout": "split",
"splitRatio": "40:60"
}
}
}
Performance Optimization
1. Lazy Loading Detail Data
{
"detailConfig": {
"loadOnSelect": true,
"cacheDetails": true,
"cacheDuration": 300000
}
}
2. Pagination in Related Lists
{
"title": "Orders",
"type": "relatedList",
"query": "getordersbycustomer",
"pagination": {
"enabled": true,
"pageSize": 10,
"showSizeChanger": true
}
}
3. Virtual Scrolling for Large Lists
{
"masterConfig": {
"virtualScroll": true,
"itemHeight": 48,
"overscan": 5
}
}
Best Practices
1. Design for Context
Good: Contextual Actions
{
"actions": [
{
"key": "email_customer",
"label": "Email {{customer.name}}",
"icon": "MailOutlined"
}
]
}
2. Provide Clear Navigation
Good: Breadcrumbs
{
"breadcrumbs": [
{"label": "Home", "link": "/"},
{"label": "Customers", "link": "/crm/customers"},
{"label": "{{name}}", "current": true}
]
}
3. Show Loading States
{
"detailConfig": {
"loadingStrategy": "skeleton",
"loadingText": "Loading customer details..."
}
}
4. Handle Empty States
{
"relatedLists": [
{
"title": "Orders",
"query": "getordersbycustomer",
"emptyState": {
"icon": "ShoppingCartOutlined",
"title": "No orders yet",
"description": "This customer hasn't placed any orders.",
"action": {
"label": "Create First Order",
"type": "drawer",
"drawerKey": "order_create"
}
}
}
]
}
5. Keyboard Navigation
Enable keyboard shortcuts for master-detail navigation:
- ↑/↓: Navigate master list
- Enter: Open detail
- Esc: Close detail/return to list
- Tab: Navigate between sections
Troubleshooting
Detail Not Loading
Problem: Detail panel shows loading indefinitely
Solutions:
- Check
detailQueryis defined correctly - Verify query parameter matches (e.g.,
{{id}}) - Check query returns data for the ID
- Look for errors in browser console
Related Lists Empty
Problem: Related list shows no data
Solutions:
- Verify foreign key relationship is correct
- Check query filters related records properly
- Ensure
$withRelatedis configured - Test query independently
Performance Issues
Problem: Screen is slow with many records
Solutions:
- Enable virtual scrolling for master list
- Implement pagination for related lists
- Use lazy loading for detail data
- Optimize queries with proper indexes
- Cache detail data when appropriate
Mobile Layout Broken
Problem: Layout doesn't work on mobile
Solutions:
- Configure responsive breakpoints
- Use stacked layout for mobile
- Test on actual devices
- Adjust splitRatio for tablets
Summary
You now understand:
- Split view master-detail layouts
- Drill-down navigation patterns
- Tabbed detail views
- Modal detail overlays
- Hierarchical data display
- Mobile-responsive patterns
- Performance optimization techniques
- Best practices for master-detail UX
Next Steps
- Implementing Search - Add search to master views
- Performance Optimization - Optimize large datasets
- Custom Queries - Build efficient queries for detail views
- Building Workflows - Add workflows to detail screens