3.1 — Tab Bar & Document Lifecycle
Next Step: → Sidebar
Page Metadata
| Property |
Value |
| Scenario |
03 — Document Management |
| Page Number |
3.1 |
| Platform |
Web (PWA), portable to macOS/Windows |
| Page Type |
Embedded Strip (within App Shell, between header and editor) |
Overview
Page Purpose: Enable multi-document workflow. Users can open, create, close, reorder, and rename documents via tabs. The tab bar is the primary navigation between open documents.
Success Criteria:
- Switching tabs feels instant (< 50ms)
- Users never lose work (auto-save before switch)
- Tab state survives page reload
- 15+ tabs remain usable (horizontal scroll)
Layout Structure
Spacing
| Property |
Token |
Pixels |
| Tab bar height |
— |
36px |
| Tab padding horizontal |
space-sm |
8px |
| Tab gap |
— |
0px (tabs are flush, separated by 1px border) |
| Tab min width |
— |
100px |
| Tab max width |
— |
200px |
| Close button size |
— |
16px × 16px |
| Close button margin-left |
space-xs |
6px |
| Modified dot size |
— |
6px |
| Modified dot margin-right |
space-2xs |
4px |
| New tab button width |
— |
36px (square) |
Typography
| Element |
Size |
Weight |
Color |
| Tab label (active) |
text-xs |
500 |
var(--text-h) |
| Tab label (inactive) |
text-xs |
400 |
var(--text) |
| Close × |
text-xs |
400 |
var(--text) at 50%, hover → var(--text) |
| New + icon |
text-sm |
300 |
var(--text), hover → var(--accent) |
Tab States
Active Tab
| Property |
Value |
| Background |
var(--bg) — same as editor, creates visual connection |
| Border bottom |
None — tab "opens into" the editor |
| Border left/right |
1px solid var(--border) |
| Border top |
2px solid var(--accent) — active indicator |
| Label |
Weight 500, var(--text-h) |
| Close button |
Always visible |
Inactive Tab
| Property |
Value |
| Background |
var(--bg-secondary) |
| Border bottom |
1px solid var(--border) — closed off from editor |
| Border left/right |
1px solid var(--border) |
| Border top |
2px solid transparent |
| Label |
Weight 400, var(--text) |
| Close button |
Visible on hover only |
Hover (Inactive)
| Property |
Value |
| Background |
Blend between bg-secondary and bg (subtle lighten) |
| Transition |
background 0.1s |
Dragging
| Property |
Value |
| Appearance |
Tab lifts with subtle shadow (0 2px 8px rgba(0,0,0,0.15)) |
| Opacity |
90% |
| Placeholder |
2px var(--accent) vertical line at insertion point |
| Cursor |
grabbing |
Interactions
| Action |
Behavior |
| Click tab |
Switch to document. Auto-save current. Restore editor state (content, cursor, scroll, undo). |
| Click + |
Create "Untitled" doc, open in new tab, focus editor. |
| Click × |
Close tab. If unsaved and modified, no prompt (auto-saved). Remove from openTabIds. |
| Middle-click tab |
Close tab (same as ×). |
| Double-click tab |
Inline rename — label becomes input field, Enter confirms, Esc cancels. |
| Drag tab |
Reorder. Drop position shown by accent line indicator. |
| Ctrl/Cmd+Tab |
Next tab (wraps). |
| Ctrl/Cmd+Shift+Tab |
Previous tab (wraps). |
| Ctrl/Cmd+W |
Close active tab. If last tab, create new "Untitled". |
| Ctrl/Cmd+N |
New document + tab. |
| Ctrl/Cmd+1–9 |
Jump to tab by position. |
| Mouse wheel on tab bar |
Horizontal scroll when tabs overflow. |
Tab Overflow
| Property |
Value |
| Trigger |
Total tab width > container width |
| Scroll |
Horizontal, smooth, via mouse wheel or trackpad |
| Indicators |
16px fade gradient on left/right edges when scrollable |
| Active tab |
Auto-scrolls into view when selected via keyboard |
| New tab button |
Sticky right — always visible outside scroll area |
Document Lifecycle
Create
| Trigger |
Behavior |
| Click [+] |
New doc: { id: uuid(), title: "Untitled", content: "", folderId: null } |
| Sidebar template click |
New doc with template content and suggested title |
| Ctrl/Cmd+N |
Same as [+] |
Title auto-increments: "Untitled", "Untitled 2", "Untitled 3"...
Rename
| Trigger |
Behavior |
| Double-click tab |
Label becomes <input>, pre-selected text, 200px max width |
| Sidebar right-click → Rename |
Same inline editing in sidebar |
| Enter |
Confirm rename, update document and sidebar |
| Esc |
Cancel, revert to previous name |
| Blur |
Confirm (same as Enter) |
| Empty name |
Revert to "Untitled" |
Delete
| Trigger |
Behavior |
| Sidebar right-click → Delete |
Confirmation: "Delete '{title}'? This cannot be undone." |
| Confirm |
Remove from documents[], close tab if open, remove from folder |
| Cancel |
No action |
| Undo |
5-second toast: "Document deleted. [Undo]" — restores document if clicked |
Duplicate
| Trigger |
Behavior |
| Sidebar right-click → Duplicate |
New doc: same content, title = "{original} (copy)", same folder |
| Opens in new tab automatically |
|
Auto-Save
| Property |
Value |
| Trigger |
Document content change |
| Debounce |
500ms after last keystroke |
| Storage |
localStorage (calctext-documents) |
| Indicator |
Modified dot (●) appears immediately on change, disappears on save |
| Manual save |
Ctrl/Cmd+S shows brief checkmark animation in tab (visual confirmation) |
Modified Indicator
| Property |
Value |
| Shape |
Filled circle, 6px |
| Color |
var(--text) at 60% |
| Position |
Before tab label, 4px gap |
| Appears |
On first character change |
| Disappears |
On auto-save completion (500ms debounce) |
| Animation |
Fade in 0.2s |
| Item |
Action |
| Close |
Close this tab |
| Close Others |
Close all tabs except this one |
| Close to the Right |
Close all tabs to the right |
| — |
(separator) |
| Rename |
Inline rename |
| Duplicate |
Duplicate document |
| — |
(separator) |
| Reveal in Sidebar |
Scroll sidebar to show this file |
Mobile Adaptations
| Property |
Value |
| Tab bar |
Horizontal scroll, touch-friendly |
| Tab height |
40px (larger touch target) |
| Close button |
Hidden — swipe left on tab to reveal close |
| New tab |
[+] button at far right |
| Rename |
Long-press → context menu → Rename |
| Reorder |
Long-press + drag |
Page States
| State |
When |
Behavior |
| Single tab |
Only one document open |
Tab still shown (establishes pattern). Close creates new "Untitled". |
| Many tabs (>10) |
Heavy usage |
Horizontal scroll. Active tab auto-scrolls into view. |
| All tabs closed |
User closed everything |
Auto-create "Untitled" tab (workspace never empty). |
| First launch |
No localStorage |
Single "Welcome" tab with demo content. |
Technical Notes
- Tab switching performance: Store
EditorState per tab in memory. On switch: destroy current EditorView, create new with stored state. Content swap < 20ms.
- Tab order persistence:
openTabIds: string[] in localStorage maintains order.
- Cross-platform: Maps to native tab bars. macOS: NSTabView or custom tab strip. Windows: iced tabs widget.
- Max tabs: Soft limit at 20 with performance hint in status bar. No hard limit.
Created using Whiteport Design Studio (WDS) methodology