11 — Components (@locara/components)
The UI primitive layer. Designed for two audiences:
- Human developers who want to ship a polished app without designing every UX detail.
- AI coding assistants who can read, modify, and extend components in the user’s repo.
Distribution model: shadcn-style copy-in source
Locara components are not installed as an npm package. They’re copied into the user’s repo as source code, via the CLI:
locara add chat
# → copies src/components/ui/chat.tsx into your project
locara add transcript-stream doc-dropzone
# → copies multiple at once
locara add --all
# → copies all curated components
The user owns the source. They edit it freely. Updates are pulled by re-running locara add, with optional diff-and-merge.
This approach (pioneered by shadcn/ui) has three benefits for Locara specifically:
- AI-friendly. LLMs read the actual component code in the repo, modify it, write back. No black-box library to fight. See v0 + community design systems.
- Customization-first. Want square corners on the chat bubble? Edit the file. No theme override systems, no className wrestling.
- Reviewable. Each app’s component code is in scope for security review. The reviewer sees exactly what’s running.
Tradeoff: maintenance shifts to the user. Bug fixes upstream require re-add + manual merge. Acceptable cost for the benefits above.
Curated components (v1)
These ship in the official Locara registry on day 1:
Chat / conversation
<Chat>— Conversation-style UI with streaming, message history, role indicators.<ChatInput>— Auto-resize textarea with submit + tool buttons.<MessageBubble>— Single message with markdown rendering, code blocks.<ToolInvocation>— Inline display of tool calls during a chat.
Document / file handling
<DocDropzone>— Drag-drop zone for files, with type filtering, multi-file support.<FilePicker>— Powerbox-mediated file picker wrapper.<DocPreview>— PDF / image / text preview pane.
Transcription / audio
<AudioInput>— Mic input control with level visualization.<TranscriptStream>— Live transcription display with segment highlighting.<AudioPlayer>— Standard playback control.
Search / data
<SearchBar>— Hybrid search input (FTS + vector).<ResultsList>— Result list with relevance scoring, snippets.<DataGrid>— Sortable, filterable table over sqlite query results.
Layout / shell
<AppShell>— Top-level layout (sidebar, header, content).<Sidebar>— Standard sidebar nav.<CommandPalette>— ⌘K-style command palette.<EmptyState>— Standard empty / loading / error states.
Settings / model management
<ModelPicker>— Compact dropdown (native select) for in-panel use.<ModelSelector>— Cursor-style rich picker with per-row download flow, capability badges, RAM-based disabling.<ModelDownloader>— Standalone model-fetch surface with hash, size, progress, throughput, ETA.<CapabilityIndicator>— Show this app’s capability profile (for transparency).
AI affordances (new in v0.0.3)
<ChatComposer>— Composite: PromptBox + ModelSelector + ModeSwitcher + AttachmentChip rail + SlashMenu trigger + voice button.<ListeningSurface>— Composite: VoiceOrb / WaveformBars + StatusPill + live partial transcript + Stop/Pause.<ListeningPill>— Granola-style floating recording indicator. Floats over any container; draggable.<VoiceOrb>— Pulsating gradient orb. State machine (idle/listening/thinking/speaking/error), amplitude-reactive.<WaveformBars>— Animated audio equaliser bars. Live (amplitude-driven) or static (decorative).<CitationChip>+<SourceCard>— Perplexity-style inline citation pill + companion source card.<ToolCallCard>— Cursor-style collapsible tool invocation with status + Approve/Reject for capability-gated tools.<Reasoning>— Collapsible chain-of-thought block. Streams “Thinking…” while in progress; settles into “Thought for Ns.”<ThinkingIndicator>— Pre-token cue. Three variants: shimmer (Anthropic), dots (iMessage), pulse (minimalist).<StreamingText>— Inline wrapper with a trailing blinking caret while streaming.<ContextMeter>— Visualises context-window usage as a bar or pill, with warn/danger thresholds.<ModeSwitcher>— Cursor Ask/Edit/Agent pill tabs (or arbitrary modes).<AttachmentChip>— File/image/audio pill rail above the composer.<StatusPill>— Linear-style colored-dot + label. Tones + optional pulse for live states.<EmptyStateCards>— Starter-prompt grid for fresh canvas.<FollowupRow>— Perplexity/Claude follow-up suggestion chips.<AcceptReject>— Notion-AI accept-edit-discard ribbon.<SlashMenu>— Caret-anchored command list with fuzzy filtering.<CommandPalette>— Linear-grade ⌘K with sticky group headers + kbd hints.<HoverActionRow>— Clean row with trailing actions revealed on hover.<AIText>— Granola’s gray-AI / strong-user color treatment; whole-block or mixed-pieces.
Misc
<MarkdownView>— Streaming markdown.<CodeBlock>— Code with copy button, language tag.<Spinner>,<Skeleton>,<Tooltip>,<Kbd>— Standard utilities.
The full catalog with live previews lives at /components. Community registries can extend.
Component schema
Each component is published with metadata at registry/components/<name>.json:
{
"id": "chat",
"version": "0.1.0",
"displayName": "Chat",
"description": "Conversation-style UI with streaming + tool calls.",
"license": "MIT",
"files": [
{ "path": "src/components/ui/chat.tsx", "url": "..." },
{ "path": "src/components/ui/message-bubble.tsx", "url": "..." }
],
"dependencies": {
"react": "^18.0.0",
"@locara/sdk": "^0.1.0"
},
"tailwind_config_additions": { ... },
"uses_components": ["message-bubble", "markdown-renderer"]
}
The CLI’s locara add chat reads the schema, downloads the source files into the project, adds Tailwind config additions, and pulls in transitively-required components.
Stack assumptions
Components are built with:
- React 18+ as the rendering primitive (open: support other frameworks?).
- TypeScript with strong types.
- Tailwind CSS for styling.
- Radix UI primitives for accessibility / behavior layers (modal, popover, dropdown, etc.).
@locara/sdkfor any data access (no direct fetch calls inside components).
These are committed for v1. The shadcn-flavored ecosystem is large enough to share patterns; reinventing in another framework adds friction.
(open) Vue / Svelte support — likely v2+. Same component schema, alternative implementations published in parallel registries.
Community / third-party registries
Anyone can publish a component registry. The CLI supports adding registries:
locara registry add design-system https://my-org.com/locara-components.json
# Then: locara add --from=design-system button
Registries are JSON manifests served from any HTTPS endpoint. The CLI fetches, verifies, copies. This is the same model as shadcn — encourages a thriving third-party ecosystem.
Locara’s official registry is one option among many; design choices avoid lock-in.
For trust:
- Locara-official registry components have a Locara signature; CLI verifies.
- Third-party registries are unsigned by default; CLI shows a warning (“unverified registry — review the source you’re about to copy”).
- A registry can opt into Locara verification (PR to register the registry’s domain + signing key); verified registries don’t trigger the warning.
Component update workflow
When new component versions are published:
locara add chat
# CLI detects existing chat.tsx, shows diff vs new version, asks to merge
# Options: overwrite / skip / open in editor for manual merge
Components track their origin:
// src/components/ui/chat.tsx
// locara: chat@0.1.0 (locara-official)
// To update: locara add chat
The header comment lets the CLI detect outdated copies and offer updates.
AI authoring affordances
Components are designed to be modified by LLMs in the user’s editor:
- Verbose prop docs. Every component has JSDoc on its props. Cursor / Claude tab-completion shows them.
- Single-purpose components. Each component does one thing. Easier for LLMs to reason about than mega-components.
- Declarative children. Composition via children, not config props. LLMs handle JSX naturally.
- No magic. No
forwardRefgymnastics, no opaque hooks, no global state. Plain React + Tailwind.
For example, when a user asks Cursor “add a voice-input button to the chat,” the LLM:
- Reads
src/components/ui/chat.tsx. - Sees the existing
<ChatInput>and where it renders. - Imports
<AudioInput>fromsrc/components/ui/audio-input.tsx. - Wires the button into the chat input toolbar.
- Saves both files.
This works because the source is readable and the components are small.
Theming
Components use Tailwind utility classes. Apps can:
- Edit components directly. Want different colors? Edit the file.
- Override Tailwind config. Standard Tailwind theming via
tailwind.config.js. - Use CSS variables for runtime theming. Locara components reference CSS variables (
--locara-primary, etc.) so users can switch dark/light/custom at runtime.
There’s no separate “theme” abstraction. Tailwind + CSS variables is the theme system.
What’s NOT in components
- No data fetching components. Components don’t make API calls. The app does, components render data.
- No state management library. No global stores, no Redux. Apps pick their own state library; components accept props and callbacks.
- No routing. Apps pick their own routing.
- No form library. Form composition is up to the app.
- No i18n. v1 is English-only. Future versions add translation hooks.
This keeps the components small, focused, and easy to compose.
Cross-references
- Distribution model background:
../notes/shadcn-ui.md - Community design system pattern:
../notes/v0-community-design-systems.md - SDK that components consume: 05-sdk.md
- CLI
locara addcommand: 06-cli.md - Tauri shell that hosts the rendered components: 07-runtime.md