Interfaces
Forms, workspaces, and UI for human tasks
Human Interfaces
Define how humans interact with tasks through forms, workspaces, and custom UI components.
Forms
Basic Form
import { human } from 'human-in-the-loop'
const approvalForm = human.form({
name: 'expense-approval',
fields: [
{
name: 'decision',
type: 'select',
options: ['approve', 'reject', 'request-more-info'],
required: true,
},
{
name: 'notes',
type: 'textarea',
placeholder: 'Add any notes...',
required: false,
},
],
})Field Types
const comprehensiveForm = human.form({
name: 'detailed-form',
fields: [
// Text inputs
{ name: 'title', type: 'text', maxLength: 100 },
{ name: 'description', type: 'textarea', rows: 4 },
{ name: 'email', type: 'email' },
{ name: 'phone', type: 'phone' },
{ name: 'url', type: 'url' },
// Numbers
{ name: 'amount', type: 'number', min: 0, max: 10000 },
{ name: 'rating', type: 'rating', scale: 5 },
{ name: 'slider', type: 'slider', min: 0, max: 100, step: 10 },
// Selection
{ name: 'category', type: 'select', options: ['A', 'B', 'C'] },
{ name: 'tags', type: 'multiselect', options: ['tag1', 'tag2', 'tag3'] },
{ name: 'priority', type: 'radio', options: ['low', 'medium', 'high'] },
{ name: 'features', type: 'checkbox', options: ['f1', 'f2', 'f3'] },
// Date/Time
{ name: 'dueDate', type: 'date' },
{ name: 'meetingTime', type: 'datetime' },
{ name: 'duration', type: 'duration' },
// Files
{ name: 'attachment', type: 'file', accept: '.pdf,.doc' },
{ name: 'images', type: 'files', accept: 'image/*', maxFiles: 5 },
// Rich content
{ name: 'content', type: 'richtext' },
{ name: 'code', type: 'code', language: 'typescript' },
{ name: 'markdown', type: 'markdown' },
// Special
{ name: 'signature', type: 'signature' },
{ name: 'location', type: 'location' },
],
})Conditional Fields
const conditionalForm = human.form({
name: 'conditional-form',
fields: [
{
name: 'decision',
type: 'select',
options: ['approve', 'reject', 'defer'],
},
{
name: 'reason',
type: 'textarea',
showWhen: 'decision === "reject"',
required: { when: 'decision === "reject"' },
},
{
name: 'deferUntil',
type: 'date',
showWhen: 'decision === "defer"',
},
{
name: 'adjustedAmount',
type: 'number',
showWhen: 'decision === "approve"',
label: 'Adjusted Amount (optional)',
},
],
})Form Validation
const validatedForm = human.form({
name: 'validated-form',
fields: [
{
name: 'email',
type: 'email',
required: true,
validation: {
pattern: /^[\w-\.]+@company\.com$/,
message: 'Must be a company email',
},
},
{
name: 'amount',
type: 'number',
validation: {
min: 0,
max: { value: 'context.budget', message: 'Exceeds budget' },
custom: (value, form) =>
value > form.previousAmount ? 'Cannot increase amount' : null,
},
},
],
// Form-level validation
validate: (values) => {
const errors = {}
if (values.endDate < values.startDate) {
errors.endDate = 'End date must be after start date'
}
return errors
},
})Workspaces
Basic Workspace
const reviewWorkspace = human.workspace({
name: 'document-review',
layout: 'split-view',
panels: [
{
type: 'document-viewer',
source: 'input.document',
position: 'left',
},
{
type: 'form',
form: reviewForm,
position: 'right',
},
],
})Multi-Panel Workspace
const analysisWorkspace = human.workspace({
name: 'data-analysis',
layout: 'grid',
panels: [
{
id: 'data',
type: 'data-table',
source: 'input.dataset',
position: { row: 1, col: 1, colspan: 2 },
features: ['sort', 'filter', 'search'],
},
{
id: 'chart',
type: 'chart',
source: 'input.dataset',
chartType: 'auto',
position: { row: 1, col: 3 },
},
{
id: 'suggestions',
type: 'ai-suggestions',
source: 'context.analysis',
position: { row: 2, col: 1 },
},
{
id: 'notes',
type: 'notes',
position: { row: 2, col: 2 },
},
{
id: 'decision',
type: 'form',
form: decisionForm,
position: { row: 2, col: 3 },
},
],
})Panel Types
const panels = [
// Content viewers
{ type: 'document-viewer', source: 'input.doc' },
{ type: 'pdf-viewer', source: 'input.pdf' },
{ type: 'image-viewer', source: 'input.images' },
{ type: 'code-viewer', source: 'input.code', language: 'typescript' },
{ type: 'diff-viewer', source: { old: 'input.old', new: 'input.new' } },
// Data display
{ type: 'data-table', source: 'input.data' },
{ type: 'chart', source: 'input.metrics', chartType: 'line' },
{ type: 'json-viewer', source: 'input.json' },
{ type: 'timeline', source: 'input.events' },
// Interactive
{ type: 'form', form: myForm },
{ type: 'annotation-tools' },
{ type: 'drawing-canvas' },
{ type: 'notes' },
// AI assistance
{ type: 'ai-suggestions', source: 'context.suggestions' },
{ type: 'ai-chat', agent: 'assistant' },
{ type: 'similar-items', source: 'context.similar' },
// Context
{ type: 'context-panel', source: 'context' },
{ type: 'history', source: 'context.history' },
{ type: 'related-items', source: 'context.related' },
]Context Display
Showing Context
const contextualTask = human.task({
name: 'contextual-review',
context: {
// What to show
show: ['request', 'customer', 'history', 'policy'],
// How to display
display: {
request: { panel: 'main', highlight: ['amount', 'category'] },
customer: { panel: 'sidebar', compact: true },
history: { panel: 'bottom', type: 'timeline' },
policy: { panel: 'collapsible', expanded: false },
},
},
})Context Sections
const structuredContext = human.task({
name: 'structured-context',
context: {
sections: [
{
title: 'Request Details',
source: 'input.request',
display: 'card',
fields: ['id', 'amount', 'category', 'description'],
},
{
title: 'Customer Profile',
source: 'context.customer',
display: 'profile',
compact: true,
},
{
title: 'Similar Past Requests',
source: 'context.similar',
display: 'list',
maxItems: 5,
},
{
title: 'AI Analysis',
source: 'context.aiAnalysis',
display: 'formatted',
highlight: ['recommendation', 'confidence'],
},
],
},
})Custom Components
import { registerComponent } from 'human-in-the-loop'
// Register custom component
registerComponent({
name: 'custom-rating',
props: {
value: { type: 'number' },
max: { type: 'number', default: 5 },
labels: { type: 'array' },
},
render: ({ value, max, labels, onChange }) => (
<div className="rating">
{Array.from({ length: max }, (_, i) => (
<button
key={i}
className={i < value ? 'active' : ''}
onClick={() => onChange(i + 1)}
>
{labels?.[i] || i + 1}
</button>
))}
</div>
),
})
// Use in form
const form = human.form({
fields: [
{
name: 'satisfaction',
type: 'custom-rating',
max: 5,
labels: ['Poor', 'Fair', 'Good', 'Great', 'Excellent'],
},
],
})Themes and Styling
const styledTask = human.task({
name: 'styled-task',
ui: {
theme: 'light', // 'light' | 'dark' | 'auto'
styles: {
primaryColor: '#0066cc',
accentColor: '#00cc66',
fontFamily: 'Inter, sans-serif',
},
branding: {
logo: 'https://company.com/logo.png',
title: 'Company Review Portal',
},
},
})Mobile Support
const mobileTask = human.task({
name: 'mobile-friendly',
ui: {
responsive: true,
mobile: {
layout: 'stack', // Stack panels vertically
simplify: true, // Use simplified controls
offlineSupport: true,
},
tablet: {
layout: 'sidebar',
},
desktop: {
layout: 'grid',
},
},
})Best Practices
- Keep forms simple - Only ask for necessary information
- Smart defaults - Pre-fill when possible
- Clear validation - Immediate, helpful error messages
- Responsive design - Support all device sizes
- Show context - Give humans the information they need
- Progress indication - Show form completion status
Was this page helpful?