| title | Layout DSL |
|---|---|
| description | Declarative layout description language for pages, sections, and responsive grids |
import { Layout, Grid, Columns, Box, Monitor, Smartphone, Tablet, MousePointer } from 'lucide-react';
The Layout DSL (Domain-Specific Language) is ObjectUI's declarative syntax for defining page structure, sections, and responsive grids. It abstracts away CSS Grid, Flexbox, and platform-specific layout engines.
Instead of giving you infinite layout freedom (and infinite ways to break responsive design), ObjectUI provides a constrained, opinionated layout system:
- 12-Column Grid: All layouts use a 12-column responsive grid
- Section-Based: Pages are composed of sections, sections contain fields/widgets
- Auto-Responsive: Breakpoints applied automatically (no media queries)
- Platform-Agnostic: Same DSL renders as CSS Grid (web), Auto Layout (iOS), ConstraintLayout (Android)
Why Constraints?
- Consistency: All forms look professionally designed
- Accessibility: Keyboard navigation and screen readers work automatically
- Maintainability: Designers can't create 47 different button sizes
Page
├─ Header (Title, Actions, Breadcrumbs)
├─ Main Region
│ ├─ Section 1
│ │ ├─ Field Group
│ │ │ ├─ Field A (span: 6)
│ │ │ └─ Field B (span: 6)
│ │ └─ Widget
│ └─ Section 2
│ └─ Related List
└─ Sidebar Region
├─ Widget: Quick Stats
└─ Widget: Activity Feed
Top-level layout structures that define macro organization.
context: record
object: account
template: standard
regions:
- name: header
components:
- type: page:header
properties:
title: Customer Details
actions: [edit, delete, share]
- name: main
components:
- type: page:section
properties:
label: Contact Information
fields: [name, email, phone]
- name: sidebar
components:
- type: widget
properties:
component: activity_feedVisual Layout:
┌────────────────────────────────────────────────────┐
│ Header: Customer Details [Edit] [Delete] │
├──────────────────────────────┬─────────────────────┤
│ │ │
│ Main Region │ Sidebar │
│ │ │
│ ┌────────────────────────┐ │ ┌───────────────┐ │
│ │ Contact Information │ │ │ Activity Feed │ │
│ │ Name: ____________ │ │ │ • Called │ │
│ │ Email: ___________ │ │ │ • Emailed │ │
│ │ Phone: ___________ │ │ └───────────────┘ │
│ └────────────────────────┘ │ │
│ │ │
└──────────────────────────────┴─────────────────────┘
Breakpoint Behavior:
- Desktop (≥1024px): Sidebar on right (25% width)
- Tablet (768-1023px): Sidebar below main (full width)
- Mobile (less than 768px): Sidebar below main (full width)
High-density layout for power users (like Salesforce Service Console).
context: app
template: console
regions:
- name: left
width: small
components:
- type: record:list
properties:
object: case
- name: right
width: full
components:
- type: record:details
properties:
tabs: [details, related, activity]Visual Layout:
┌──────────────┬─────────────────────────────────────┐
│ │ Case #12345 │
│ Case List │ ┌───┬─────────┬──────────┬────────┐│
│ │ │Det│ Related │ Activity │ ││
│ □ Case #123 │ └───┴─────────┴──────────┴────────┘│
│ ■ Case #124 │ │
│ □ Case #125 │ Subject: Email not working │
│ □ Case #126 │ Priority: High │
│ │ Status: In Progress │
│ │ │
│ │ [Resolve] [Escalate] │
└──────────────┴─────────────────────────────────────┘
Use Cases:
- Customer service (Case management)
- Sales (Opportunity pipeline + details)
- Helpdesk (Ticket queue + ticket details)
Multi-step flow with progress indicator.
# page_template: wizard
steps:
- name: account_info
label: Account Information
fields: [company_name, industry, size]
- name: contact_details
label: Contact Details
fields: [name, email, phone]
- name: preferences
label: Preferences
fields: [newsletter, notifications]
- name: review
label: Review & Submit
type: summaryVisual Layout:
┌────────────────────────────────────────────────────┐
│ New Customer Setup │
│ ● Account Info → ○ Contact → ○ Preferences → ○ │
├────────────────────────────────────────────────────┤
│ │
│ Account Information │
│ │
│ Company Name: _______________________________ │
│ Industry: [Select Industry ▼] │
│ Company Size: ○ 1-10 ○ 11-50 ○ 51-200 │
│ │
│ [Cancel] [Next Step →] │
└────────────────────────────────────────────────────┘
All layouts use a responsive grid that divides space into 12 columns.
section:
label: Contact Information
columns: 2 # Each field takes 6/12 columns (50% width)
fields:
- name # Column 1-6
- email # Column 7-12
- phone # Column 1-6 (new row)
- company # Column 7-12Rendered Grid:
┌──────────────────────┬──────────────────────┐
│ Name: ______________ │ Email: _____________ │
├──────────────────────┼──────────────────────┤
│ Phone: _____________ │ Company: ___________ │
└──────────────────────┴──────────────────────┘
section:
label: Product Details
layout:
- field: product_name
span: 12 # Full width
- field: price
span: 4 # 33% width
- field: quantity
span: 4 # 33% width
- field: total
span: 4 # 33% width
- field: description
span: 12 # Full widthRendered Grid:
┌─────────────────────────────────────────────────┐
│ Product Name: _________________________________│
├───────────────┬───────────────┬────────────────┤
│ Price: $_____ │ Qty: ________ │ Total: $______ │
├───────────────┴───────────────┴────────────────┤
│ Description: │
│ ____________________________________________ │
│ ____________________________________________ │
└─────────────────────────────────────────────────┘
Grid automatically collapses on smaller screens:
section:
columns: 3 # Desktop: 3 columns, Tablet: 2 columns, Mobile: 1 column
fields: [field_a, field_b, field_c, field_d, field_e, field_f]Desktop (≥1024px): 3 columns
┌──────────┬──────────┬──────────┐
│ Field A │ Field B │ Field C │
├──────────┼──────────┼──────────┤
│ Field D │ Field E │ Field F │
└──────────┴──────────┴──────────┘
Tablet (768-1023px): 2 columns
┌──────────────┬──────────────┐
│ Field A │ Field B │
├──────────────┼──────────────┤
│ Field C │ Field D │
├──────────────┼──────────────┤
│ Field E │ Field F │
└──────────────┴──────────────┘
Mobile (less than 768px): 1 column
┌────────────────────────┐
│ Field A │
├────────────────────────┤
│ Field B │
├────────────────────────┤
│ Field C │
├────────────────────────┤
│ Field D │
├────────────────────────┤
│ Field E │
├────────────────────────┤
│ Field F │
└────────────────────────┘
Sections are collapsible containers for related fields.
sections:
- label: Contact Information
collapsible: true
collapsed: false # Expanded by default
fields:
- name
- email
- phoneRendered:
┌─ Contact Information ──────────────────────────▼─┐
│ │
│ Name: __________________________________________│
│ Email: __________________________________________│
│ Phone: __________________________________________│
│ │
└──────────────────────────────────────────────────┘
Show sections based on field values or permissions:
sections:
- label: Basic Info
fields: [name, email]
- label: Billing Information
visible:
field: account_type
value: premium # Only show for premium accounts
fields: [payment_method, billing_address]
- label: Admin Settings
visible:
permission: admin # Only show to admins
fields: [api_key, rate_limit]sections:
- label: Quick Summary
variant: compact # Reduced padding
columns: 4
fields: [status, priority, assignee, due_date]
- label: Description
variant: spacious # Extra padding
fields: [long_description]
- label: Danger Zone
variant: danger # Red border, warning icon
fields: [archive, delete]Group related fields on the same row:
sections:
- label: Name
layout:
- type: field_group
inline: true
fields:
- field: first_name
span: 6
placeholder: First
- field: last_name
span: 6
placeholder: LastRendered:
Name
┌──────────────────────┬──────────────────────┐
│ First: _____________ │ Last: ______________ │
└──────────────────────┴──────────────────────┘
- type: field_group
label: Address
fields:
- field: street
span: 12
- field: city
span: 6
- field: state
span: 3
- field: postal_code
span: 3Rendered:
Address
┌─────────────────────────────────────────────────┐
│ Street: _______________________________________│
├──────────────────────┬───────────┬────────────┤
│ City: _______________ │ State: __ │ ZIP: _____ │
└──────────────────────┴───────────┴────────────┘
Organize large forms into tabbed sections.
layout:
mode: tabbed
tabs:
- name: details
label: Details
icon: file-text
sections:
- label: Basic Info
fields: [name, email, phone]
- name: address
label: Address
icon: map-pin
sections:
- label: Primary Address
fields: [street, city, state, zip]
- name: preferences
label: Preferences
icon: settings
sections:
- label: Notifications
fields: [email_notifications, sms_notifications]Rendered:
┌──────────────────────────────────────────────────┐
│ [Details] [Address] [Preferences] │
├──────────────────────────────────────────────────┤
│ │
│ Basic Info │
│ Name: __________________________________________│
│ Email: __________________________________________│
│ Phone: __________________________________________│
│ │
└──────────────────────────────────────────────────┘
Load tab content only when clicked (performance optimization):
tabs:
- name: details
label: Details
lazy: false # Load immediately
- name: history
label: History (1,234 records)
lazy: true # Load when tab clicked
source: /api/customers/123/historytabs:
- name: details
label: Details
- name: tasks
label: Tasks
badge: 5 # Show "5" badge
badgeVariant: danger # Red badge
- name: notes
label: Notes
badge: { count: 12, variant: info }fields:
- name: detailed_description
span: 12
visible:
desktop: true # Show on desktop
tablet: true # Show on tablet
mobile: false # Hide on mobile
- name: short_summary
span: 12
visible:
desktop: false # Hide on desktop
tablet: false # Hide on tablet
mobile: true # Show on mobilesection:
columns:
portrait: 1 # 1 column in portrait mode
landscape: 2 # 2 columns in landscape mode
fields: [field_a, field_b, field_c]Display child records within a parent record's page.
sections:
- type: related_list
label: Contacts
object: contact
relationField: account_id # contact.account_id → account.id
columns:
- name
- email
- phone
actions:
- type: standard_new
label: New ContactRendered:
┌─ Contacts ─────────────────────────────── [+ New Contact] ─┐
│ │
│ Name Email Phone │
│ ──────────────── ───────────────── ───────────────── │
│ John Doe john@acme.com 555-1234 │
│ Jane Smith jane@acme.com 555-5678 │
│ │
│ Showing 2 of 2 contacts │
└─────────────────────────────────────────────────────────────┘
sections:
- type: related_list
label: Invoice Line Items
object: invoice_line
relationField: invoice_id
mode: inline_edit # Edit cells directly
columns:
- field: product
type: lookup
object: product
- field: quantity
type: number
editable: true
- field: unit_price
type: currency
editable: true
- field: total
type: formula
formula: quantity * unit_price
editable: false
actions:
- type: add_row
label: Add Line ItemWidgets are pre-built UI components that display data or provide functionality.
sections:
- type: widget
component: metric
config:
title: Open Opportunities
value: 47
trend: +12%
trendDirection: up
icon: trending-up
color: successRendered:
┌─────────────────────┐
│ Open Opportunities │
│ │
│ 47 ↑ +12% │
│ │
└─────────────────────┘
sections:
- type: widget
component: activity_feed
config:
object: activity
filter: { related_to: '{recordId}' }
limit: 10
showFilters: truesections:
- type: widget
component: custom.approval_timeline
config:
recordId: '{recordId}'
showComments: truelayout:
type: split_view
orientation: horizontal # or 'vertical'
split: 60 # Left: 60%, Right: 40%
left:
type: list_view
object: opportunity
view: my_opportunities
right:
type: detail_view
object: opportunity
recordId: '{selectedRecordId}'layout:
type: card_grid
columns:
desktop: 3
tablet: 2
mobile: 1
cards:
- type: metric_card
title: Revenue
value: $1.2M
- type: metric_card
title: Deals
value: 47
- type: chart_card
title: Pipeline
chartType: funnellayout:
type: kanban
object: project_task
groupBy: status
columns:
- value: todo
label: To Do
color: gray
- value: in_progress
label: In Progress
color: blue
- value: done
label: Done
color: green
cardFields:
- title
- assignee
- due_dateinterface PageLayout {
template: 'standard' | 'console' | 'wizard';
title?: string;
subtitle?: string;
icon?: string;
regions: {
header?: Component[];
main: Component[];
sidebar?: Component[];
footer?: Component[];
};
responsive?: ResponsiveConfig;
}interface Section {
type: 'section';
label: string;
description?: string;
icon?: string;
collapsible?: boolean;
collapsed?: boolean;
columns?: number | ResponsiveColumns;
variant?: 'default' | 'compact' | 'spacious' | 'danger';
visible?: VisibilityRule;
fields: FieldReference[];
}interface FieldLayout {
field: string; // Field name from ObjectQL schema
span?: number; // Column span (1-12)
offset?: number; // Column offset
label?: string; // Override label
placeholder?: string;
helpText?: string;
required?: boolean;
readonly?: boolean;
visible?: VisibilityRule;
}interface ResponsiveColumns {
desktop?: number; // ≥1024px
tablet?: number; // 768-1023px
mobile?: number; // less than 768px
portrait?: number;
landscape?: number;
}type VisibilityRule =
| { field: string; value: any } // Show if field equals value
| { permission: string } // Show if user has permission
| { expression: string } // Show if expression evaluates to true
| { desktop?: boolean; tablet?: boolean; mobile?: boolean };name: customer_360
object: customer
template: standard
regions:
header:
- type: title
field: name
- type: actions
buttons: [edit, delete, share, convert]
main:
- type: section
label: Overview
columns: 2
fields:
- name
- status
- industry
- employee_count
- website
- phone
- type: section
label: Key Contacts
columns: 1
component:
- type: related_list
object: contact
relationField: account_id
columns: [name, title, email, phone]
- type: section
label: Open Opportunities
component:
- type: related_list
object: opportunity
relationField: account_id
filter: { stage: { $ne: 'closed_won' } }
columns: [name, amount, close_date, stage]
sidebar:
- type: widget
component: metric
config:
title: Total Revenue
value: $1.2M
- type: widget
component: activity_feed
config:
limit: 10name: employee_onboarding
object: employee
template: wizard
steps:
- name: personal_info
label: Personal Information
sections:
- label: Basic Details
columns: 2
fields:
- first_name
- last_name
- email
- phone
- date_of_birth
- ssn
- name: employment
label: Employment Details
sections:
- label: Position
fields:
- job_title
- department
- manager
- start_date
- employment_type
- name: compensation
label: Compensation
sections:
- label: Salary & Benefits
fields:
- base_salary
- bonus_eligible
- equity_grant
- benefits_plan
- name: review
label: Review & Submit
type: summary
sections:
- type: summary_view
showAllFields: trueLoad sections/tabs only when visible:
sections:
- label: Details
lazy: false # Load immediately
- label: History (10,000 records)
lazy: true # Load when section expanded
source: /api/customers/123/historyFor long lists of fields:
section:
label: Product Catalog (1,000 items)
virtualScroll: true
itemHeight: 60 # px
fields: [...] # Large arrayRender above-the-fold content first:
layout:
renderStrategy: progressive
priority:
- sections[0] # Render first section immediately
- sections[1] # Render second section after 100ms
- sections[2] # Render third section after 200ms- FormView Reference - Complete form configuration API
- ListView Reference - Table, kanban, calendar views
- Responsive Design Guide - Best practices
- Accessibility Guide - WCAG 2.1 AA compliance