Notebook-UI Developer Guide
This guide helps application developers effectively use notebook-ui to build complete, consistent applications.
Quick Start
Installation
npm install github:kwhittenberger/notebook-uiRequired Peer Dependencies
npm install react react-dom react-router-dom lucide-react tailwindcssSetup
1. Import styles in your entry file (e.g., src/main.tsx):
import 'notebook-ui/styles';2. Configure Tailwind (tailwind.config.js):
import notebookConfig from 'notebook-ui/tailwind-config';
export default {
...notebookConfig,
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/notebook-ui/src/**/*.{js,ts,jsx,tsx}',
],
};Core Principles
1. No Custom HTML/CSS
notebook-ui provides ALL UI components. You should never:
- Create custom
<div>with Tailwind classes - Use inline styles
- Create custom styled components
Wrong:
<div className="flex gap-4 p-4 bg-white rounded-lg">
<h2 className="text-lg font-bold">Title</h2>
</div>Correct:
import { Card, CardHeader, CardTitle, Stack } from 'notebook-ui';
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
</CardHeader>
</Card>2. Use Layout Primitives
Instead of flexbox/grid classes, use layout components:
import { Stack, Grid, Box, Text } from 'notebook-ui';
// Vertical stack with spacing
<Stack spacing="md">
<Text>Item 1</Text>
<Text>Item 2</Text>
</Stack>
// Horizontal stack
<Stack direction="horizontal" spacing="sm" align="center">
<Button>Save</Button>
<Button variant="secondary">Cancel</Button>
</Stack>
// Grid layout
<Grid columns={3} gap="md">
<Card>Card 1</Card>
<Card>Card 2</Card>
<Card>Card 3</Card>
</Grid>
// Box for padding/margin/borders
<Box padding="lg" border="all">
<Text>Content with padding and border</Text>
</Box>3. Use Semantic Text
Always use the Text component instead of HTML elements:
import { Text } from 'notebook-ui';
// Headings
<Text as="h1" size="2xl" weight="bold">Page Title</Text>
<Text as="h2" size="xl" weight="semibold">Section Title</Text>
// Body text
<Text>Regular paragraph text</Text>
<Text size="sm" color="muted">Helper text</Text>
// Labels
<Text as="label" weight="medium">Field Label</Text>4. Icons from lucide-react
All icons must come from lucide-react:
import { Plus, Edit, Trash2, Search } from 'lucide-react';
import { Button, Input } from 'notebook-ui';
<Button icon={Plus}>Add Item</Button>
<Input icon={Search} placeholder="Search..." />Common Patterns
Page Layout
import {
Layout,
Page,
Sidebar,
Breadcrumbs,
Card,
CardHeader,
CardTitle,
CardContent
} from 'notebook-ui';
function MyPage() {
return (
<Layout
sidebar={<Sidebar items={navItems} currentPath="/my-page" />}
>
<Page title="My Page">
<Breadcrumbs items={[
{ label: 'Home', href: '/' },
{ label: 'My Page' }
]} />
<Card>
<CardHeader>
<CardTitle>Content</CardTitle>
</CardHeader>
<CardContent>
{/* Your content */}
</CardContent>
</Card>
</Page>
</Layout>
);
}Data Table with Actions
import { DataTable, Badge, Button } from 'notebook-ui';
import { Edit, Trash2 } from 'lucide-react';
const columns = [
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email', sortable: true },
{
key: 'status',
header: 'Status',
render: (row) => (
<Badge variant={row.status === 'active' ? 'success' : 'neutral'}>
{row.status}
</Badge>
)
},
];
const actions = [
{
label: 'Edit',
icon: Edit,
onClick: (row) => handleEdit(row)
},
{
label: 'Delete',
icon: Trash2,
onClick: (row) => handleDelete(row),
variant: 'danger'
},
];
<DataTable
data={users}
columns={columns}
actions={actions}
loading={isLoading}
selectable
onRowSelect={setSelectedRows}
onSortChange={handleSort}
currentSort={sort}
/>Form with Validation
import {
Card,
CardHeader,
CardTitle,
CardContent,
CardFooter,
Stack,
Input,
Select,
Textarea,
Button,
addSuccessMessage,
addErrorMessage
} from 'notebook-ui';
function MyForm() {
const [errors, setErrors] = useState({});
const handleSubmit = async (e) => {
e.preventDefault();
try {
await saveData(formData);
addSuccessMessage('Saved successfully');
} catch (error) {
addErrorMessage('Failed to save');
}
};
return (
<Card>
<CardHeader>
<CardTitle>Edit Profile</CardTitle>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent>
<Stack spacing="md">
<Input
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
validationState={errors.name ? 'error' : null}
validationMessage={errors.name}
required
/>
<Select
label="Country"
options={countries}
value={country}
onChange={setCountry}
searchable
/>
<Textarea
label="Bio"
value={bio}
onChange={(e) => setBio(e.target.value)}
maxLength={500}
showCounter
/>
</Stack>
</CardContent>
<CardFooter>
<Stack direction="horizontal" spacing="sm" justify="end">
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
<Button type="submit" loading={isSubmitting}>
Save
</Button>
</Stack>
</CardFooter>
</form>
</Card>
);
}Modal Dialog
import { Modal, ModalFooter, Button, Stack, Input } from 'notebook-ui';
function EditModal({ isOpen, onClose, onSave }) {
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title="Edit Item"
size="md"
>
<Stack spacing="md">
<Input label="Name" value={name} onChange={setName} />
<Input label="Email" value={email} onChange={setEmail} />
</Stack>
<ModalFooter>
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
<Button onClick={handleSave} loading={isSaving}>
Save Changes
</Button>
</ModalFooter>
</Modal>
);
}Confirmation Dialog
import { ConfirmDialog, useConfirmDialog, Button } from 'notebook-ui';
function DeleteButton({ item, onDelete }) {
const { isOpen, open, close } = useConfirmDialog();
const handleConfirm = async () => {
await onDelete(item.id);
close();
};
return (
<>
<Button variant="danger" onClick={open}>
Delete
</Button>
<ConfirmDialog
isOpen={isOpen}
onClose={close}
onConfirm={handleConfirm}
title="Delete Item"
message={`Are you sure you want to delete "${item.name}"?`}
variant="danger"
confirmLabel="Delete"
/>
</>
);
}Toast Notifications
import {
addSuccessMessage,
addErrorMessage,
addWarningMessage,
addInfoMessage
} from 'notebook-ui';
// In your component
async function handleSave() {
try {
await api.save(data);
addSuccessMessage('Item saved successfully');
} catch (error) {
addErrorMessage('Failed to save item');
}
}
// Warning
addWarningMessage('Your session will expire in 5 minutes');
// Info
addInfoMessage('New features are available');Loading States
import { Loading, Skeleton, SkeletonCard, SkeletonTable } from 'notebook-ui';
// Spinner
<Loading variant="spinner" size="lg" text="Loading..." />
// Skeleton for custom content
<Skeleton className="h-8 w-full" />
// Pre-built skeletons
<SkeletonCard />
<SkeletonTable rows={5} />Empty State
import { EmptyState } from 'notebook-ui';
import { FileText, Plus } from 'lucide-react';
<EmptyState
icon={FileText}
title="No Documents"
description="Get started by creating your first document"
primaryAction="Create Document"
onPrimaryAction={() => setShowCreateModal(true)}
secondaryAction="Import"
onSecondaryAction={() => setShowImportModal(true)}
/>Tabs
import { Tabs } from 'notebook-ui';
import { User, Settings, CreditCard } from 'lucide-react';
const tabs = [
{ id: 'profile', label: 'Profile', icon: User },
{ id: 'settings', label: 'Settings', icon: Settings },
{ id: 'billing', label: 'Billing', icon: CreditCard },
];
<Tabs
tabs={tabs}
activeTab={activeTab}
onChange={setActiveTab}
variant="underline"
/>
{activeTab === 'profile' && <ProfilePanel />}
{activeTab === 'settings' && <SettingsPanel />}
{activeTab === 'billing' && <BillingPanel />}Filter Bar
import { FilterBar } from 'notebook-ui';
const filters = [
{
key: 'status',
label: 'Status',
type: 'select',
options: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
]
},
{
key: 'search',
label: 'Search',
type: 'text',
placeholder: 'Search by name...'
},
{
key: 'dateRange',
label: 'Date',
type: 'date'
}
];
<FilterBar
filters={filters}
values={filterValues}
onChange={handleFilterChange}
onClear={handleClearFilters}
/>Display Components
Currency
import { CurrencyDisplay } from 'notebook-ui';
<CurrencyDisplay amount={1234.56} />
// $1,234.56
<CurrencyDisplay amount={1234.56} currency="EUR" />
// €1,234.56
<CurrencyDisplay amount={-500} color="error" showSign />
// -$500.00 (in red)Dates
import { DateDisplay } from 'notebook-ui';
<DateDisplay date={new Date()} />
// Nov 19, 2025
<DateDisplay date={new Date()} format="long" />
// November 19, 2025
<DateDisplay date={null} fallback="Not set" />
// Not setBadges
import { Badge, StatusBadge } from 'notebook-ui';
import { Star } from 'lucide-react';
// Generic badge
<Badge variant="success">Active</Badge>
<Badge variant="warning" icon={Star}>Featured</Badge>
// Status badge with auto icon
<StatusBadge status="paid" />
<StatusBadge status="pending" />
<StatusBadge status="overdue" />When a Component is Missing
If notebook-ui doesn't have a component you need:
1. Check the Inventory
Review docs/COMPONENT-INVENTORY.md to see if:
- The component exists under a different name
- There's a similar component that can be adapted
- It's on the roadmap
2. Do NOT Create It in Your App
Creating custom components in host apps violates the design system principle.
3. Add It to notebook-ui
Step 1: Create the component in src/components/:
// src/components/MyNewComponent.tsx
import React from 'react';
export interface MyNewComponentProps {
// props
}
export default function MyNewComponent(props: MyNewComponentProps) {
return (/* implementation */);
}Step 2: Export from src/components/index.ts:
export { default as MyNewComponent } from './MyNewComponent';
export type { MyNewComponentProps } from './MyNewComponent';Step 3: Build:
npm run buildStep 4: Update in your app:
npm update notebook-ui4. Component Guidelines
When creating new components:
- Use design tokens:
text-ink-600,bg-paper-50,border-paper-200 - Follow existing patterns: Look at similar components for API design
- Support variants/sizes: Use the standard scales (
sm | md | lg, etc.) - Add TypeScript types: Export both component and props interface
- Include accessibility: ARIA labels, keyboard support, focus management
- Keep it generic: Avoid app-specific logic
Best Practices
1. Spacing Consistency
Use the spacing scale consistently:
xs: 4px - tight spacingsm: 8px - compact layoutsmd: 16px - standard spacing (default)lg: 24px - comfortable breathing roomxl: 32px - section separation
2. Validation States
Use the validation system for all form inputs:
<Input
validationState={error ? 'error' : success ? 'success' : null}
validationMessage={error || successMessage}
/>3. Loading States
Always show loading states when fetching data:
<DataTable
data={data}
loading={isLoading}
// ...
/>4. Error Handling
Use toast messages for async operations:
try {
await operation();
addSuccessMessage('Success');
} catch (error) {
addErrorMessage(error.message || 'Operation failed');
}5. Accessibility
- Use semantic elements via
asprop on Text - Provide icon buttons with
aria-label - Use ConfirmDialog for destructive actions
- Support keyboard navigation
Type Safety
notebook-ui exports TypeScript types for all components:
import type {
ButtonProps,
DataTableColumn,
SelectOption,
SortConfig,
PaginationResponse
} from 'notebook-ui';
// Define typed columns
const columns: DataTableColumn<User>[] = [
{ key: 'name', header: 'Name', sortable: true },
];
// Define typed options
const options: SelectOption[] = [
{ value: '1', label: 'Option 1' },
];Troubleshooting
Styles Not Applying
- Ensure you imported styles:
import 'notebook-ui/styles'; - Check Tailwind content paths include notebook-ui
- Rebuild your app after notebook-ui updates
Component Not Found
- Check the correct export name in
src/components/index.ts - Run
npm update notebook-uito get latest version - Restart your dev server
TypeScript Errors
- Ensure
@types/reactand@types/react-domare installed - Check that you're importing types correctly:typescript
import type { ButtonProps } from 'notebook-ui';
Docker Development
When developing with Docker:
# After notebook-ui changes, rebuild the image
docker-compose build --no-cache frontend
# Hard refresh browser
Ctrl+Shift+RGetting Help
- Review this guide and the component inventory
- Check existing components for patterns
- Look at the README.md for examples
- File an issue for missing features
Component Quick Reference
Form Inputs
Button Input Textarea Select MultiSelect Switch Checkbox RadioGroup
Layout
Stack Grid GridItem Box Text Card (+ Header, Title, Content, Footer)
Data Display
DataTable Table Badge StatusBadge Avatar CurrencyDisplay DateDisplay EmptyState
Feedback
Toast (+ addSuccessMessage, etc.) Alert Modal (+ ModalFooter) ConfirmDialog Tooltip
Navigation
Sidebar Breadcrumbs Tabs Pagination StepIndicator Accordion Dropdown
Loading
Loading Skeleton SkeletonCard SkeletonTable LoadingOverlay
Utilities
FilterBar ControlBar ExportButton QueryTransparency
App Layout
Layout AppLayout PageLayout Page Dashboard