@design.estate/dees-catalog
Lit and TypeScript web components for application shells, forms, tables, charts, storage, media, and agent workspaces. Shared theme tokens and controls keep these surfaces consistent.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Find the right component
Start with the task below, then open the component's API and source in the
component index. Search by both the UI pattern and the element
tag before implementing a custom control. For example, segmented control, view
switcher, view-toggle, and single-selection toggle all lead to
dees-input-multitoggle.
| Task or UI pattern | Existing component | Selection guidance |
|---|---|---|
| Switch between a few views, modes, or time periods; segmented control | dees-input-multitoggle |
One selected value in a shared track. Use keyed options when labels differ from stored values. |
| Choose one form answer from visible radio options | dees-input-radiogroup |
Radio indicators, required validation, optional payload values. |
| Turn one setting on or off | dees-input-toggle, dees-input-checkbox |
Boolean setting or checkable form choice. Multitoggle's boolean mode is useful when both alternatives need explicit labels. |
| Toggle an independent toolbar option | dees-input-toggle |
Boolean state. A set of independent options is not a single-selection multitoggle. |
| Choose from a longer menu of values | dees-input-dropdown |
Searchable single selection; enableSearch defaults to true. |
| Group related commands | dees-button-group, dees-actionbar |
Layout for actions. A button group does not own a selected value. |
| Navigate open content panels, with optional close controls | dees-appui-tabs |
Tab selection, close actions and keyboard focus. Use multitoggle for changing a display mode within a panel. |
| Build an application shell with views and menus | dees-appui |
Main/secondary navigation, content tabs, bars and view lifecycle. |
| Show, paste or share a link to the current app screen; address/location bar | dees-appui-location |
Clickable ancestors, URL editing and copy feedback. Enable it in App UI; route state belongs to the shell. |
| Build a simpler grouped dashboard | dees-simple-appdash |
Grouped dashboard navigation without configuring the full App UI shell. |
| Offer sign-in methods and reveal an authenticated app | dees-simple-login |
Passkeys, providers and password form; the application owns authentication. |
| Show products with quantities or select a kit | dees-shopping-productcard |
Independent quantity and selection events; the application owns the cart and inventory. |
| See components composed into one working application | Integrated App UI demo | Shared state, layout presets, navigation, service tabs, charts, files, settings and activity. Start here before assembling a shell. |
| Collect and submit form values | dees-form, dees-form-submit |
Give each input a stable key; use the form's collection and validation flow. |
| Search, filter, sort, edit and act on records | dees-table |
Use its columns, selection and data actions before building a table toolbar or row-action layer. |
| Show metrics, health and operational context | dees-statsgrid, dees-dataview-statusobject |
Metric tiles versus structured status details and actions. |
| Plot a time series, compare categories, show proportions, capacity or profiles | Chart components | Area, bar, donut, gauge and radar have distinct data contracts. |
| Browse object storage with folders, files and preview | dees-storage-browser |
Owns columns/list switching, provider operations and responsive preview. |
| Preview a file or display source/diffs | dees-preview, dees-dataview-codebox |
Preview chooses the viewer; codebox owns source and inline/split diffs. |
| Edit project files with terminals | dees-workspace |
Monaco, file tree, terminal and diff workspace. |
| Run an agent conversation with tools and decisions | dees-harness-chat |
Compose the harness family; use its session list for conversation management. |
| Ask for a focused decision or show contextual commands | dees-modal, dees-contextmenu |
Modal task flow versus contextual action menu. Use existing overlay ownership. |
| Show completion, progress or transient feedback | dees-stepper, dees-progressbar, dees-toast |
Workflow stages, measurable progress, or a brief notification. |
Before adding a control
- Find the task above and search the tag index below. Read the linked API and the actual implementation; similarly named controls have different contracts.
- Open its catalog demo with
pnpm run watch(the running workspace uses port 3002). Check selection, events, form participation, keyboard behavior and sizing. - Compose the existing component. If it lacks a needed reusable capability, extend its owner and verify existing consumers instead of copying its markup and styles into each view.
- When adding or extending a component, update its task entry, tag/source index, API example and demo together. Keep internal anchor and source links valid.
Documentation map
- Installation and quick start
- Component index: exact tags, API sections and source files
- Component API: properties, examples and behavior
- TypeScript interfaces
- UI Components Playbook: library implementation patterns
- Third-party notices and license
Installation
The examples below import DeesElement directly, so install both packages:
pnpm add @design.estate/dees-catalog @design.estate/dees-element@^3.1.0
Quick Start
import { html, DeesElement, customElement } from '@design.estate/dees-element';
import '@design.estate/dees-catalog';
@customElement('my-app')
class MyApp extends DeesElement {
render() {
return html`
<dees-button type="accent" @click=${() => alert('Hello!')}>
Click me!
</dees-button>
`;
}
}
Development Guide
For developers working on this library, please refer to the UI Components Playbook for comprehensive patterns, best practices, and architectural guidelines.
Component index
Search this table by the actual HTML tag. It includes registered catalog elements and supporting subcomponents; the task guide identifies the usual composition entry points. API links lead to sections below; source links always lead to the owning implementation, including components without a dedicated reference section yet. Demo files live beside their components.
Run node scripts/check-component-docs.cjs to check index coverage and README
links. After adding a component or API section, run the same command with
--write-index to regenerate the tag table from registrations. Update the
task guide and examples deliberately; the generated table does not replace them.
| Element | Family | API | Source |
|---|---|---|---|
dees-actionbar |
feedback | DeesActionbar | Source |
dees-appui |
appui | DeesAppui | Source |
dees-appui-activitylog |
appui | DeesAppuiActivitylog | Source |
dees-appui-appbar |
appui | — | Source |
dees-appui-bottombar |
appui | DeesAppuiBottombar | Source |
dees-appui-location |
appui | DeesAppuiLocation | Source |
dees-appui-maincontent |
appui | DeesAppuiMaincontent | Source |
dees-appui-mainmenu |
appui | DeesAppuiMainmenu | Source |
dees-appui-profiledropdown |
appui | — | Source |
dees-appui-secondarymenu |
appui | DeesAppuiSecondarymenu | Source |
dees-appui-tabs |
appui | DeesAppuiTabs | Source |
dees-audio-viewer |
media | DeesAudioViewer | Source |
dees-badge |
feedback | DeesBadge | Source |
dees-button |
button | DeesButton | Source |
dees-button-exit |
button | DeesButtonExit | Source |
dees-button-group |
button | DeesButtonGroup | Source |
dees-chart-area |
chart | DeesChartArea | Source |
dees-chart-bar |
chart | DeesChartBar | Source |
dees-chart-donut |
chart | DeesChartDonut | Source |
dees-chart-gauge |
chart | DeesChartGauge | Source |
dees-chart-log |
chart | DeesChartLog | Source |
dees-chart-radar |
chart | DeesChartRadar | Source |
dees-chips |
layout | DeesChips | Source |
dees-contextmenu |
overlay | DeesContextmenu | Source |
dees-dashboardgrid |
layout | — | Source |
dees-dataview-codebox |
dataview | DeesDataviewCodebox | Source |
dees-dataview-statusobject |
dataview | DeesDataviewStatusobject | Source |
dees-form |
form | DeesForm | Source |
dees-form-submit |
form | DeesFormSubmit | Source |
dees-formatting-menu |
input | — | Source |
dees-grouped-list |
dataview | — | Source |
dees-harness-chat |
harness | DeesHarnessChat | Source |
dees-harness-composer |
harness | DeesHarnessComposer | Source |
dees-harness-content-blocks |
harness | DeesHarnessContentBlocks | Source |
dees-harness-message |
harness | DeesHarnessMessage | Source |
dees-harness-message-list |
harness | DeesHarnessMessageList | Source |
dees-harness-overflow-text |
harness | DeesHarnessOverflowText | Source |
dees-harness-permission-card |
harness | DeesHarnessPermissionCard | Source |
dees-harness-question-card |
harness | DeesHarnessQuestionCard | Source |
dees-harness-reasoning |
harness | DeesHarnessReasoning | Source |
dees-harness-session-list |
harness | DeesHarnessSessionList | Source |
dees-harness-session-sidebar |
harness | DeesHarnessSessionSidebar | Source |
dees-harness-status |
harness | DeesHarnessStatus | Source |
dees-harness-todos |
harness | DeesHarnessTodos | Source |
dees-harness-tool-card |
harness | DeesHarnessToolCard | Source |
dees-harness-tool-fullscreen |
harness | — | Source |
dees-harness-usage |
harness | DeesHarnessUsage | Source |
dees-heading |
layout | DeesHeading | Source |
dees-hint |
feedback | DeesHint | Source |
dees-icon |
utility | DeesIcon | Source |
dees-image-viewer |
media | DeesImageViewer | Source |
dees-input-checkbox |
input | DeesInputCheckbox | Source |
dees-input-code |
input | DeesInputCode | Source |
dees-input-datepicker |
input | DeesInputDatepicker | Source |
dees-input-datepicker-popup |
input | — | Source |
dees-input-dropdown |
input | DeesInputDropdown | Source |
dees-input-dropdown-popup |
input | — | Source |
dees-input-fileupload |
input | DeesInputFileupload | Source |
dees-input-iban |
input | DeesInputIban | Source |
dees-input-list |
input | DeesInputList | Source |
dees-input-multitoggle |
input | DeesInputMultitoggle | Source |
dees-input-phone |
input | DeesInputPhone | Source |
dees-input-profilepicture |
input | DeesInputProfilePicture | Source |
dees-input-quantityselector |
input | DeesInputQuantitySelector | Source |
dees-input-radiogroup |
input | DeesInputRadiogroup | Source |
dees-input-richtext |
input | DeesInputRichtext | Source |
dees-input-tags |
input | DeesInputTags | Source |
dees-input-text |
input | DeesInputText | Source |
dees-input-toggle |
input | DeesInputToggle | Source |
dees-input-typelist |
input | DeesInputTypelist | Source |
dees-input-wysiwyg |
input | DeesInputWysiwyg | Source |
dees-label |
layout | DeesLabel | Source |
dees-mobilenavigation |
appui | — | Source |
dees-modal |
overlay | DeesModal | Source |
dees-mosaic |
layout | DeesMosaic | Source |
dees-pagination |
layout | DeesPagination | Source |
dees-panel |
layout | DeesPanel | Source |
dees-pdf-viewer |
media | DeesPdfViewer | Source |
dees-preview |
media | DeesPreview | Source |
dees-profilepicture-modal |
input | — | Source |
dees-progressbar |
feedback | DeesProgressbar | Source |
dees-searchbar |
utility | DeesSearchbar | Source |
dees-settings |
layout | — | Source |
dees-shopping-productcard |
simple | DeesShoppingProductcard | Source |
dees-simple-appdash |
simple | DeesSimpleAppDash | Source |
dees-simple-login |
simple | DeesSimpleLogin | Source |
dees-slash-menu |
input | — | Source |
dees-sparklebox |
layout | — | Source |
dees-speechbubble |
overlay | DeesSpeechbubble | Source |
dees-spinner |
feedback | DeesSpinner | Source |
dees-statsgrid |
dataview | DeesStatsGrid | Source |
dees-stepper |
layout | DeesStepper | Source |
dees-storage-browser |
dataview | DeesStorageBrowser | Source |
dees-storage-columns |
dataview | — | Source |
dees-storage-keys |
dataview | — | Source |
dees-storage-preview |
dataview | — | Source |
dees-table |
dataview | DeesTable | Source |
dees-terminal-view |
harness | — | Source |
dees-theme |
utility | DeesTheme | Source |
dees-thumbnail-audio |
media | DeesThumbnailAudio | Source |
dees-thumbnail-folder |
media | DeesThumbnailFolder | Source |
dees-thumbnail-image |
media | DeesThumbnailImage | Source |
dees-thumbnail-note |
media | DeesThumbnailNote | Source |
dees-thumbnail-pdf |
media | DeesThumbnailPdf | Source |
dees-thumbnail-video |
media | DeesThumbnailVideo | Source |
dees-tile |
layout | — | Source |
dees-toast |
feedback | DeesToast | Source |
dees-updater |
utility | DeesUpdater | Source |
dees-video-viewer |
media | DeesVideoViewer | Source |
dees-windowcontrols |
utility | DeesWindowControls | Source |
dees-windowlayer |
overlay | DeesWindowLayer | Source |
dees-workspace |
workspace | DeesWorkspace | Source |
dees-workspace-bottombar |
workspace | DeesWorkspaceBottombar | Source |
dees-workspace-diff-editor |
workspace | DeesWorkspaceDiffEditor | Source |
dees-workspace-filetree |
workspace | DeesWorkspaceFiletree | Source |
dees-workspace-markdown |
workspace | DeesWorkspaceMarkdown | Source |
dees-workspace-markdownoutlet |
workspace | DeesWorkspaceMarkdownoutlet | Source |
dees-workspace-monaco |
workspace | DeesWorkspaceMonaco | Source |
dees-workspace-terminal |
workspace | DeesWorkspaceTerminal | Source |
dees-workspace-terminal-preview |
workspace | DeesWorkspaceTerminalPreview | Source |
dees-wysiwyg-block |
input | — | Source |
Component API
Core UI Components
DeesButton
A versatile button component supporting multiple styles and states.
// Basic usage
const button = document.createElement('dees-button');
button.text = 'Click me';
// With options
<dees-button
type="accent" // Options: default, accent, destructive, outline, secondary, ghost, link (legacy: normal, highlighted, discreet, big)
size="sm" // Options: default, sm, lg, icon (square)
shape="pill" // Options: squircle (default rounded rect), pill (capsule for standalone actions)
status="pending" // Options: normal, pending, success, error
disabled={false} // Optional: disables the button
full-width // Optional: stretches the button face to the host width
@click=${handleClick}
>Click me</dees-button>
The button is keyboard operable: its face carries role="button" and tabindex, Enter and
Space activate it (dispatching a real click, so both @click and @clicked fire), a
disabled button leaves the tab order, and keyboard focus draws a :focus-visible ring.
Direct light-DOM text and an optional direct dees-icon remain consumer-owned and update
reactively. Adopted values remain observable through text, icon, and iconPosition; assigning
different public values independently takes control until the corresponding adopted value is restored.
DeesBadge
Display status indicators or counts with customizable styles.
<dees-badge
type="success" // Options: default, primary, success, warning, error
text="New" // Text to display
rounded // Optional: applies rounded corners
></dees-badge>
DeesChips
Interactive chips/tags with selection capabilities.
<dees-chips
selectionMode="multiple" // Options: none, single, multiple
chipsAreRemovable // Optional: allows removing chips
.selectableChips=${[
{ key: 'tag1', value: 'Important' },
{ key: 'tag2', value: 'Urgent' }
]}
@selection-change=${handleSelection}
></dees-chips>
DeesIcon
Display Lucide icons. Legacy fa: and iconFA inputs are unsupported and log an error.
Applications that only need the icon component can use the granular entry point without registering or bundling the complete catalog:
import '@design.estate/dees-catalog/icon';
// Lucide icons — use 'lucide:' prefix
<dees-icon
icon="lucide:check" // Lucide icon with lucide: prefix
iconSize="24" // Size in pixels
color="#22c55e" // Optional: custom color
></dees-icon>
// Unsupported legacy inputs log an error and render no icon:
// <dees-icon icon="fa:check"></dees-icon>
// <dees-icon iconFA="check"></dees-icon>
dees-icon follows normal CSS text-color inheritance, including translucent
rgba(...) colors, and composites the complete Lucide shape as one layer.
Use native CSS color and opacity; consumers do not need icon-specific
opacity variables or stroke-color workarounds.
DeesLabel
Text label component with optional required indicator and info tooltip. Used internally by all input components.
<dees-label
.label=${'Email Address'} // Label text
.required=${true} // Optional: shows red asterisk
.infoText=${'We will never share your email'} // Optional: shows hover info icon with tooltip
></dees-label>
DeesSpinner
Loading indicator with customizable appearance.
<dees-spinner
.size=${20} // Optional: diameter in pixels (default 20)
.status=${'normal'} // Optional: 'normal' | 'pending' | 'success' | 'error'
.bnw=${true} // Optional: black-and-white treatment
></dees-spinner>
The arc colour follows the --dees-spinner-color custom property, falling back to
--dees-color-text-primary. Set it on an ancestor when the spinner sits on a coloured
surface — an accent button face, for example — so the arc matches that surface's foreground:
dees-button {
--dees-spinner-color: var(--dees-color-on-accent);
}
DeesToast
Notification toast messages with various styles, positions, and auto-dismiss functionality.
// Programmatic usage
DeesToast.show({
message: 'Operation successful',
type: 'success', // Options: info, success, warning, error
duration: 3000, // Time in milliseconds before auto-dismiss
position: 'top-right' // Options: top-right, top-left, bottom-right, bottom-left, top-center, bottom-center
});
// Convenience methods
DeesToast.info('Information message');
DeesToast.success('Success message');
DeesToast.warning('Warning message');
DeesToast.error('Error message');
// Advanced control
const toast = await DeesToast.show({
message: 'Processing...',
type: 'info',
duration: 0 // No auto-dismiss
});
// Later dismiss programmatically
toast.dismiss();
Key Features:
- Multiple toast types with distinct icons and colors
- 6 position options for flexible placement
- Auto-dismiss with visual progress indicator
- Manual dismiss by clicking
- Smooth animations and transitions
- Automatic stacking of multiple toasts
- Theme-aware styling
- Programmatic control
DeesButtonExit
Exit/close button component with consistent styling.
<dees-button-exit
@click=${handleClose}
></dees-button-exit>
DeesButtonGroup
Groups slotted action buttons. It does not manage selection; for a segmented
choice use dees-input-multitoggle.
Container for grouping related buttons together.
<dees-button-group label="Actions" direction="horizontal">
<dees-button type="accent" @clicked=${handleSave}>Save</dees-button>
<dees-button @clicked=${handleCancel}>Cancel</dees-button>
</dees-button-group>
DeesHeading
Consistent heading component with level and styling options.
<dees-heading
level={1} // 1-6 for H1-H6
text="Page Title"
.subheading=${'Optional subtitle'}
centered // Optional: center alignment
></dees-heading>
DeesHint
Hint/tooltip component for providing contextual help.
<dees-hint
text="This field is required"
type="info" // Options: info, warning, error, success
position="top" // Options: top, bottom, left, right
></dees-hint>
DeesPanel
Container component for grouping related content with optional title and actions.
<dees-panel
.title=${'Panel Title'}
.subtitle=${'Optional subtitle'}
collapsible // Optional: allow collapse/expand
collapsed={false} // Initial collapsed state
.actions=${[
{ icon: 'settings', action: handleSettings }
]}
>
<!-- Panel content -->
</dees-panel>
DeesSearchbar
Search input component with suggestions and search handling.
<dees-searchbar
placeholder="Search..."
.suggestions=${['item1', 'item2', 'item3']}
showClearButton // Show clear button when has value
@search=${handleSearch}
@suggestion-select=${handleSuggestionSelect}
></dees-searchbar>
DeesWindowcontrols
Window control buttons (minimize, maximize, close) for desktop-like applications.
<dees-windowcontrols
.controls=${['minimize', 'maximize', 'close']}
@minimize=${handleMinimize}
@maximize=${handleMaximize}
@close=${handleClose}
></dees-windowcontrols>
DeesActionbar
Floating action bar for contextual actions.
<dees-actionbar
.actions=${[
{ icon: 'lucide:save', label: 'Save', action: () => handleSave() },
{ icon: 'lucide:trash', label: 'Delete', action: () => handleDelete() }
]}
></dees-actionbar>
Form Components
DeesForm
Collect keyed inputs with await form.collectFormData() or receive event.detail.data
from formData when a dees-form-submit is activated. changeSubject publishes
updated form data as fields change. Required values control the submit button;
component-specific validation is owned by the field. There is no formValidation event.
html`<dees-form @formData=${(event) => savePreferences(event.detail.data)}>
<dees-input-text key="name" label="Workspace name" value="Design studio" required></dees-input-text>
<dees-input-toggle key="notifications" label="Notifications" .value=${true}></dees-input-toggle>
<dees-form-submit type="accent" text="Save preferences"></dees-form-submit>
</dees-form>`
Use grouped for compact settings rows and horizontal-layout for wrapping filters.
Keep grouped controls as direct children. Compound fields (tags, lists, uploads,
avatars and editors) retain a full-width editing surface and a label above it.
Input family and composition
- Simple fields: text/password, searchable dropdown, date/time, phone and IBAN.
- Choices: checkbox and toggle for booleans; radiogroup or multitoggle for one choice; quantityselector for a non-negative integer.
- Collections: typelist for strings, tags for suggested tokens, list for inline
editing and reordering. Fileupload returns
File[]; profilepicture returns an image string. - Documents: richtext for HTML, wysiwyg for structured blocks, code for source text.
DeesInputBase owns field geometry, label/helper spacing, focus and disabled
styling. Controls use 32px desktop height and at least 44px on coarse pointers;
grouped values use 28px on desktop. Override --dees-input-control-height only for
an intentional density context. A single outer focus ring marks a field; compound
actions retain their own keyboard indicator. Larger editors do not fit the
compact label/value grid. Subclasses declare inputLayout as field, choice,
or compound; this is reflected as input-layout for the form's layout rules.
See Pages → inputShowcase for all 18 implemented inputs in one preferences form, plus editor/settings and validation states. Focused element demos use the same fixtures. Before adding a control, check this list, the task chooser, and the exact source. The empty search-select source does not register a component; use the searchable dropdown.
DeesInputText
Text input field with validation, info tooltips, description text, and context menu (Cut/Copy/Paste/Select All).
<dees-input-text
key="email" // Unique identifier for form data
label="Email" // Input label
value="initial@value.com" // Initial value
required // Makes the field required
disabled // Disables the input
placeholder="name@example.com"
inputmode="email"
.autocomplete=${'username'} // Autofill hint; unset renders no autocomplete attribute
.infoText=${'Hover icon tooltip text'} // Shows ⓘ icon on label with hover tooltip
.description=${'Permanent help text below the input'} // Small text below the input
.validationFunction=${(value) => { // Auto-validates on every keystroke
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (emailRegex.test(value)) {
return { valid: true, message: 'Email is valid' };
}
return { valid: false, message: 'Please enter a valid email' };
}}
></dees-input-text>
💡 All input components share these common properties from
DeesInputBase:key,label,required,disabled,infoText,description,layoutMode,labelPosition.
DeesInputCheckbox
Checkbox input component for boolean values.
<dees-input-checkbox
key="terms"
label="Accept Terms"
.value=${true}
required
@newValue=${handleChange}
></dees-input-checkbox>
An unlabeled checkbox accepts aria-label (or .ariaLabel) for its inner checkbox control. indeterminate exposes the mixed state, and disabled prevents interaction. controlTabIndex (attribute control-tabindex) defaults to 0; use -1 for programmatic-only focus. The control CSS part exposes the full clickable area for sizing in dense layouts. Empty label/description space is omitted.
DeesInputToggle
Toggle switch component for boolean on/off states.
<dees-input-toggle
key="darkMode"
label="Enable Dark Mode"
.value=${true}
@newValue=${handleToggle}
></dees-input-toggle>
DeesInputDropdown
Dropdown selection component with search and filtering capabilities.
<dees-input-dropdown
key="country"
label="Select Country"
.options=${[
{ key: 'us', option: 'United States' },
{ key: 'uk', option: 'United Kingdom' }
]}
.enableSearch=${true} // Search is enabled by default
@selectedOption=${(event) => handleCountry(event.detail)}
></dees-input-dropdown>
DeesInputFileupload
File upload component with drag-and-drop support.
<dees-input-fileupload
key="documents"
label="Upload Files"
multiple // Allow multiple file selection
accept=".pdf,.doc" // Accepted file types
.maxSize=${5 * 1024 * 1024} // Bytes per file; selection is local
></dees-input-fileupload>
DeesInputIban
Specialized input for IBAN (International Bank Account Number) with validation.
<dees-input-iban
key="bankAccount"
label="IBAN"
required
></dees-input-iban>
DeesInputPhone
Digit-based phone input with US-style grouping. Ten digits display as (415) 555-0123; longer values retain a leading country-code portion. The stored value contains digits only. It does not offer a country selector or country-specific validation.
<dees-input-phone
key="phone"
label="Phone Number"
required
></dees-input-phone>
DeesInputQuantitySelector
Non-negative integer input with native increment/decrement buttons. Decrement stops at zero; there are no min, max, or step properties.
<dees-input-quantityselector
key="quantity"
label="Quantity"
value="1" // Initial value
></dees-input-quantityselector>
User increments/decrements emit newValue with the numeric event.detail, bubbling
across shadow boundaries. The existing changeSubject subscription remains available
for form collection. Assigning .value or calling setValue() does not emit a user event.
DeesInputMultitoggle
The catalog's segmented control for view switchers, modes, time periods and
short setting choices. Despite the name and the default type="multi", it selects
one value, not several. Both multi and single select a string key; boolean
returns a boolean. Use radio groups for radio-style form answers and button groups
for action layout.
html`<dees-input-multitoggle
label="File view"
.labelPosition=${'none'}
.options=${[
{ key: 'columns', label: 'Columns' },
{ key: 'list', label: 'List' },
{ key: 'gallery', label: 'Gallery', disabled: true },
]}
.selectedOption=${this.view}
@change=${(event: CustomEvent<{ value: string }>) => {
this.view = event.detail.value;
}}
></dees-input-multitoggle>`;
| Property/API | Behavior |
|---|---|
options: TMultitoggleOption[] |
Strings or { key, label, disabled? } objects. |
selectedOption |
Selected string/key; bind with .selectedOption in Lit. |
value, getValue(), setValue(value) |
String key, or boolean in boolean mode. value is a JavaScript property, not a value HTML attribute. |
type="boolean" |
Two alternatives named by booleanTrueName / booleanFalseName; .boolValue supplies a boolean selection. |
size |
default or sm for compact toolbars. Touch targets remain at least 44px tall. |
label, labelPosition |
Visible label by default; none hides it while retaining the group name for assistive technology. |
disabled |
Disables the whole control; an option object can disable one choice. |
key |
Name collected by dees-form; required for form participation. |
input, change |
One composed, bubbling CustomEvent each per user selection, with { value, selectedOption }. Programmatic updates do not emit them. |
option-activate |
Composed event with the same detail on every enabled option activation, including reselecting the active option. Used by codebox to pin an automatic layout explicitly. |
changeSubject |
Existing reactive notification with the component instance per user selection. |
required validation is not implemented by multitoggle. String mode chooses the
first enabled option when initially empty. Avoid required on boolean controls:
false is a valid selection, while the form's generic truthiness check treats it as
missing. Use a required radio group when an explicit answer is necessary.
Arrow keys select and focus the next enabled option; Home/End select the first/last. Enter and Space activate the focused option. The selected segment has a stable size, follows wrapping/resizing, and respects reduced motion. Containers that translate the inner input event into their own public event should stop the inner event's propagation.
DeesInputRadiogroup
Radio-style single-choice form input, including required validation and optional payload values. For a segmented view switcher, use dees-input-multitoggle. Object options use { key, option, payload? }; the visible text field is option, unlike multitoggle's label.
<dees-input-radiogroup
key="plan"
label="Select Plan"
.options=${['Free', 'Pro', 'Enterprise']}
selectedOption="Pro"
required
@change=${handlePlanChange}
></dees-input-radiogroup>
// With custom option objects
<dees-input-radiogroup
key="priority"
label="Priority Level"
.options=${[
{ key: 'low', option: 'Low Priority' },
{ key: 'medium', option: 'Medium Priority' },
{ key: 'high', option: 'High Priority' }
]}
selectedOption="medium"
></dees-input-radiogroup>
DeesInputTags
Tag input component for managing lists of tags with auto-complete and validation.
<dees-input-tags
key="skills"
label="Skills"
.value=${['JavaScript', 'TypeScript', 'CSS']}
placeholder="Add a skill..."
.suggestions=${[
'JavaScript', 'TypeScript', 'Python', 'Go', 'Rust',
'React', 'Vue', 'Angular', 'Node.js', 'Docker'
]}
.maxTags=${10} // Optional: limit number of tags
required
@change=${handleTagsChange}
></dees-input-tags>
Key Features:
- Add tags by pressing Enter or typing comma/semicolon
- Remove tags with click or backspace
- Auto-complete suggestions with keyboard navigation
- Maximum tag limit support
- Full theme support
- Form validation integration
DeesInputTypelist
Dynamic list input for managing arrays of typed values.
<dees-input-typelist
key="features"
label="Product Features"
.value=${['Feature 1', 'Feature 2']}
></dees-input-typelist>
DeesInputList
Advanced list input with drag-and-drop reordering, inline editing, and validation.
<dees-input-list
key="items"
label="List Items"
placeholder="Add new item..."
.value=${['Item 1', 'Item 2', 'Item 3']}
.maxItems=${10} // Optional: maximum items
.minItems=${1} // Optional: minimum items
.allowDuplicates=${false} // Optional: allow duplicate values
.allowFreeform=${true} // Accept items outside the candidates list
.sortable=${true} // Optional: enable drag-and-drop reordering
.confirmDelete=${true} // Optional: confirm before deletion
@change=${handleListChange}
></dees-input-list>
Key Features:
- Add, edit, and remove items inline
- Drag-and-drop reordering with visual feedback
- Optional duplicate prevention
- Min/max item constraints
- Delete confirmation dialog
- Full keyboard support
- Form validation integration
DeesInputProfilepicture
Profile picture input with cropping, zoom, and image processing.
<dees-input-profilepicture
key="avatar"
label="Profile Picture"
shape="round" // Options: round, square
.size=${120} // Display size in pixels
.value=${imageBase64} // Base64 encoded image or URL
.allowUpload=${true} // Enable upload button
.allowDelete=${true} // Enable delete button
.maxFileSize=${5242880} // Max file size in bytes (5MB)
.acceptedFormats=${['image/jpeg', 'image/png', 'image/webp']}
.outputSize=${800} // Output resolution in pixels
.outputQuality=${0.95} // JPEG quality (0-1)
></dees-input-profilepicture>
Key Features:
- Interactive cropping modal with zoom and pan
- Drag-and-drop file upload
- Round or square output shapes
- Configurable output size and quality
- File size and format validation
- Delete functionality
- Always-visible change/remove actions and keyboard-accessible image selection
DeesInputDatepicker
Date and time picker component with calendar interface and manual typing support.
Use changeSubject for field updates, or collect .value through dees-form.
<dees-input-datepicker
key="eventDate"
label="Event Date"
placeholder="YYYY-MM-DD"
value="2025-01-15T14:30:00Z" // ISO string format
dateFormat="YYYY-MM-DD" // Display format (default: YYYY-MM-DD)
.enableTime=${true} // Enable time selection
timeFormat="24h" // Options: 24h, 12h
.minuteIncrement=${15} // Time step in minutes
minDate="2025-01-01" // Minimum selectable date
maxDate="2025-12-31" // Maximum selectable date
.disabledDates=${[ // Array of disabled dates
'2025-01-10',
'2025-01-11'
]}
.weekStartsOn=${1} // 0 = Sunday, 1 = Monday
required
></dees-input-datepicker>
Key Features:
- Interactive calendar popup
- Manual date typing with multiple formats
- Optional time selection
- Configurable date format
- Min/max date constraints
- Disable specific dates
- Keyboard navigation
- Today button
- Clear functionality
- 12/24 hour time formats
- Theme-aware styling
- Live parsing and validation
Manual Input Formats:
// Date formats supported
"2023-12-20" // ISO format (YYYY-MM-DD)
"20.12.2023" // European format (DD.MM.YYYY)
"12/20/2023" // US format (MM/DD/YYYY)
// Date with time (add space and time after any date format)
"2023-12-20 14:30"
"20.12.2023 9:45"
"12/20/2023 16:00"
DeesInputRichtext
Rich text editor with formatting toolbar powered by TipTap.
<dees-input-richtext
key="content"
label="Article Content"
.value=${htmlContent}
placeholder="Start writing..."
.minHeight=${300} // Minimum editor height
.showWordCount=${true} // Show word count
@change=${handleContentChange}
></dees-input-richtext>
Key Features:
- Full formatting toolbar (bold, italic, underline, strike, etc.)
- Heading levels H1–H3
- Lists (bullet, ordered)
- Links with URL editing
- Code blocks and inline code
- Blockquotes
- Horizontal rules
- Undo/redo support
- Word count
- HTML output
DeesInputWysiwyg
Advanced block-based editor with slash commands and rich content blocks.
Use changeSubject for field updates, or collect .value through dees-form.
<dees-input-wysiwyg
key="document"
label="Document Editor"
.value=${documentContent}
outputFormat="html" // Options: html, markdown, json
></dees-input-wysiwyg>
Key Features:
- Slash commands for quick formatting
- Block-based editing (paragraphs, headings, lists, etc.)
- Drag and drop block reordering
- Multiple output formats
- Keyboard shortcuts
- Extensible block types
DeesInputCode
Code input component for editing source code with syntax highlighting.
<dees-input-code
key="snippet"
label="Code Snippet"
.value=${codeString}
language="typescript"
></dees-input-code>
DeesFormSubmit
Submit button component specifically designed for DeesForm.
<dees-form-submit
disabled // Optional: disable submit button
status="normal" // Options: normal, pending, success, error
full-width // Optional: stretches the button face to the host width
>Submit Form</dees-form-submit>
DeesFormSubmit.focus(options) forwards keyboard focus to the inner button without
submitting. Call await submit.submit() to gather and dispatch form data explicitly.
Enter in the final single-line form input calls submit(); composed text input,
multiline editors, and keys handled by a picker keep their own behavior. Disabled or
non-normal submit controls do not submit.
Migration: code that previously used submit.focus() to submit must call
submit.submit(). This is an intentional breaking correction to the focus contract.
App Shell (Layout) Components
DeesMosaic
Sway-like tiling workspace: a controlled split-tree layout where tiles resize against each other, drag by a slim tilebar (which also closes them), and dock onto any edge of another tile with a live drop preview. Content projects through named slots, so hosts keep light-DOM ownership — Electron <webview> content survives every layout change because slot reassignment never moves the light-DOM node.
import { createMosaicLeaf, mosaicInsert, type IMosaicLayout } from '@design.estate/dees-catalog';
let layout: IMosaicLayout = mosaicInsert({ root: null }, null, 'right', createMosaicLeaf('editor', 'l-editor'));
layout = mosaicInsert(layout, 'l-editor', 'right', createMosaicLeaf('chat', 'l-chat'));
layout = mosaicInsert(layout, null, 'bottom', createMosaicLeaf('terminal', 'l-term'));
<dees-mosaic
.layout=${layout}
.surfaces=${{ editor: { title: 'Editor', icon: 'lucide:Code' }, chat: { title: 'AI Chat' }, terminal: { title: 'Terminal' } }}
@mosaic-layout-change=${(e) => { layout = e.detail.layout; /* persist */ }}
>
<my-editor slot="tile-l-editor"></my-editor>
<my-chat slot="tile-l-chat"></my-chat>
<my-terminal slot="tile-l-term"></my-terminal>
<div slot="empty">Workspace is empty.</div>
</dees-mosaic>
The component is controlled: apply e.detail.layout back (and persist it) on every mosaic-layout-change (reason: 'resize' | 'drop' | 'close' | 'equalize'). Other events: cancelable mosaic-tile-close, mosaic-tile-focus, throttled mosaic-resizing (re-measure embedded content), mosaic-drag-state. Methods: getLeafRects() (viewport rect per leaf content area), equalize(splitId?), focusTile(id). Pure helpers (mosaicInsert/mosaicRemove/mosaicMove/mosaicResize/mosaicEqualize/mosaicNormalize/mosaicLeaves) are exported for host-side layout logic. Splitters are keyboard-accessible (role="separator", arrow keys resize, double-click equalizes); Escape cancels drags.
DeesAppui
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, bottom bar, and view management.
Integrated App UI demo
Open App UI → DeesAppui in the catalog. The Gateway workspace uses the whole App UI family in one example. Browse, Inspect and Focus change the shell layout while preserving open service tabs, selected charts and unsaved drafts. Below a 700px shell width, navigation opens on demand and activity fills the content area.
| Component | Role in the workspace | Try it |
|---|---|---|
dees-appui |
Owns layout, routing and view lifecycle | Switch layout presets, navigate, then return to a cached view. |
dees-appui-appbar |
Gateway, File, View and Window menus, location, search and account | Search for edge-eu; use a breadcrumb to return to Overview. |
dees-appui-location |
Exact screen links, editable address and copy feedback | Paste #settings/notifications or copy a service link with region filters; refresh to restore it. |
dees-appui-profiledropdown |
Account actions | Toggle availability or open Profile settings; Escape returns focus. |
dees-appui-mainmenu |
Stable work areas and pinned Settings | Observe the Services attention badge, including collapsed mode. |
dees-appui-secondarymenu |
Contextual regions, attention filter and settings sections | Filter Services to Europe and Needs attention. |
dees-appui-maincontent |
Cached views with guarded navigation | Edit a file or settings draft, switch layouts, then save or discard. |
dees-appui-tabs |
Closable service tabs and analytics views | Open two services; close one or use Close other service tabs. |
dees-appui-activitylog |
Events from the shared workspace | Inspect a service, save a file or preferences, then search Activity. |
dees-appui-bottombar |
Health, sample number and refresh | Refresh to advance the shared sample; right-click health for actions. |
The incident path is Overview → Simulate latency → Inspect edge-eu → Restore service. The service table, chart measurements, metrics, badges and activity read the same state. Analytics composes area, bar, donut, gauge and radar charts; Files uses the storage browser and preview/editor; Settings uses the form and input components. File changes are disposable drafts and never deploy anything.
Implementation map: shell configuration and walkthrough, shared state and navigation, composed views and search. Presets own collapse/activity preferences; views own contextual menus and tabs. Use this composition before building custom chrome or embedding standalone component demo launchers inside an application.
The shared shell honors activityLog.visible in configure(), preserves widgets
while the bottom bar is hidden, and keeps hidden panels inert. Activity emits
close-request; the shell handles it and restores focus to its toggle.
Area charts preserve their visible time range when panel widths change, including
explicit ranges supplied through the chart API.
App UI chrome shares an opaque tone and a 40px desktop / 48px touch row across
the app bar, pane headings, tabs and compact navigation. Sidebar selection follows
the item's key, including immutable badge updates. Tabs use an inset selection
surface attached to the tab, keeping it aligned through scrolling and resizing.
Full API Documentation: See ts_web/elements/00group-appui/dees-appui/readme.md for complete documentation including all programmatic APIs, view lifecycle hooks, and TypeScript interfaces.
Quick Start:
import { html, DeesElement, customElement } from '@design.estate/dees-element';
import { DeesAppui } from '@design.estate/dees-catalog';
@customElement('my-app')
class MyApp extends DeesElement {
private appui: DeesAppui;
async firstUpdated() {
this.appui = this.shadowRoot.querySelector('dees-appui');
this.appui.configure({
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
views: [
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
],
mainMenu: {
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
},
defaultView: 'dashboard',
bottomBar: {
visible: true,
widgets: [
{ id: 'status', iconName: 'lucide:activity', label: 'Online', status: 'success' }
]
}
});
}
render() {
return html`<dees-appui></dees-appui>`;
}
}
Architecture Overview:
┌─────────────────────────────────────────────────────────────────────┐
│ AppBar (dees-appui-appbar) │
│ ├── Menus (File, Edit, View...) │
│ ├── Breadcrumbs │
│ ├── User Profile + Dropdown │
│ └── Activity Log Toggle │
├─────────────┬───────────────────────────────────┬───────────────────┤
│ Main Menu │ Content Area │ Activity Log │
│ (collapsed/ │ ├── Content Tabs │ (slide panel) │
│ expanded) │ │ (closable, from tables/lists)│ │
│ │ └── View Container │ │
│ ┌─────────┐ │ └── Active View │ │
│ │ 🏠 Home │ ├─────────────────────────────────┐ │ │
│ │ 📁 Files│ │ Secondary Menu │ │ │
│ │ ⚙ Set.. │ │ ├── Collapsible Groups │ │ │
│ │ │ │ │ ├── Tabs / Actions │ │ │
│ └─────────┘ │ │ ├── Filters / Links │ │ │
│ │ │ └── Dividers / Headers │ │ │
├─────────────┴──┴───────────────────────────────┴───────────────────┤
│ Bottom Bar (dees-appui-bottombar) — 24px status bar │
│ ├── Status widgets (left/right) │
│ └── Action buttons (left/right) │
└─────────────────────────────────────────────────────────────────────┘
Configuration (IAppConfig):
interface IAppConfig {
branding?: { logoIcon?: string; logoText?: string };
appBar?: IAppBarConfig;
views: IViewDefinition[];
mainMenu?: IMainMenuConfig;
defaultView?: string;
activityLog?: IActivityLogConfig;
bottomBar?: IBottomBarConfig;
onViewChange?: (viewId: string, view: IViewDefinition) => void;
onSearch?: (query: string) => void;
}
Key Features:
- 🔧 Configure API — Single
configure()method for complete app setup - 📄 View Management — Automatic view caching, lazy loading, and lifecycle hooks (
onActivate,onDeactivate,canDeactivate) - 🧭 Hash-based Routing — Automatic URL synchronization with view navigation and parameterized routes
- 📊 Activity Log — Slide-out panel with stacked entries, date grouping, search, and filtering
- 📌 Bottom Status Bar — Configurable widgets and actions with status colors and loading states
- 🎯 RxJS Observables —
viewChanged$andviewLifecycle$for reactive programming - 🏷️ TypeScript-first — Typed
IViewActivationContextpassed to views on activation
Programmatic APIs:
| Area | Methods |
|---|---|
| Navigation | navigateToView(viewId, params?), getCurrentView(), getViewRegistry() |
| App Bar | setAppBarMenus(), updateAppBarMenu(), setBreadcrumbs(), setUser(), setProfileMenuItems(), setSearchVisible(), onSearch(), setWindowControlsVisible() |
| Main Menu | setMainMenu(), updateMainMenuGroup(), addMainMenuItem(), removeMainMenuItem(), setMainMenuSelection(), setMainMenuCollapsed(), setMainMenuVisible(), setMainMenuBadge(), clearMainMenuBadge() |
| Secondary Menu | setSecondaryMenu(), updateSecondaryMenuGroup(), addSecondaryMenuItem(), setSecondaryMenuSelection(), setSecondaryMenuCollapsed(), setSecondaryMenuVisible(), clearSecondaryMenu() |
| Content Tabs | setContentTabs(), addContentTab(), removeContentTab(), selectContentTab(), getSelectedContentTab(), setContentTabsVisible(), setContentTabsAutoHide() |
| Activity Log | activityLog.add(), activityLog.addMany(), activityLog.clear(), activityLog.getEntries(), activityLog.filter(), activityLog.search(), setActivityLogVisible(), toggleActivityLog(), getActivityLogVisible() |
| Bottom Bar | bottomBar.addWidget(), bottomBar.updateWidget(), bottomBar.removeWidget(), bottomBar.getWidget(), bottomBar.clearWidgets(), bottomBar.addAction(), bottomBar.removeAction(), bottomBar.clearActions(), setBottomBarVisible(), getBottomBarVisible() |
| Observables | viewChanged$, viewLifecycle$ |
View Lifecycle Hooks:
import { DeesElement, customElement } from '@design.estate/dees-element';
import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog';
@customElement('my-settings-view')
class MySettingsView extends DeesElement implements IViewLifecycle {
// Called when view becomes visible
async onActivate(context: IViewActivationContext) {
const { appui, viewId, params } = context;
// Set view-specific secondary menu
appui.setSecondaryMenu({
heading: 'Settings',
groups: [{ name: 'Options', items: [...] }]
});
// Control visibility of other shell parts
appui.setContentTabsVisible(false);
appui.setSecondaryMenuVisible(true);
}
// Called when navigating away
onDeactivate() { /* cleanup */ }
// Return false or a message string to block navigation
canDeactivate(): boolean | string {
if (this.hasUnsavedChanges) return 'You have unsaved changes. Leave anyway?';
return true;
}
}
Secondary Menu Item Types:
The secondary menu supports 8 distinct item types for building rich contextual sidebars:
| Type | Description |
|---|---|
| Tab (default) | Selectable item that stays highlighted |
| Action | Executes on click without staying selected (blue styling) |
| Filter | Checkbox toggle for filtering |
| MultiFilter | Collapsible multi-select filter box |
| Divider | Visual separator line |
| Header | Non-interactive section label |
| Link | Opens an external URL |
| Danger Action | Red-styled action with optional confirmation |
DeesAppuiLocation
An editable location bar for app screens. Search terms: address bar, URL input, deep link, share screen, and editable breadcrumbs. Its catalog demo uses the same Gateway workspace as App UI.
Enable the optional control through appui.configure({ appBar: { location: true }, views, ... }).
Ordinary string breadcrumbs remain available when it is disabled. The shell accepts
same-origin URLs at its hosting path and bare hashes; it rejects foreign, unknown or
invalid routes before leaving the current view. The location field uses the same
canDeactivate(destination) guard as sidebar and browser navigation.
appui.configure({
appBar: { location: true, showWindowControls: false },
views: [{ id: 'settings', name: 'Settings', route: 'settings/:section?', content: 'workspace-settings' }],
defaultView: 'settings',
});
// Derive these ancestors from the view activation context.
appui.setLocationBreadcrumbs([
{ label: 'Settings', href: appui.createViewUrl('settings') },
{ label: 'Notifications' },
]);
await appui.navigateToUrl('#settings/notifications');
In view activation, derive ancestors from context.params and context.query, using
setLocationBreadcrumbs([{ label, href? }]). Omit href on the current item.
createViewUrl(viewId, params?, query?) builds an absolute link; optional path parameters
are omitted, parameter values are encoded, and query keys are sorted inside the hash.
getCurrentRoute() returns an isolated { viewId, params, query } snapshot. Views can
provide validateRoute(route) to reject unsupported sections, resources or filters.
appBar.locationBaseUrl optionally specifies the canonical hosting URL without a hash.
It must share the current origin and path. The Gateway demo uses this to leave catalog
sidebar settings out of copied links. Its links include settings section, service,
region/attention filters, analytics tab and file folder/preview; drafts, layout presets,
activity visibility and sample counters stay local.
The field is centered within the space between the menus and account controls, with a maximum width of 560px. It shows readable ancestors at rest. Click the empty space or focus the edit control and press Enter/F2 to select the URL; Enter navigates and Escape cancels. Unchanged edits close on blur. Invalid or blocked navigation stays visible with an error. At narrow widths ancestors move into a parent-screens menu; on a very narrow bar only edit/copy controls remain, and editing expands across the app bar. Browser Cmd/Ctrl+L is untouched. Copy displays “Copied” only after success. Where clipboard access is unavailable (including insecure HTTP hosts), the field selects the URL and explains how to copy it manually.
For another router, use the controlled component directly:
html`<dees-appui-location .location=${{
href: currentCanonicalUrl,
items: [{ label: 'Workspace', href: homeUrl }, { label: screenName }],
navigate: async (href: string) => appui.navigateToUrl(href),
}}></dees-appui-location>`;
The .location value is IAppLocation (href, items, async navigate). The callback
returns false for a blocked transition or throws a user-facing error. The component
owns no global route listeners; its host owns the canonical URL and navigation policy.
edit() opens the editor programmatically.
DeesAppuiMainmenu
Main navigation menu component for application-wide navigation. Supports collapsed (icon-only) mode.
<dees-appui-mainmenu
.menuGroups=${[
{
name: 'Main',
items: [
{ key: 'dashboard', iconName: 'lucide:home', action: () => navigate('dashboard') },
{ key: 'settings', iconName: 'lucide:settings', action: () => navigate('settings') }
]
}
]}
collapsed // Optional: show collapsed icon-only version
></dees-appui-mainmenu>
DeesAppuiSecondarymenu
Secondary navigation component for sub-section selection with collapsible groups, badges, and 8 item types.
<dees-appui-secondarymenu
.heading=${'Projects'}
.groups=${[
{
name: 'Active',
iconName: 'lucide:folder',
items: [
{ key: 'Frontend App', iconName: 'lucide:code', action: () => select('frontend'), badge: 3, badgeVariant: 'warning' },
{ key: 'API Server', iconName: 'lucide:server', action: () => select('api') }
]
}
]}
@item-select=${handleSectionChange}
></dees-appui-secondarymenu>
DeesAppuiMaincontent
Main content area with tab management support.
<dees-appui-maincontent
.tabs=${[
{ key: 'Overview', iconName: 'lucide:home', action: () => selectTab('overview') },
{ key: 'Details', iconName: 'lucide:info', action: () => selectTab('details') }
]}
@tab-select=${handleTabChange}
>
<!-- Content goes here -->
</dees-appui-maincontent>
DeesAppuiAppbar
Once a window menu is open, hovering another enabled heading switches its dropdown. Left/Right switches between open menus; Escape closes and restores focus to the current heading. Touch uses taps. At narrow component widths, multiple window menus combine into a single menu so every command remains reachable beside the location and account controls.
Collapsed main and secondary sidebars show compact hints beside icons on pointer hover or keyboard focus, including their expand controls. Professional application bar component with hierarchical menus, breadcrumb navigation, user account management, and activity log toggle.
<dees-appui-appbar
.menuItems=${[
{
name: 'File',
action: async () => {},
submenu: [
{ name: 'New File', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => handleNewFile() },
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => handleOpen() },
{ divider: true },
{ name: 'Save', shortcut: 'Cmd+S', iconName: 'save', action: async () => handleSave(), disabled: true }
]
}
]}
.breadcrumbs=${'Project > src > components'}
.showWindowControls=${true}
.showSearch=${true}
.showActivityLogToggle=${true}
.activityLogCount=${5}
.activityLogActive=${false}
.user=${{
name: 'John Doe',
avatar: '/path/to/avatar.jpg',
status: 'online' // Options: 'online' | 'offline' | 'busy' | 'away'
}}
@menu-select=${(e) => handleMenuSelect(e.detail.item)}
@breadcrumb-navigate=${(e) => handleBreadcrumbClick(e.detail)}
@activity-toggle=${() => handleActivityToggle()}
></dees-appui-appbar>
Key Features:
- Hierarchical Menu System — Top-level menus with dropdown submenus, icons, and keyboard shortcuts
- Keyboard Navigation — Full keyboard support (Tab, Arrow keys, Enter, Escape)
- Breadcrumb Navigation — Customizable breadcrumb trail with click events
- User Account Section — Avatar with status indicator and profile dropdown
- Activity Log Toggle — Button with badge count to show/hide activity panel
- Accessibility — Full ARIA support with menubar roles
DeesAppuiActivitylog
Activity panel with a visible-entry count, date groups and search. Set .live=${true}
only while an event stream is connected to show a static live indicator. The shell
exposes the same choice through configure({ activityLog: { live: true } }) or
the activityLogLive property; it defaults to false.
<dees-appui-activitylog></dees-appui-activitylog>
// Programmatic API
activityLog.add({
type: 'update', // Options: login, logout, view, create, update, delete, custom
user: 'John Doe',
message: 'Updated project settings',
iconName: 'lucide:settings' // Optional: custom icon
});
activityLog.addMany(entries); // Add multiple entries
activityLog.clear(); // Clear all entries
activityLog.getEntries(); // Get all entries
activityLog.filter({ user: 'John' }); // Filter by user/type
activityLog.search('settings'); // Search by message
Key Features:
- Stacked entry layout with icon, user, timestamp, and message
- Date grouping (Today, Yesterday, etc.)
- Search and filter functionality
- Context menu for entry actions
- Optional static live indicator, without perpetual animation
- Animated slide-in/out panel
- Theme-aware styling
DeesAppuiBottombar
A 24px fixed-height status bar at the bottom of the application shell. Supports status widgets and action buttons positioned left or right.
// Configure via DeesAppui
appui.configure({
bottomBar: {
visible: true,
widgets: [
{
id: 'status',
iconName: 'lucide:activity',
label: 'System Online',
status: 'success', // 'idle' | 'active' | 'success' | 'warning' | 'error'
tooltip: 'All systems operational',
onClick: () => console.log('Status clicked'),
},
{
id: 'version',
iconName: 'lucide:gitBranch',
label: 'v1.2.3',
position: 'right',
}
],
actions: [
{
id: 'terminal',
iconName: 'lucide:terminal',
tooltip: 'Open Terminal',
position: 'right',
onClick: () => console.log('Terminal clicked'),
}
]
}
});
// Programmatic updates
appui.bottomBar.addWidget({ id: 'build', iconName: 'lucide:hammer', label: 'Building...', loading: true, status: 'active' });
appui.bottomBar.updateWidget('build', { label: 'Build complete', loading: false, status: 'success' });
appui.bottomBar.removeWidget('build');
appui.bottomBar.addAction({ id: 'refresh', iconName: 'lucide:refreshCw', onClick: () => location.reload() });
appui.bottomBar.removeAction('refresh');
appui.setBottomBarVisible(false);
Key Features:
- Configurable status widgets with icons, labels, and colored status indicators
- Loading spinner state for widgets
- Contextual actions with icon buttons
- Left/right positioning for both widgets and actions
- Tooltips on hover
- Context menu support per widget
DeesAppuiTabs
Content-panel navigation with native tab buttons, separate close controls and
arrow/Home/End focus movement. Enter/Space select; Delete closes a closeable tab.
IMenuItem.key remains the identity; optional label supplies display text.
Use multitoggle for a view mode within the current panel.
Both horizontal and vertical layouts use an inset selection surface.
showTabIndicator=false hides that surface while retaining selected text and ARIA
state. Selection styling stays attached to the tab through scrolling and resizing.
<dees-appui-tabs
.tabs=${[
{ key: 'Home', iconName: 'lucide:home', action: () => console.log('Home') },
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') }
]}
tabStyle="horizontal" // Options: horizontal, vertical
.showTabIndicator=${true}
@tab-select=${handleTabSelect}
></dees-appui-tabs>
Data Display Components
DeesTable
Schema-driven table with sorting, search, selection, editing, and pinned row actions.
html`<dees-table
heading1="Transactions"
heading2="Review ownership and amounts"
dataName="transactions"
.data=${transactions}
.rowKey=${'id'}
.columns=${[
{ key: 'name', header: 'Name' },
{ key: 'amount', header: 'Amount', align: 'end', editor: 'number', parse: Number },
]}
.dataActions=${[{
name: 'View details',
iconName: 'lucide:PanelRight',
type: ['inRow', 'contextmenu', 'doubleClick'],
actionFunc: async ({ item, table }) => openTransaction(item),
}]}
.selectionMode=${'multi'}
.showSelectionCheckbox=${true}
.fixedHeight=${true}
style="--table-max-height: 420px"
@selectionChange=${handleSelectionChange}
@cellEdit=${handleCellEdit}
></dees-table>`
heading1is the title,heading2its description, andlabela compact alternative. Headings default to empty. The footer reports visible, total, and selected rows; filtered-out selections remain counted when their IDs still exist indata.- Columns accept
align: 'start' | 'center' | 'end'(defaultstart). End-aligned values use tabular numerals. Alignment is explicit and never inferred from formatted content. AdisplayFunctioncan supply columns, or augment a partial schema withaugmentFromDisplayFunction. - Quiet row separators are the default. Set
showGrid,showVerticalLines, orshowHorizontalLinesfor stronger grid lines. - Search stays visible when
searchableis true. Its menu selectssearchMode:tablesearches visible column values,datasearches original row values, andserveremitssearchRequestwith{ query, mode: 'server' }. The application owns server results;filterTextis not applied again in that mode. Local search supports Lucene-like syntax and emitsfilterChangewith{ text, columns }.showColumnFiltersenables per-column inputs; a column can opt out withfilterable: false. - Native header buttons sort and expose
aria-sort. Shift-click builds a multi-column sort; the header context menu controls sort priority.sortByandsortChangeexpose the descriptors. selectionModeacceptsnone,single, ormulti. Use a stablerowKeyto retain selection and editing through fresh data arrays.showSelectionCheckboxincludes select-all for visible rows.- Action
typeacceptsheader,inRow,contextmenu,footer,doubleClick,preview, andkeyCombination. UseactionRelevancyCheckFuncfor row-specific availability. Desktop rows show up to three relevant primary actions plus overflow; below 700px of table width, one Actions menu contains the row actions. Coarse pointers receive 44px targets independently of width. The opaque pinned column shows an edge shadow only while it covers scrolling columns. fixedHeightuses internal scrolling with sticky headers and--table-max-height; the default uses a floating header while the page scrolls.virtualizedlimits rendered rows and retains spacer geometry.- Set a column's
editortotext,number,checkbox,dropdown,date, ortags;editable: truedefaults to text. UseeditorOptions,format,parse, andvalidatefor value handling. Double-click edits a cell; Enter/Tab commit and move, Escape cancels.cellEditreports{ row, key, oldValue, newValue };cellEditErrorreports validation failures. - File drop onto rows and
highlightUpdates: 'flash'remain available. Flashing respects reduced motion and stable row identity.
DeesDataviewCodebox
The Inline/Split selector uses dees-input-multitoggle and follows the effective
layout, including automatic resizing. frameless removes the outer frame when
embedded in a parent-owned preview.
Code display component with syntax highlighting and line numbers.
<dees-dataview-codebox
progLang="typescript" // Programming language for syntax highlighting
.codeToDisplay=${`
import { html } from '@design.estate/dees-element';
export const myComponent = () => {
return html\`<div>Hello World</div>\`;
};
`}
></dees-dataview-codebox>
Diff mode — set codeBefore (compared against codeToDisplay as the after state) or a pre-computed unifiedDiff patch; diffView selects 'inline', 'split' (side-by-side), or 'auto' and can force diff mode on its own:
<dees-dataview-codebox
progLang="typescript"
filename="config.ts"
diffView="split"
.codeBefore=${previousSource}
.codeToDisplay=${newSource}
></dees-dataview-codebox>
Diff rendering includes word-level intraline change highlighting, unchanged-context folding with expandable "N unchanged lines" rows, +added −removed stats in the footer, and a "Copy Unified Diff" context-menu action. The app bar switches between inline and split layouts and emits the bubbling, composed diff-view-change event with IDiffViewChangeDetail ({ diffView: TDiffView }). The exported TDiffView type is 'inline' | 'split'. Split mode keeps a fixed center divider and gives each side an independent horizontal scroll position while the containing surface owns their shared vertical movement. Set showDiffLineNumbers to false when snippet inputs do not carry trustworthy source positions. The copy button yields the after-state text. The dependency-free engine (computeLineDiff, parseUnifiedDiff, foldContextRows, buildSplitRows, diffStats, toUnifiedDiff) is exported for programmatic use.
diffView="auto" uses inline rows below 640px of codebox width and split rows at 640px or wider. Resizing preserves expanded context and does not emit diff-view-change. Clicking either layout button fixes that choice, including when it already matches the automatic layout; set diffView back to 'auto' to resume adaptation. The exported TDiffViewRequest adds 'auto' to TDiffView, while event details and saved manual choices remain 'inline' | 'split'. The default empty diffView and explicit layouts retain their existing behavior.
DeesDataviewStatusobject
Service health with a summary, named checks, values, and status explanations. Checks use distinct icons for ok, partly_ok, and not_ok; long values wrap to the available width. Missing data is shown separately from a healthy result.
<dees-dataview-statusobject
.statusObject=${{
id: '1',
name: 'System Status',
combinedStatus: 'partly_ok',
combinedStatusText: 'Partially OK',
details: [
{ name: 'Database', value: 'Connected', status: 'ok', statusText: 'OK' },
{ name: 'API Service', value: 'Degraded', status: 'partly_ok', statusText: 'Partially OK' }
]
}}
></dees-dataview-statusobject>
Set lastUpdated to an epoch timestamp in milliseconds to show when checks were received. Copy JSON copies the current status object. Each check has a Copy options button for its value, key, or key and value; right-click and Shift+F10 open the same menu. Clipboard success and failure are announced visibly. Controls support keyboard focus and 44px touch targets.
DeesStatsGrid
A responsive grid component for displaying statistical data with various visualization types.
<dees-statsgrid
.tiles=${[
{
id: 'revenue',
title: 'Total Revenue',
value: 125420,
unit: '$',
type: 'number',
icon: 'lucide:dollarSign',
description: '+12.5% from last month',
color: '#22c55e'
},
{
id: 'cpu',
title: 'CPU Usage',
value: 73,
type: 'gauge',
icon: 'lucide:cpu',
gaugeOptions: {
min: 0, max: 100,
thresholds: [
{ value: 0, color: '#22c55e' },
{ value: 60, color: '#f59e0b' },
{ value: 80, color: '#ef4444' }
]
}
},
{
id: 'requests',
title: 'API Requests',
value: '1.2k',
unit: '/min',
type: 'trend',
icon: 'lucide:server',
trendData: [45, 52, 38, 65, 72, 68, 75, 82, 79, 85, 88, 92]
},
{
id: 'cores',
title: 'CPU Cores',
value: 0,
type: 'cpuCores',
icon: 'lucide:cpu',
columnSpan: 2,
coresData: [
{ id: 0, usage: 45, label: '0' },
{ id: 1, usage: 72, label: '1' },
{ id: 2, usage: 30, label: '2' },
{ id: 3, usage: 88, label: '3' }
]
}
]}
.minTileWidth=${250}
.gap=${16}
></dees-statsgrid>
Tile Types: number, gauge, percentage, trend, text, multiPercentage, cpuCores, partition, disk.
minTileWidth and gap determine the number of columns from the component's available width. A tile's columnSpan is clamped to that count, so wide metrics fit narrow containers. Descriptions share the same placement across tile types. Replace tiles or gridActions to render a fresh sample; the component does not poll for data.
Tile actions and gridActions accept IMenuItem[]. A single tile action appears as a named button and also runs when the tile is clicked. Multiple actions appear in an explicit menu button. Right-click or Shift+F10 opens the same tile menu; keyboard focus returns to its opener on dismissal. Touch controls use 44px targets, and metric transitions respect reduced motion.
DeesPagination
Pagination component for navigating through large datasets.
<dees-pagination
totalItems={500}
itemsPerPage={20}
currentPage={1}
maxVisiblePages={7}
@page-change=${handlePageChange}
></dees-pagination>
DeesStorageBrowser
An object-storage workspace with a shared Columns/List segmented control, breadcrumb navigation, file/folder action menus and a resizable preview. At narrow widths the preview replaces the file list; Back to files restores focus. Editing, selection and navigation respect unsaved changes and in-progress writes.
html`<dees-storage-browser
style="height: 600px"
.dataProvider=${this.storageProvider}
.bucketName=${'assets'}
.onChangeEvent=${this.subscribeToStorageChanges}
></dees-storage-browser>`;
Implement IStorageDataProvider from the
storage interfaces.
It provides listObjects, getObject, putObject, deleteObject, deletePrefix,
getObjectUrl, moveObject and movePrefix. File content is base64 at the provider
boundary; list results contain objects and prefixes. Move methods return a
success flag and optional error; write/delete methods return a boolean.
onChangeEvent accepts a callback and returns an unsubscribe function. Its events
use { type: 'add' | 'modify' | 'delete', key, bucket, size?, lastModified? }.
Subscriptions are scoped to the bucket and released when detached. Provider and
selection changes invalidate stale preview requests.
getLocation() returns { prefix, key }; setLocation({ prefix, key? }) applies a
host-owned location and returns false when the current draft or write blocks it.
storage-location-change reports accepted navigation. Columns and List retain the
same folder, and narrow Columns layouts show the current folder with the breadcrumb
for navigating back. Column and preview dividers support pointer and keyboard resizing.
Row rename, move, and delete operations update the open preview and any affected
folder path. The child views emit storage-before-mutation (cancelable) before a
move/rename/delete and storage-mutation after a successful move/rename/delete, with
IStorageMutationEvent detail. When composing the child views directly, use these
events to guard drafts and update selected paths. dees-storage-preview exposes a
header-actions slot for a close/back control; the slot remains available during
loading and errors. Search uses dees-input-text; view switching and selected rows
reuse the catalog's shared control and selection styles.
The browser composes dees-storage-columns, dees-storage-keys and
dees-storage-preview. Use the browser for the complete workflow; compose these
lower-level components only when the application owns navigation and preview state.
For route integration, getLocation() returns { prefix, key } and
setLocation({ prefix, key? }) applies a host-owned folder/preview selection. It returns
false if a pending write or unsaved preview prevents navigation. canLeave() exposes
the same guard to app shells. Accepted user navigation emits the composed
storage-location-change event with { prefix, key }; programmatic setLocation()
does not emit it, preventing host/child update loops. Wait for the browser's initial
updateComplete after assigning its provider before applying a deep link.
Media & Thumbnail Components 🎬
A rich collection of thumbnail components for displaying media files in grids. All thumbnails share a consistent base class (DeesThumbnailBase) with lazy loading via IntersectionObserver, hover interactions, click events, and three size variants (small, default, large). DeesThumbnailPdf also supplies PDF-specific context-menu actions.
Thumbnail metadata sits in a shared caption below the preview. The preview stays mounted
while loading, so image load events and media rendering can finish. Clickable tiles have
an accessible name, a visible keyboard focus ring, and Enter/Space activation through
tile-click; set .clickable=${false} for a non-interactive preview. Hover keeps the
content visible while scrubbing PDF pages or notes. Labels remain visible during errors.
Image and video tiles accept .fit=${'contain'} to show the complete frame; the default
cover preserves cropped photo/video tiles. Audio and video release pending source work
on replacement or disconnect; failures show the shared error state and a new source
can recover. Audio waveforms come from the recording, and video frame captures are
bounded to the preview size and device pixel ratio. PDF pages retain a 2px radius and
the existing shared worker/render pipeline. All six demos share a mixed file shelf,
open real previews, and retain their individual samples and size examples below it.
DeesThumbnailPdf
PDF document thumbnail with a locally rendered page preview.
<dees-thumbnail-pdf
.pdfUrl=${'/documents/report.pdf'}
label="Annual Report"
.clickable=${true}
@tile-click=${handleClick}
></dees-thumbnail-pdf>
Key Features:
- Renders first page as canvas preview
- Hover to scrub through pages (mouse X position maps to page number)
- Shows page count, file size, and hover page indicator
- Detects A4/Letter vs non-standard aspect ratios
- Bundles the PDF.js worker locally and releases it when the URL changes or the component disconnects
Public properties include pdfUrl, currentPreviewPage, pageCount, rendered,
fileSize, clickable, loading, error, size, and label. Loading starts within
the thumbnail observer's 200px viewport preload margin. URL replacement, failed loads, and disconnection all
release the current PDF document and worker.
DeesThumbnailImage
Image thumbnail with lazy loading and dimension display.
<dees-thumbnail-image
src="/photos/landscape.jpg"
alt="Mountain landscape"
label="landscape.jpg"
clickable
@tile-click=${handleClick}
></dees-thumbnail-image>
Key Features:
- Lazy loads image on scroll into view
- Shows image dimensions after loading (e.g. "1920 × 1080")
- Checkerboard background for transparent images
DeesThumbnailAudio
Audio file tile with waveform visualization.
<dees-thumbnail-audio
src="/music/track.mp3"
title="Summer Vibes"
artist="DJ Example"
clickable
@tile-click=${handleClick}
></dees-thumbnail-audio>
Key Features:
- Generates waveform visualization from audio data
- Shows duration badge (e.g. "3:42")
- Displays title and artist metadata
- Keyboard and pointer activation through
tile-click
DeesThumbnailVideo
Video tile with thumbnail capture and hover preview.
<dees-thumbnail-video
src="/videos/intro.mp4"
poster="/thumbs/intro.jpg"
label="Introduction"
clickable
@tile-click=${handleClick}
></dees-thumbnail-video>
Key Features:
- Auto-captures first frame as thumbnail (or uses provided
poster) - Plays video preview on hover
- Shows duration badge
- Play button overlay
DeesThumbnailNote
Plain-text note thumbnail with a monospace preview.
<dees-thumbnail-note
title="config.ts"
language="TypeScript"
.content=${codeString}
clickable
@tile-click=${handleClick}
></dees-thumbnail-note>
Key Features:
- Monospace plain-text preview
- Optional language metadata in the bottom information bar
- Scrollable content on hover (mouse X position controls scroll)
- Gradient fade at bottom
DeesThumbnailFolder
Folder tile with 2×2 content preview grid.
<dees-thumbnail-folder
name="Project Assets"
.items=${[
{ type: 'image', name: 'logo.png', thumbnailSrc: '/thumbs/logo.png' },
{ type: 'pdf', name: 'spec.pdf' },
{ type: 'audio', name: 'jingle.mp3' },
{ type: 'video', name: 'demo.mp4' },
]}
clickable
@tile-click=${handleClick}
></dees-thumbnail-folder>
Key Features:
- 2×2 preview grid showing first 4 items (thumbnails or type icons)
- Item count badge (e.g. "12 items")
- Folder icon header with name
- Supports:
pdf,image,audio,video,note,folder,unknowntypes
DeesPreview
Use dees-preview to inspect a file without choosing an individual viewer. Supply
one source: file, url, raw base64 with mimeType, or textContent. It detects
images, PDFs, audio, video, text, and source files; contentType and language
override detection. filename supplies a readable heading and download name.
html`<dees-preview style="height: 560px" .file=${selectedFile}></dees-preview>`;
The preview owns the frame and filename heading; embedded viewers do not add a
second frame. Set frameless when the enclosing layout already owns that border.
showFilename hides the heading; showToolbar controls the image/PDF tools.
Failed text requests show an error with a retry action (reload()); source changes
abort pending reads and revoke owned file URLs. Unsupported types show an empty state.
Use the individual image/PDF/audio/video viewer when its specialized controls or
properties are needed. Use dees-thumbnail-* for file tiles, and dees-preview
for the selected file's full inspection area. Reuse dees-appui-tabs for the file
list and dees-input-multitoggle for view switching instead of local toggle markup.
DeesPdfViewer
Full PDF viewer with page navigation, zoom, fit modes, selectable text, downloads, printing, an optional thumbnail sidebar, and an all-page gallery. PDF.js and its module worker are bundled locally; the component owns and releases each worker as documents are replaced or the viewer disconnects.
<dees-pdf-viewer
.pdfUrl=${'/documents/report.pdf'}
.initialPage=${1}
.initialZoom=${'page-fit'}
.showSidebar=${true}
></dees-pdf-viewer>
Public properties include pdfUrl, initialPage, initialZoom, showToolbar,
showSidebar, sidebarPosition, viewMode (document or gallery), frameless,
showFooter, filename, currentPage, totalPages, currentZoom, loading,
loadError, and pdfFileSize. The Document/Gallery segmented control changes modes;
clicking or pressing Enter on a page tile returns to that page in the document.
Gallery tiles share the document's existing PDF.js worker, load near the visible
area, and rasterize at the current width and device pixel ratio. Supply
.viewMode=${'gallery'} to open directly in the gallery. This works independently
of the document's showSidebar preference. URL replacement, failed loads, and disconnection cancel active work,
remove observers and listeners, and release the current PDF document and worker.
PdfManager
Use PdfManager when loading a PDF outside the supplied components. Always release the
returned document. An abort signal cancels an in-flight load and triggers bounded worker
cleanup.
When migrating from 6.x, pass the PDFDocumentProxy returned by loadDocument() to
releaseDocument() instead of passing the source URL.
PdfManager.initialize() is retained as an asynchronous no-op for existing callers;
static PDF.js imports require no initialization step.
import { PdfManager } from '@design.estate/dees-catalog';
const abortController = new AbortController();
const pdfDocument = await PdfManager.loadDocument(pdfUrl, abortController.signal);
try {
const firstPage = await pdfDocument.getPage(1);
// Render or inspect the page.
firstPage.cleanup();
} finally {
await PdfManager.releaseDocument(pdfDocument);
}
DeesImageViewer
Image inspection with zoom, pointer pan, Fit/Actual size, dimensions, and download.
Set src, alt, optional filename, fit (contain, cover, or actual), and
showToolbar. The zoomIn(), zoomOut(), resetZoom(), fitToScreen(), and
actualSize() methods operate on the same view state as the toolbar.
DeesAudioViewer
Audio playback with a waveform decoded from src, keyboard-operable seeking,
volume, mute, loop, and optional title/artist. showWaveform hides the waveform;
autoplay remains subject to browser permission. Waveform decoding failure leaves
playback available and shows a note. Playback does not re-rasterize the waveform
each frame. Replacing the source or disconnecting stops the old player and aborts
its pending download.
DeesVideoViewer
Video playback with seeking, volume, mute, loop, and fullscreen. Set src,
poster, autoplay, loop, and muted; showControls=false uses the browser's
native controls. Set frameless to fill an enclosing preview stage. Custom controls
reappear for keyboard focus and respect reduced motion. Disconnecting releases the
media source; reconnecting restores it. Both audio and video expose play(),
pause(), togglePlay(), seek(seconds), and setVolume(0..1).
Visualization Components
Charts share a quiet frame, heading, readable series footer and empty/loading states. ECharts bar/donut/gauge/radar components expose a textual data summary and respect reduced motion; legend buttons toggle their actual series. Charts resize with their container and recreate their engines when reattached. Assign a bounded height when embedding in a dashboard.
| Question | Component | Data |
|---|---|---|
| How does a value change over time? | dees-chart-area |
Named series of { x, y } samples. |
| How do categories compare? | dees-chart-bar |
categories and named numeric series. |
| What makes up the whole? | dees-chart-donut |
{ name, value, color? } slices. |
| How close is a value to capacity? | dees-chart-gauge |
value, min, max, unit, optional thresholds. |
| How do profiles compare on several dimensions? | dees-chart-radar |
indicators and named value arrays. |
DeesChartBar
html`<dees-chart-bar label="Weekly requests"
.categories=${['Mon', 'Tue', 'Wed']}
.series=${[{ name: 'Gateway', data: [120, 180, 150] }]}
.valueFormatter=${(value: number) => `${value}k`}
></dees-chart-bar>`;
horizontal, stacked and showLegend control presentation. Each series accepts
an optional color; category and series arrays should have matching lengths.
DeesChartDonut
html`<dees-chart-donut label="Requests by region"
.data=${[{ name: 'Europe', value: 42 }, { name: 'Americas', value: 34 }, { name: 'Asia Pacific', value: 24 }]}
></dees-chart-donut>`;
Use showLabels, showLegend, innerRadiusPercent (default '55%') and
valueFormatter to adjust presentation. Narrow layouts use short percentage labels outside
the ring while the wrapping footer retains names and values.
DeesChartGauge
html`<dees-chart-gauge label="Storage capacity" .value=${64} .min=${0} .max=${100} unit="%"></dees-chart-gauge>`;
thresholds accepts { value, color } entries. showTicks controls the scale;
NaN represents an unavailable value and displays the empty state.
DeesChartRadar
html`<dees-chart-radar label="Service profile"
.indicators=${[{ name: 'Speed', max: 100 }, { name: 'Reliability', max: 100 }, { name: 'Capacity', max: 100 }]}
.series=${[{ name: 'Gateway', values: [82, 96, 74] }]}
></dees-chart-radar>`;
fillArea and showLegend control presentation. Each series can supply a color.
Its values array follows the order of indicators.
DeesChartArea
yAxisFormatter formats both the value axis and legend statistics. It receives
fractional values from the chart engine even when samples are integers, so bound
precision explicitly (for example, (value) => Math.round(value) + "k"). Unbounded
decimals reserve unnecessary axis space on small charts. The default Mbps
formatter rounds to two decimal places.
Area chart component built on Lightweight Charts for time-series data. Enable
rangeSelectionEnabled to let the user drag across the plot. The composed,
bubbling range-change event carries { from, to } as Unix epoch
milliseconds. Apply that range back through selectedRange to keep the
selection visible; set it to null to clear the overlay.
<dees-chart-area
label="System Usage"
.rangeSelectionEnabled=${true}
.selectedRange=${selectedRange}
.series=${[
{
name: 'CPU',
data: [
{ x: '2025-01-15T03:00:00', y: 25 },
{ x: '2025-01-15T07:00:00', y: 30 },
{ x: '2025-01-15T11:00:00', y: 20 }
]
}
]}
@range-change=${(event: CustomEvent<{ from: number; to: number }>) => {
selectedRange = event.detail;
}}
></dees-chart-area>
DeesChartLog
Specialized chart component for visualizing log data and events.
<dees-chart-log
label="System Events"
.data=${[
{ timestamp: '2025-01-15T03:00:00', event: 'Server Start', type: 'info' },
{ timestamp: '2025-01-15T03:15:00', event: 'Error Detected', type: 'error' }
]}
.filters=${['info', 'warning', 'error']}
@event-click=${handleEventClick}
></dees-chart-log>
Dialogs & Overlays Components
DeesModal
An opaque, themed window backed by a native modal dialog. The browser contains
keyboard focus, makes the underlying page inert, and restores focus on dismissal.
Escape closes the frontmost dialog. destroy() is safe to call repeatedly.
const modal = await DeesModal.createAndShow({
heading: 'Confirm Action',
content: html`
<dees-form>
<dees-input-text key="reason" label="Enter reason"></dees-input-text>
</dees-form>
`,
menuOptions: [
{ name: 'Cancel', action: (instance) => instance!.destroy() },
{ name: 'Confirm', action: async (instance) => { /* handle */ await instance!.destroy(); } }
]
});
Use subheading for supporting text and sidebar for a settings navigation
template. Sidebar windows reserve 400px of height on desktop and 520px at
viewport widths up to 600px, where navigation stacks above the content. Replacing
content, heading, or subheading does not resize the window. Set height to
another pixel value to override the height in both layouts. Both dimensions are clamped
to the viewport; the content scrolls independently of the heading and actions.
Dialogs without a sidebar use their content height unless height is supplied.
width accepts small (380px), medium (560px), large (800px), fullscreen,
or a pixel value. minWidth and maxWidth remain bounded by the viewport.
mobileFullscreen fills the screen below 600px. The optional header controls are
showCloseButton, showHelpButton, and onHelp. contentPadding defaults to 24px.
The final menuOptions action receives the accent treatment.
For a single name, use await DeesModal.prompt({ heading, label, value?, confirmLabel?, validate?, onConfirm? }). It returns the trimmed value on success
or null on dismissal. Enter confirms, while composition remains with the input.
validate(value) returns an error string or undefined; async onConfirm(value)
runs before closing. Rejected operations leave the dialog open with their error,
and repeated submission and dismissal are blocked while confirmation is pending.
The modal emits modal-close when removed.
The modal gallery includes a working settings draft: values survive navigation,
Save returns the draft, and Cancel discards it. The example lives in
ts_web/elements/00group-overlay/dees-modal/dees-modal.demo.ts.
Dropdowns, calendars, context menus, and speech bubbles opened from a dialog join
its native top layer. Custom overlays should pass their invoking node as
ownerElement to DeesWindowLayer.createAndShow(). Toasts use the current keyboard
focus scope by default; pass ownerElement explicitly when showing a notification
from background work. Toast duration: 0 keeps it visible until dismissal.
DeesContextmenu
Context menu component for right-click actions with nested submenu support.
Menus take focus after becoming visible. ArrowUp/ArrowDown move through enabled items, Enter activates the focused item, and Escape dismisses the menu chain without reaching enclosing app shortcuts. Hosts can register focus restoration with the returned menu's registerGarbageFunction() hook.
// Programmatic usage
DeesContextmenu.openContextMenuWithOptions(mouseEvent, [
{
name: 'Edit',
iconName: 'lucide:edit',
action: async () => handleEdit()
},
{ divider: true },
{
name: 'More Options',
iconName: 'lucide:moreHorizontal',
submenu: [
{ name: 'Duplicate', iconName: 'lucide:copy', action: async () => handleDuplicate() },
{ name: 'Archive', iconName: 'lucide:archive', action: async () => handleArchive() },
]
},
{
name: 'Delete',
iconName: 'lucide:trash2',
action: async () => handleDelete()
}
]);
// Component-based (implement getContextMenuItems on any element)
class MyComponent extends DeesElement {
getContextMenuItems() {
return [
{ name: 'View Details', iconName: 'lucide:eye', action: async () => { ... } },
{ name: 'Edit', iconName: 'lucide:edit', action: async () => { ... } },
];
}
}
For menus belonging to an interactive control group such as a menubar, pass a third options argument: { ownerElement: trigger, interactionRoot: menubar }. The trigger determines dialog ownership and restored focus; the interaction root keeps related controls reachable while outside presses still dismiss. Ordinary context menus retain their pointer-blocking backdrop.
DeesSpeechbubble
For compact plain-text hints, place the bubble immediately after its control (or set .reffedElement). .disabled suppresses it when labels are already visible. Hints open to the right after a short hover delay or immediately on keyboard focus, escape scroll clipping through the browser's popover layer, and dismiss on Escape, activation, scroll, resize, or disconnect. They use an opaque surface and do not intercept input.
<button aria-label="Services">…</button>
<dees-speechbubble hint text="Services" .disabled=${!collapsed}></dees-speechbubble>
Tooltip-style speech bubble component for contextual information.
// Programmatic usage
const bubble = await DeesSpeechbubble.createAndShow(
referenceElement,
'Helpful information about this feature'
);
DeesWindowlayer
Base overlay component used by modal dialogs and other overlay components.
const layer = await DeesWindowLayer.createAndShow({
dimmed: false,
ownerElement: invokingElement,
});
Navigation Components
DeesStepper
Guided form and background-work flows with a compact progress strip, shared action
buttons, and a vertical transition between steps. Use it inline or open the same
steps as a modal with DeesStepper.createAndShow({ steps, cancelable: true }).
IStepacceptstitle, optionaldescription,content,menuOptions,allowBack,validationFunc,onReturnToStepFunc, andprogressStep.- The last menu action is primary. A nested
dees-formgates that action on its required fields and dispatches collectedformDatabefore the action runs. The required-fields hint fades beside the heading without changing its layout. - Async actions prevent duplicate activation and display failures in the current
step. Progress failures expose Try again;
retryStep()reruns validation. Keep external work abortable using the callback'sAbortSignal. - Progress steps use
dees-progressbarand advance after successful validation unlessprogressStep.autoAdvanceisfalse. UseupdateProgressStep(),appendProgressStepLine(), andresetProgressStep()to report work. goNext()andgoBack()preserve mounted form contents. Back honorsallowBack: false. Inactive steps are inert. Transitions respect reduced motion and adjust to changing content and container sizes without snapping.- Overlay mode uses native dialog focus containment and restoration. Cancel,
Escape, and backdrop clicks share a confirmation flow;
cancelable: falsedisables those dismissal paths.destroy()closes the flow;stepper-closefires when it disconnects. Disconnecting also aborts work and releases observers. - Inline steppers fill a bounded area (440px minimum height by default). Set a
height appropriate to the composition;
--dees-stepper-widthcontrols the card's maximum width (520px by default).
The catalog demo combines setup fields, preferences, a review, background work, and completion. Retry example fails the first background attempt and preserves the choices for recovery. It also shows optional and required-completion overlays.
<dees-stepper
style="height: 540px"
.steps=${[
{
title: 'Account Setup',
description: 'Choose the owner of your workspace.',
content: html`<dees-form>...</dees-form>`,
menuOptions: [{ name: 'Continue', action: async (stepper) => stepper?.goNext() }]
},
{
title: 'Provision Workspace',
content: html`<p>Preparing your environment...</p>`,
progressStep: {
label: 'Workspace setup',
indeterminate: true,
statusRows: 4,
terminalLines: ['Allocating workspace']
},
validationFunc: async (stepper, _element, signal) => {
stepper.updateProgressStep({ percentage: 35, statusText: 'Installing dependencies...' });
stepper.appendProgressStepLine('Installing dependencies');
if (signal?.aborted) return;
stepper.updateProgressStep({ percentage: 100, indeterminate: false, statusText: 'Workspace ready.' });
}
}
]}
></dees-stepper>
DeesProgressbar
Progress indicator component for tracking completion status, with optional fixed-height status text or terminal-style recent activity output.
<dees-progressbar
.percentage=${75}
label="Uploading"
statusText="Uploading thumbnails to edge cache..."
.statusRows=${2}
></dees-progressbar>
<dees-progressbar
label="Installing dependencies"
.indeterminate=${true}
.statusRows=${4}
.terminalLines=${[
'Resolving workspace packages',
'Downloading tarballs',
'Linking local binaries'
]}
></dees-progressbar>
Theming Components
DeesTheme
Theme provider component that wraps children and provides CSS custom properties for consistent theming.
// Basic usage — wrap your app
<dees-theme>
<my-app></my-app>
</dees-theme>
// With custom overrides
<dees-theme
.customColors=${{
primary: '#007bff',
success: '#28a745'
}}
.customSpacing=${{
lg: '24px',
xl: '32px'
}}
>
<my-section></my-section>
</dees-theme>
Key Features:
- Provides CSS custom properties for colors, spacing, radius, shadows, and transitions
- Can be nested for section-specific theming
- Works with dark/light mode
- Overrides cascade to all child components
DeesUpdater
Updater controller that opens a non-cancelable dees-stepper flow with a progress step and a ready step.
const updater = await DeesUpdater.createAndShow({
currentVersion: '3.79.0',
updatedVersion: '3.80.0',
moreInfoUrl: 'https://code.foss.global/design.estate/dees-catalog',
changelogUrl: 'https://code.foss.global/design.estate/dees-catalog/-/blob/main/changelog.md',
successAction: 'reload',
successDelayMs: 10000,
});
updater.updateProgress({
percentage: 35,
statusText: 'Downloading signed bundle...',
terminalLines: ['Checking release manifest', 'Downloading signed bundle']
});
updater.appendProgressLine('Verifying checksum');
updater.updateProgress({ percentage: 72, statusText: 'Verifying checksum...' });
await updater.markUpdateReady();
After markUpdateReady(), the updater switches to a second countdown step with a determinate progress bar and runs the configured success action when the timer reaches zero.
Workspace / IDE Components 💻
A full-featured IDE workspace component suite for building browser-based code editors, terminal interfaces, and documentation viewers.
DeesWorkspace
An editor workspace with shared document tabs, file navigation, Terminal/Problems
switching, terminal sessions, and a script/status bar. Supply an
IExecutionEnvironment; the shell uses that environment and does not own its
destruction. WebContainerEnvironment runs real commands in supported browsers;
backend implementations use the same interface.
html`<dees-workspace
workspaceName="My project"
.executionEnvironment=${environment}
.initializationPromise=${projectReady}
initialFilePath="/src/index.ts"
.fileTreeWidth=${250}
.terminalHeight=${200}
></dees-workspace>`
initializationPromise optionally waits for your project to be mounted.
initialFilePath opens a document after setup. Initialization errors remain
visible in the shell. showFileTree and showTerminal control which panels are
available; toolbar controls collapse the available panels. On narrow containers,
Files opens over the editor and returns to the document on selection.
Resizers support pointer dragging and arrow keys, Home, and End.
Use openFile(path, name?), saveActiveFile(), saveAllFiles(),
setFileTreeWidth(width), setTerminalHeight(height), and resetLayout() on the
element. Save shortcuts apply only inside the workspace. Failed saves retain
dirty state, and external-edit conflicts wait for an explicit choice.
The Workspace → DeesWorkspace demo composes a real, dependency-free Studio
Notes project. Try Edit/Run/Focus, edit and save src/notes.js, then choose
Scripts → test or preview. Sessions, output, and the filesystem use the
same runtime; there is no automatic package installation or simulated shell.
Hosting this demo requires the secure context and cross-origin isolation
supported by WebContainers.
Document and terminal sessions reuse dees-appui-tabs; output and layout choices
reuse dees-input-multitoggle. Tabs support appearance="sidebar" for flat
navigation rows, --dees-tabs-font-size, and --dees-tabs-vertical-bg for embedded
surfaces. Their item badges show unsaved documents and process exit status.
Prefer these shared controls over locally recreated tabs or toggles.
Terminal sessions can be renamed with double-click, F2, or the Rename context
menu. The label changes without restarting the process or clearing scrollback.
Reusable tabs expose this through IMenuItem.onRename. Both sidebar lists use
the shared --dees-color-sidebar-selection raised neutral fill with primary text.
The file explorer's New file and New folder dialogs accept Enter, reject existing
names, and show write errors before closing. A created file opens in the editor.
When Files is collapsed, the document strip's New file (+) action uses the same
creation flow and keeps Files collapsed. The explorer also exposes
createNewFile(parentPath?) and createNewFolder(parentPath?), defaulting to its
rootPath.
DeesWorkspaceMonaco
Monaco Editor integration for code editing with full IntelliSense, syntax highlighting, and language support.
<dees-workspace-monaco
.content=${code}
.language=${'typescript'}
.filePath=${'/src/index.ts'}
@content-change=${(event) => saveDraft(event.detail)}
></dees-workspace-monaco>
DeesWorkspaceDiffEditor
Side-by-side diff editor powered by Monaco for comparing file versions.
<dees-workspace-diff-editor
.originalContent=${originalCode}
.modifiedContent=${modifiedCode}
.language=${'typescript'}
></dees-workspace-diff-editor>
DeesWorkspaceFiletree
File tree navigation component with expand/collapse, file icons, and selection.
<dees-workspace-filetree
.executionEnvironment=${environment}
.rootPath=${'/'}
.selectedPath=${'/src/index.ts'}
@file-select=${handleFileSelect}
></dees-workspace-filetree>
Selection reveals parent directories. Arrow keys navigate the tree; Enter opens
a file, and Shift+F10 opens its actions. file-select carries { path, name }.
DeesWorkspaceTerminal
Terminal emulator component powered by xterm.js.
<dees-workspace-terminal></dees-workspace-terminal>
DeesWorkspaceTerminalPreview
Terminal with integrated preview pane for output visualization.
DeesWorkspaceMarkdown
Markdown editor with live preview.
DeesWorkspaceMarkdownoutlet
Read-only markdown renderer for documentation display.
DeesWorkspaceBottombar
IDE-style bottom status bar for the workspace.
Agentic Chat Components 🤖
The 00group-harness family renders what an agent harness produces — streaming messages, reasoning, tool calls, MCP content, permission prompts — behind one normalized data contract (IHarnessMessage, IHarnessToolCall, IHarnessPermissionRequest, IHarnessSessionMeta, IHarnessStatus). Consumers adapt their wire format to these interfaces and feed the components; all events are harness-* CustomEvents (bubbles + composed).
DeesHarnessChat
The assembled chat: optional toolbar (heading, usage chip, host-supplied actions, and session-details control), streaming message list with inline permission cards, transcript status line, and docked composer.
const chat = document.querySelector('dees-harness-chat');
chat.messages = adaptedMessages; // IHarnessMessage[]
chat.permissions = pendingPermissions; // IHarnessPermissionRequest[]
chat.status = { type: 'busy', message: 'Responding…' };
chat.account = 'connection-a';
chat.accountOptions = [
{ label: 'Personal · alex@example.com', value: 'connection-a' },
{ label: 'Team · alex@example.com', value: 'connection-b' },
];
chat.modelOptions = ['gpt-5.5', 'o4-mini'];
chat.effortOptions = ['high', 'medium', 'low'];
chat.markdownWhileStreaming = false; // optional: plain streams, one final parse at message end
chat.toolbarActions = [
{
id: 'refresh',
label: 'Refresh conversation',
iconName: 'lucide:RefreshCw',
tooltip: 'Refresh conversation',
action: () => refreshConversation(),
},
];
const commandSuggestions = [
{ label: 'Explain code', value: 'Explain the selected code' },
{ label: 'Review diff', value: 'Review the current diff', description: 'Focus on bugs and regressions.' },
];
chat.suggestions = commandSuggestions;
chat.addEventListener('harness-input', (event) => {
const value = event.detail.value;
chat.suggestions = value.startsWith('/')
? commandSuggestions.filter((suggestion) => suggestion.label.toLowerCase().includes(value.slice(1).toLowerCase()))
: [];
});
chat.addEventListener('harness-send', (event) => {
const { text, attachments, account, model, reasoningEffort } = event.detail;
});
chat.addEventListener('harness-permission-response', (event) => {
const { requestId, response, remember } = event.detail; // 'once' | 'always' | 'reject'
});
// streaming fast path — mutates the same message object, re-renders only that element
chat.applyDelta({ type: 'text', messageId: 'm1', delta: 'chunk' });
chat.applyDelta({ type: 'message-end', messageId: 'm1', usage: { totalTokens: 1200 } });
// one-level child-session streaming uses the parent tool-message id
chat.applyDelta({
type: 'text',
parentMessageId: 'task-message-1',
messageId: 'child-message-1',
delta: 'child chunk',
});
// authoritative same-object corrections use IDs
chat.messages.find((message) => message.id === 'm1')!.text = 'corrected text';
chat.refreshMessages(['m1']);
// structural mutations omit IDs; an empty ID list is a no-op
chat.messages.push({ id: 'm2', role: 'assistant', text: 'next message', createdAt: Date.now() });
chat.refreshMessages();
chat.refreshMessages([]);
Key props: messages, permissions, questions, status, transcriptKey, hasEarlierMessages/loadingEarlier, usage, todos/todosAuthoritative/showTodosPanel, sessionMetrics, scratchpad/scratchpadBusy/scratchpadError, sessionIntelligenceEnabled/intelligenceExchanges/intelligenceBusy/intelligenceError/intelligenceAvailabilityStatus/intelligenceUnavailableReason/intelligenceHeading, showSessionSidebar, heading, subheading, toolbarActions, showToolbar, busy, queuedCount, steeringEnabled, disabled, suggestions, account/accountOptions, model/modelOptions, reasoningEffort/effortOptions, mode/modeOptions, markdownWhileStreaming, toolRegistry. toolbarActions accepts IHarnessChatToolbarAction[]; each action has id, human-readable label, iconName, action, and optional tooltip/disabled, and renders immediately before the session-details toggle. disabled: true makes only that toolbar action inert and prevents its callback without changing the chat-level disabled state. markdownWhileStreaming defaults to true; setting it to false keeps active outer and nested subtask markdown as plain text, then performs the final markdown parse when message-end arrives. suggestions is forwarded unchanged to the controlled composer. Account options use human-readable label values and opaque stable value identifiers. The status line renders inside the transcript column (it never pushes between the session panel and the composer), and both the toolbar and the docked composer cast a subtle shadow over the scrolling messages.
After mutating canonical message objects in place, call refreshMessages(ids?). Pass IDs for targeted content corrections, omit them after structural mutations, and pass an empty array for a no-op. DeesHarnessChat.refreshMessages(ids?) uses the list semantics and, except for [], also recomputes transcript-derived sidebar state. The list and individual message components expose the corresponding refreshMessages(ids?) and refreshMessage() methods.
A session side panel shows Tasks, Tools & Subagents, Tokens, Session Scratchpad, and Session Intelligence next to the transcript. Its sections form a single-open accordion: opening one collapses the others, clicking the open heading collapses all, and each expanded section owns its scrolling while every heading remains visible. Collapsed sections retain their region wrapper but omit the content subtree until expansion; sidebar-owned scratchpad and intelligence drafts survive that lazy remount. Tools & Subagents separates active calls from terminal history and derives both directly from top-level tool messages still present in the host's retained transcript window; evicted calls are not cached. Long activity labels and descriptions use a 40px right-edge fade instead of ellipses, reveal the full line on hover, and run through once after the two-second delay when their section has measurable width. Session Intelligence keeps its exchange history scrollable above a docked, chat-style question composer. Set .todos explicitly, or leave it empty and the chat derives tasks from the newest todo tool call in messages; showTodosPanel={false} hides only that section. Unknown token metrics remain absent. The toolbar controls the session panel; host-owned navigation controls belong outside the chat component. In narrow containers the session panel becomes an accessible overlay; when the toolbar is disabled, an inline Session details control keeps it reachable. showSessionSidebar={false} disables the complete panel. Methods: applyDelta(), refreshMessages(ids?), focusComposer(), clearComposer(), scrollToBottom(). Events (also from children, all composed): harness-send, harness-steer, harness-abort, harness-input, harness-attachments-change, harness-attachment-error, harness-account-change, harness-model-change, harness-mode-change, harness-permission-response, harness-question-response, harness-subtask-open, harness-scratchpad-save, harness-session-intelligence-ask.
Set .todosAuthoritative=${true} when an empty host-owned task list must suppress transcript-derived fallback. A changed transcriptKey also clears the previous session's unsent Session Intelligence question while preserving the existing scratchpad revision handling.
Session Intelligence availability is host-controlled through intelligenceAvailabilityStatus (THarnessIntelligenceAvailabilityStatus, exported as 'checking' | 'available' | 'unavailable'), intelligenceUnavailableReason, and intelligenceHeading. Their compatibility defaults are available, an empty reason, and Session Intelligence. A blank heading also falls back to Session Intelligence, and unavailable state without a reason renders <heading> is unavailable. sessionIntelligenceEnabled remains the visibility switch. Checking and unavailable states keep exchange history visible and disable only the intelligence composer, while the main chat composer remains usable. Availability copy is rendered as text in a polite live status region; intelligenceError remains the separate operation-error channel.
History paging adds the composed child event harness-load-earlier.
DeesHarnessMessageList
Scrollable streaming-safe log. Up to 40 canonical entries (messages + permissions + questions) keep keyed full-timeline DOM; larger inputs activate a measured variable-height window with 800px overscan and return to full rendering when they shrink. Rows outside that overscanned window are unmounted, except focused rows, both endpoints of an active selection, and fullscreen-owned cards, which remain mounted until the interaction ends; threshold-mode changes are likewise deferred while protected DOM is active. applyDelta() targets a mounted message or mutates its offscreen backing model. Markdown output, disclosures, reasoning expansion, question drafts, permission choices, diff layout, terminal scroll state, and live nested-transcript state restore from retained data after an actual eviction. Hosts may pass a stable mutable IHarnessTranscriptViewState through viewState; it must contain messages, permissions, and questions maps. The list creates entries for current canonical IDs, deletes records for removed IDs, and session-keys and prunes live nested-subtask records. Without that prop, equivalent state remains list-local. Set transcriptKey to the stable identity of the displayed transcript so reused components reset scroll-local state between chats. When hasEarlierMessages is true, an intentional upward gesture reaching the top emits one bubbling, composed harness-load-earlier event. Set loadingEarlier while handling it; the next true to false transition rearms the event. Initial rendering, anchor restoration, and other component-owned scrolling never emit it.
refreshMessages(ids?) reconciles in-place host mutations without replacing canonical objects. With IDs, it refreshes only matching message rows that are currently mounted; matching offscreen rows remain unmounted and use the mutated canonical objects when later mounted. With IDs omitted, it rebuilds structural transcript state and refreshes all mounted rows. [] is a no-op.
Producers with authoritative source chronology can set IHarnessMessage.order to { messageIndex, partIndex }; explicitly ordered messages retain that relative order while timestamps continue to place legacy messages and ancillary cards. Consecutive tool calls use one generic responsive-grid pipeline at their chronological position, partitioned by descriptor layout: compact tools share rows with compact tools, subagents share rows only with subagents, and terminal plus file-write cards span a whole row. Consecutive same-layout runs are always capped at four cards per group row, even when their tool kinds differ. Columns respond to transcript content width rather than the viewport, and status changes never regroup cards. Untouched projected subtask cards collapse individually on completion with the normal disclosure animation; other cards retain their descriptor or user-selected disclosure state. Auto-follow responds to streamed growth but does not repeatedly repin while a disclosure shrinks. Permission requests, questions, and nested-stream limits behave as before. Props: messages, permissions/permissionsBusy, questions, status, viewState, transcriptKey, hasEarlierMessages, loadingEarlier, autoFollow, hideScrollbar, fadeScrollEdges, showRoles, showTimestamps, allowSubtaskStreams, markdownWhileStreaming (default true), emptyTitle/emptyText (+ slot="empty"). Native browser find and selection cover the mounted window; hosts that require complete-history search or copy should operate on their canonical messages, permissions, and questions models.
DeesHarnessMessage
One message: role accent, markdown (assistant default, message.markdown overrides), attachments grid, reasoning parts, streaming cursor, usage/error footer. Markdown renders through domtools.plugins.smartmarkdown (GFM, sanitized). markdownWhileStreaming defaults to true, with throttled ≥150 ms re-parses while streaming; set it to false to retain plain text during the stream and parse once when the message ends. Fenced code is highlighted lazily after the final parse. Streaming methods: appendTextDelta(), appendReasoningDelta(), endReasoning(), endStream(usage?, error?). Direct consumers may set allowSubtaskStreams false to suppress nested previews. Subtask drill-in emits the composed harness-subtask-open event with { sessionId, call }.
refreshMessage() reconciles direct same-object host mutations across text, reasoning, and tool content.
DeesHarnessReasoning
"Thinking" disclosure for one IHarnessReasoningPart. Auto-opens while streaming (no endedAt); the user's expand/collapse choice wins afterwards. Shows duration once finished.
DeesHarnessToolCard
Tool-call card: shared chrome (icon, label, subtitle, status pill, duration, MCP badges) with a per-kind body. Kind resolution goes through DeesHarnessToolRegistry (see below); bodies for the seeded kinds — terminal, file-read, file-write (renders a before/after diff via dees-dataview-codebox when the input carries previous content), file-delete, dir-list, browser (inline screenshots), http, json, search, subtask, project-task, todo, mcp, unknown — live in renderers.ts. File-write cards retain the user's inline/split choice while the same call updates. Numeric patch hunks preserve their source line numbers; patches and replacement snippets without coordinates hide gutters rather than presenting fabricated positions. Overflowing subtitles use a 40px right-edge fade and reveal the full line on hover plus once after a two-second initial delay. Expand/collapse always uses a smooth disclosure transition. Initially closed bodies mount lazily on first expansion, then retain the same DOM and become inert plus aria-hidden while closed. Long outputs truncate at 6,000 chars with a "Show all" expander. Terminal and error cards open by default. Terminal output uses a black inner log surface and follows the live tail after reaching the card-height cap. Scrolling upward detaches the tail; leaving the card returns to the bottom after five seconds, while re-entering cancels that pending return. Terminal calls may provide exitCode separately when output is one combined process stream; the renderer retains support for { stdout, stderr, exitCode } output objects.
After mutating a canonical tool call in place, call refreshCall(). It refreshes the mounted card, any mounted nested subtask transcript, and any open fullscreen card while retaining the same canonical call and nested-message object references; it does not clone or replace them.
Subtask calls may carry subtask: IHarnessSubtaskStream. While a projected child is busy, its Agent Brief is the leading item inside the same independently scrolling child transcript as its messages, permissions, questions, and status; no extra pinned or boxed brief sits above it. Dynamic 40px edge fades appear at the top only when content exists above and at the bottom only when content exists below, while wheel/touch scrolling remains available without visible scrollbars. Loading and unavailable projections reserve the same stable space. Once the child becomes idle or errored, the live transcript is removed from the card body and the disclosure cleanly renders Agent Brief followed by a scrollbar-free scrolling Result with the same position-aware edge fades; the full transcript remains available through the final bottom-positioned Open subagent chat action. subtask.sessionId is authoritative for drill-in when a projection exists; calls without a projection continue to use the existing childSessionId field. Authoritatively busy previews open by default and every expanded preview is bounded by --dees-harness-active-subtask-height (default 320px), including its completion transition. The user's local expand/collapse choice wins. Completed subtask cards stay in their subtask-only chronological grid and untouched cards collapse individually without a grid-level summary. Hidden retained previews pause auto-follow observation without unmounting their DOM and catch up to the newest child content when reopened. markdownWhileStreaming defaults to true and is forwarded to the nested child transcript; direct tool-card hosts can set it to false for the same parse-on-completion policy. Set allowSubtaskStreams false on a direct tool-card consumer to suppress the preview. The host remains responsible for hydration, transfer limits, authorization, gap recovery, and updating the shared objects; truncated projections visibly disclose omitted earlier messages.
delegateMessage.toolCall = {
id: 'delegate-call-1',
name: 'delegate',
status: 'running',
subtask: {
sessionId: 'child-session-1',
previewState: 'ready',
status: { type: 'busy' },
messages: childMessages,
permissions: childPermissions,
questions: childQuestions,
},
};
import { DeesHarnessToolRegistry } from '@design.estate/dees-catalog';
DeesHarnessToolRegistry.default.register({
kind: 'deploy',
names: ['deploy_service'],
label: 'Deploy',
icon: 'lucide:Rocket',
subtitle: (call) => String((call.input as any)?.service ?? ''),
renderBody: (call, helpers) => helpers.keyValues([['service', String((call.input as any)?.service)]]),
});
Custom tool renderers receive IHarnessToolRenderHelpers. Its diffBlock(before, after, options) helper accepts language, filename, and view, plus unifiedDiff for a positioned patch and showLineNumbers to suppress untrustworthy gutters. The default view is 'auto': inline below 640px of codebox width and split at 640px or wider. An explicit helper view remains fixed. User choices override the helper default and retain the existing transcript view-state persistence.
server__tool names resolve automatically to the MCP kind with server + annotation badges (readOnlyHint → success, destructiveHint → error, openWorldHint → warning). Per-element isolation: assign a forked registry to .toolRegistry.
Descriptors may set layout: 'compact' | 'full-row' | 'subtask' to control transcript grid packing; omitted custom descriptors default to compact. The built-in terminal and file-write descriptors are full-row, while delegate, subagent, and task calls with child-session metadata use the isolated subtask lane. A task call without child-session metadata uses compact project-task presentation only when its input has a list, create, update, delete, or clear action; other task calls remain subtasks for OpenCode compatibility. Project tasks prefer authoritative { tasks }, { task }, or { state: { tasks } } output. pending, in_progress, completed, and cancelled statuses use task-list presentation; other statuses remain visible as raw output instead of being normalized. todowrite, todo_write, and todoread retain todo presentation.
Live tool cards show activity through a small icon opacity pulse while pending or running; the card surface stays still. When the same call reaches a terminal state, a brief green success scan or red error scan marks the outcome. Historical calls rendered as already complete do not replay it. Reduced-motion preferences disable these animations. Session rows use the same small activity indicator and preserve their geometry when selected.
DeesHarnessOverflowText
Reusable single-line harness text for constrained headers and activity rows. Slotted text is measured against the component's actual flex/grid width. Only true overflow receives the 40px right-edge fade; short text remains fully opaque. Overflowing text reveals its complete line on hover and once after the initial two-second delay, with reduced-motion preferences respected. Props: fadeWidthPx (default 40) and introDelayMs (default 2000).
DeesHarnessContentBlocks
Renders MCP CallToolResult content structurally: text, base64 images, audio with controls, embedded text resources, downloadable blob resources, and resource_link cards.
DeesHarnessPermissionCard
Permission prompt: title, type badge (shell/delete → error tone, write/browser → warning), metadata key/values, Allow Once / Always Allow / Reject, and a "remember" checkbox (always implies remember). Emits harness-permission-response; disables itself once answered or while busy.
Inside the chat, permission cards render inline with the messages: pending requests pin to the end of the transcript (action required), and answered ones freeze at their response time in the flow. Set response/respondedAt on the request after handling it and keep it in the array — the card stays in the transcript showing what was granted or rejected.
DeesHarnessQuestionCard
A question the agent asks mid-run: option buttons with descriptions (single-select answers on click; multiSelect collects then confirms) plus an optional free-text answer (allowCustom, default on — Enter or "Send Answer" submits, combining selections with the text). Emits harness-question-response with { requestId, answers, request }. Questions flow through dees-harness-chat's questions property and render inline like permissions: pending ones pin to the end of the transcript, and answered ones persist showing the chosen answers once the host sets response/respondedAt.
DeesHarnessComposer
Place optional reply or task context in slot="context" to integrate it above the textarea inside one input surface. Set contextDescription to the same context as plain text for the textarea's accessible description. An empty slot reserves no space. Clicking informational context focuses the prompt; links and controls retain their own behavior. DeesHarnessChat supplies its reply heading here and keeps the composer within the conversation column, alongside the full-height desktop sidebar.
Prompt input: autogrow textarea, attachment picker/paste/drag-drop with configurable maxAttachmentCount, maxAttachmentBytes (10 MB default), and maxTotalAttachmentBytes (30 MB default), image/text/binary classification, optional account, model, and reasoning-effort dropdowns, Cmd/Ctrl+Enter to send. Attachment count is unlimited unless the host sets it. dees-harness-chat forwards all three limits and suggestions to its composer. Controlled component — the host owns value, attachments, suggestions, account, and the other selections. Set accountOptions to { label, value }[] so the dropdown displays readable labels while events retain opaque stable account values. Account changes emit harness-account-change with { account }; harness-send includes optional account alongside model and reasoningEffort.
Set suggestions to IHarnessComposerSuggestion[], where each item is { label, value, description? }. The composer does not filter or generate suggestions: listen for harness-input, update the controlled value, and provide a fresh filtered array. While the textarea owns focus, the listbox stays anchored above the composer; moving focus to another control closes it. Arrow keys wrap through rows, Enter chooses the active row, and Escape dismisses the current list until the next text input or fresh suggestions array. Choosing a row sets value to the suggestion's value and emits the existing bubbling/composed harness-input event; it does not emit harness-send.
While busy, Stop renders as an additional button and Send stays available so hosts can queue messages. Set queueingEnabled to label busy sends Queue next. Set inputSteeringEnabled to add a separate Steer now button that emits harness-steer-input with the same IHarnessComposerSendDetail as harness-send; it requires a nonempty draft. The host binds that input to its active turn and owns acknowledgment and clearing the draft. Both capabilities default false and are forwarded by dees-harness-chat.
A positive queuedCount remains visible independently of steering support. The existing queue-flush action remains available when inputSteeringEnabled is false: with an empty prompt, queuedCount > 0, and steeringEnabled true, the primary action becomes Steer now (harness-steer with { queuedCount }). Hosts implementing input steering should set steeringEnabled false.
Tool calls accept terminal status stopped for confirmed interruptions. Stopped cards use a warning tone, stop their running animation, and enter activity history without being treated as errors. Terminal tool aliases include command; file-write aliases include fileChange and file_change. File writes can provide { changes: [{ path, diff }] } to render each file's unified diff.
Set modeOptions (e.g. ['Planning', 'Building']) to render a segmented mode switcher; the active mode changes on click — or Shift+Tab inside the prompt cycles it — and emits harness-mode-change. Inside dees-harness-chat the composer renders docked — flush edge-to-edge with a top border and an upward shadow (--dees-shadow-up-sm) so it meets the transcript and the session panel like the toolbar does; standalone composers keep the floating-card chrome.
DeesHarnessTodos
Agent task list for IHarnessTodoItem[] — completed items dim with a check and strikethrough, cancelled items use a muted X and strikethrough, the in-progress item leads with a pulsing accent marker, and an optional heading shows completed/total progress. Used automatically as the todo tool-card body (harnessParseTodos normalizes tool input shapes) and standalone for a "current tasks" sidebar panel fed from the latest todowrite call.
DeesHarnessStatus
Idle/busy/error line with animated dots and aria-live="polite". Renders nothing when idle unless showIdle.
DeesHarnessUsage
Token usage chip (in · out · total, compact shows the total only); cache read/write detail in the tooltip.
DeesHarnessSessionSidebar
Generic session-detail panel that composes Tasks, Tools & Subagents, Tokens, Session Scratchpad, and Session Intelligence sections. Headings stay fixed in a single-open accordion; Session Intelligence splits its expanded body into independently scrolling history and a docked question composer. Set .toolMessages to top-level tool messages from the currently retained transcript window and .toolRegistry for matching labels/icons; active subagents use authoritative child status, and removed messages disappear without a second history cache. The host also supplies IHarnessSessionMetrics, IHarnessScratchpad, and IHarnessIntelligenceExchange[]; the component never loads or persists data itself. Context percentage and its progress bar render only when both currentContextTokens and a positive maxContextTokens are present.
Scratchpad edits emit harness-scratchpad-save with { text, expectedRevision }. A newer incoming revision preserves a dirty local draft and offers explicit Discard draft and Keep draft on latest actions before saving can continue. Intelligence questions emit harness-session-intelligence-ask with { question }; exchanges and busy/error states remain host-controlled. Direct sidebar hosts should update sessionKey when changing sessions; DeesHarnessChat forwards transcriptKey automatically.
Direct sidebar hosts can set intelligenceAvailabilityStatus, intelligenceUnavailableReason, and intelligenceHeading; intelligenceEnabled still controls whether the section exists. The properties default to available, an empty reason, and Session Intelligence. A blank heading falls back to Session Intelligence, and unavailable state without a reason renders <heading> is unavailable. Only available permits harness-session-intelligence-ask; checking and unavailable states retain history and expose accessible status text. The availability union is exported as THarnessIntelligenceAvailabilityStatus.
DeesHarnessSessionList
Searchable, card-based conversation column for sidebars or a DeesModal (harness-session-select on click, harness-session-open on double-click/Enter). Cards stay collapsed until their independent accessible expansion control is used. Selection and working-state changes do not affect expansion.
Each card exposes an Actions button. Actions, right-click, and ContextMenu/Shift+F10 on the primary or Actions control emit harness-session-context with the unchanged IHarnessSessionContextDetail (session, viewport clientX/clientY, and originalEvent: MouseEvent). Button and keyboard requests anchor the menu below the invoking control; pointer requests keep the pointer coordinates. Actions do not select, open, expand, or drag the session. Hosts own the menu choices and focus restoration. Call DeesContextmenu.openContextMenuWithOptions(detail.originalEvent, items) synchronously in the event handler, before any await, so the event's composed path retains the owning dialog. The session demo shows menu cleanup and focus restoration through registerGarbageFunction().
IHarnessSessionMeta.state accepts normal, working, finished, attention, or error. Every collapsed row shows its status icon and text. Optional statusLabel refines the description (for example, Starting, Waiting for feedback, or Approval needed); blank labels fall back to the standard state name. Optional harness: IHarnessAgentIdentity supplies { id, name, icon? }, independently of the model and state. The harness name stays on its own metadata line; omitted identity renders no guessed provider. Search includes harness names/ids and status labels.
Attention and error rows retain a colored edge even when selected. Group headings count both states, including when collapsed. working controls the small activity pulse on normal/working rows, defaulting to state === 'working'; attention, error and finished indicators remain stationary. Reduced-motion preferences disable the pulse. State changes preserve row height, selection, expansion and host-provided order.
With enableGrouping, sortable cards can move within and between groups. Drag from a card's focusable grip, or use its ArrowUp, ArrowDown, Home, and End keys. Pointer dragging moves a full-card preview, opens a matching drop slot, and animates surrounding cards; harness-session-move includes both the compatibility index and a stable optional beforeSessionId anchor. Set draggable: false for pinned entries such as terminals. Pass ungroupedSessionIds to control the order outside groups; omitted or unknown entries fall back to recency order.
The compact New group button sits beside search whenever enableGrouping is true, including while filtering, and emits harness-group-create-request. The host owns creation; the list keeps its current search until the host chooses to clear it. The demo clears search after adding the group. Both search and the group action use 44px targets for coarse pointers.
The Sessions demo pairs the list with a selected-session workspace. Its controls step through starting, working, feedback, approval, completion and failure without timers or backend operations. Question and permission responses update the selected session; declining approval pauses it. The narrow layout switches between the preserved list and detail panes with an All sessions control that restores row focus.
Generic host resources use the exported IHarnessResourceMeta and IHarnessResourceAssociation contracts. Set .resources, .resourceAssociations, .selectedResourceId, and .eligibleSessionIdsByResourceId. Unattached resources render in a labelled Resources list; attached resources render as compact persistent child rows inside their exact session article and stay visible while session details are collapsed. Resource metadata is presentation-only: the list never mounts a terminal, browser, file viewer, or other runtime.
import type {
IHarnessResourceContextDetail,
THarnessResourceAssociationRequestDetail,
} from '@design.estate/dees-catalog';
const list = document.querySelector('dees-harness-session-list')!;
list.resources = [
{ id: 'shell:build', kind: 'terminal', title: 'Build shell', icon: 'lucide:Terminal' },
{ id: 'file:notes', kind: 'file', title: 'Release notes', preview: '6.7.0 draft' },
];
list.resourceAssociations = [
{ resourceId: 'shell:build', sessionId: 'session-1' },
];
list.eligibleSessionIdsByResourceId = {
'shell:build': ['session-1', 'session-2'],
'file:notes': ['session-2'],
};
list.addEventListener('harness-resource-association-request', async (event) => {
const resourceEvent = event as CustomEvent<THarnessResourceAssociationRequestDetail>;
const { resource, toSessionId } = resourceEvent.detail;
await persistAssociation(resource.id, toSessionId);
list.resourceAssociations = await loadAssociations();
});
list.addEventListener('harness-resource-context', (event) => {
const resourceEvent = event as CustomEvent<IHarnessResourceContextDetail>;
openHostMenu({
x: resourceEvent.detail.clientX,
y: resourceEvent.detail.clientY,
attach: (sessionId) => resourceEvent.detail.requestAssociation(sessionId),
detach: () => resourceEvent.detail.requestAssociation(null),
onClose: resourceEvent.detail.close,
});
});
harness-resource-select and harness-resource-open carry { resource, sessionId }. harness-resource-context adds viewport coordinates, the pointer/Actions/keyboard source, the original event, requestAssociation(toSessionId), and close() for focus restoration. The visible Actions button has menu disclosure semantics; native ContextMenu and Shift+F10 invoke the same host-owned path. The component does not require DeesContextmenu and does not own menu choices.
All component-originated association request paths converge on the public requestResourceAssociation(resourceId, toSessionId, source?) method. A valid change emits one bubbling/composed harness-resource-association-request discriminated by intent: 'attach' | 'detach' | 'reassign' and carrying resource, fromSessionId, toSessionId, and source: 'drop' | 'action' | 'keyboard'. The method returns false and emits nothing for unknown resources/sessions, ineligible targets, detach from an unattached resource, or the current target. It never mutates resources, associations, selection, session groups, or session order.
Association projection is deterministic and non-destructive: resource records without a non-empty ID, kind, or title, unknown resource/session references, and later duplicate records for a resource are ignored; the first valid association wins. Duplicate resource metadata IDs likewise use the first resource. Missing eligibility entries mean no eligible attach/reassign target. Eligibility does not hide an existing authoritative association, which remains visible and detachable. Requested changes are announced separately from completion; completion is announced only after the controlled association input reflects the requested target.
Search covers resource ID, kind, title, and preview. A matching attached resource reveals its parent session and only matching children; a matching session reveals all its resources; matching unattached resources remain in Resources. Resources render with zero sessions. Resource grip dragging edge-scrolls the list and targets only eligible whole session cards, including hits on nested card content, while never creating session reorder placeholders or emitting harness-session-move. Use the Actions/context path for keyboard and mobile association workflows; coarse-pointer grip and Actions targets are at least 44px.
Pre-built Templates
DeesSimpleAppDash
Compact application shell with grouped navigation, global notices, a content scroller
and an optional terminal. Use this for a small collection of views; choose
dees-appui for screen URLs, window menus, secondary navigation and inspectors.
<dees-simple-appdash
name="Studio"
.viewTabs=${[
{ name: 'Overview', iconName: 'lucide:layoutGrid', element: StudioOverview },
{ name: 'Settings', iconName: 'lucide:settings', subViews: [
{ name: 'Profile', iconName: 'lucide:contact', element: StudioProfile }
] }
]}
@view-select=${(event) => showViewState(event.detail.view)}
@logout=${signOut}
></dees-simple-appdash>
Each element is a registered DeesElement constructor. Give the shell a bounded
height. A group without its own element opens its first navigable child. loadView(view)
opens a view; selecting the current view preserves its instance and unsaved edits.
Switching to another view creates that view anew, so keep shared application state outside
the view instances, as the Studio demo does. Replacing viewTabs removes a selected view
that no longer belongs to the collection.
Navigation supports Tab, Enter/Space and Up/Down/Home/End. collapsed selects an icon rail
with speechbubble labels. Containers below 640px use the rail automatically.
Supply globalMessages: IGlobalMessage[], or call addMessage({ id?, type, message, dismissible?, icon?, actions? }). Types are info, success, warning, and error;
actions have { name, iconName?, action }. addMessage returns the ID and replaces an
existing message with the same ID. Property updates synchronize their messages while
preserving API-added notices. removeMessage(id) emits message-dismiss with { id }
once; clearMessages() clears the stack. Notices wrap and scroll independently instead
of covering the content.
launchTerminal() toggles the embedded terminal. Pass a consumer-owned
executionEnvironment: IExecutionEnvironment and optional terminalSetupCommand.
The shell forwards the environment and removes its terminal on disconnect; it does not
destroy a shared runtime. WebContainerEnvironment requires a secure, cross-origin
isolated browser context. The Studio demo supplies it without booting until Terminal opens.
The interactive Studio demo connects project archiving, overview counts, activity, workspace name, notification preferences, notices, and sign-out in one sample app.
DeesSimpleLogin
Login surface for an application shell. Offers up to three authentication methods in one card — passkeys, identity providers ("sign in with …") and a username/password form — and renders the authenticated app through its default slot.
A method is offered only when it is configured, so the zero-configuration default is the password form alone:
<dees-simple-login name="My Application" @login=${handleLogin}>
<!-- authenticated content -->
</dees-simple-login>
login fires with detail.data.{username,password}; call switchToSlottedContent() once
credentials check out.
Turn on more methods and they render in the canonical order passkey → provider → password:
<dees-simple-login
name="login.idp.global"
.passkey=${true}
.providers=${[
{ id: 'idp-global', label: 'idp.global', icon: 'lucide:shieldCheck' },
{ id: 'workspace', label: 'Workspace SSO', icon: 'lucide:building2' },
]}
.passkeyLoginHandler=${async (context) => runWebAuthnCeremony(context)}
.providerLoginHandler=${async (context) => redirectTo(context.providerId)}
@login=${handleLogin}
></dees-simple-login>
Configuration — passkey (default false), providers (default []), password
(default true), passkeyIntents (['authenticate'], add 'register' to offer
enrollment), methodOrder, labels, passkeyAvailable (override capability detection),
passkeyAutofill (WebAuthn conditional mediation — inert unless passkeyLoginHandler is set,
the password method renders a username field, and the browser reports conditional mediation).
Who owns the WebAuthn ceremony — not this component. A ceremony needs server-issued options and server-side verification, so the catalog carries no WebAuthn dependency. Plug one in either way, never both for the same interaction:
- Event mode (no handler set) — the component dispatches
passkey-login,passkey-registerorprovider-loginand stops. Report progress back withsetBusy()/reportError(). - Handler mode (
passkeyLoginHandler,passkeyRegisterHandler,providerLoginHandler,passwordLoginHandler) — the component awaits the handler, owns that method's busy state and turns a rejection into that method's error message. The request event is not dispatched, so a ceremony can never start twice.
login is a notification rather than a request and always fires, even in handler mode.
Busy and error state is independent per method, per passkey intent and per provider, so a
failed passkey attempt never blanks the password form. reportError(target, message),
setBusy(target, busy), isBusy(target) and getError(target) each take either a
TDeesLoginMethod or an IDeesLoginTarget ({ method, intent?, providerId? }).
clearError(target?) takes an optional target and clears every error when called without
one; reset() takes no arguments and clears all busy and error state plus the password form.
Provider icons are always consumer-supplied Lucide names — the catalog ships no third-party brand assets.
The optional brand slot accepts an application mark; without it a neutral sign-in glyph
is shown. All methods share one framed surface, with a divider between button methods
and credentials. The password-only tile remains available through the existing shadow
DOM handles. .loginContainer, .login, .slotContainer, the password form, its keys,
and externally managed submit status retain their existing contracts.
The sign-in demo offers method configurations, success/error responses and a sign-out
round trip. Real consumers should move focus into the authenticated view after awaiting
switchToSlottedContent() and back into the login after signing out.
Shopping Components
DeesShoppingProductcard
Product image, title, description, stock label and price in one surface, with optional
quantity and selection controls. Reuse this component before assembling a custom product
tile. imageUrl is contained without cropping; iconName supplies the fallback artwork.
<dees-shopping-productcard
.productData=${{
name: 'Premium Headphones',
category: 'Electronics',
description: 'High-quality wireless headphones with noise cancellation',
price: 199.99,
originalPrice: 249.99,
currency: '$',
inStock: true,
imageUrl: '/images/headphones.jpg'
}}
.quantity=${1}
.showQuantitySelector=${true}
.selectable=${true}
.selected=${false}
@quantityChange=${handleQuantityChange}
@selectionChange=${handleSelectionChange}
></dees-shopping-productcard>
quantityChange carries { quantity, productData }; selectionChange carries
{ selected, productData }. Both bubble across shadow roots. The checkbox is keyboard
accessible and clicking the rest of a selectable card toggles it; using the quantity
buttons never changes selection. External property assignments do not emit user events.
inStock and stockText describe availability. Cart totals, inventory limits and purchase
policy belong to the consumer; omit the selector with showQuantitySelector=false for
unavailable products. The work-kit demo demonstrates this, restocking, shared bag totals,
and selecting a kit with dees-input-multitoggle. Its Review bag action places no order.
TypeScript Interfaces
The library exports unified interfaces for consistent API patterns:
// Base menu item interface (used by tabs, menus, etc.)
interface IMenuItem {
key: string;
iconName?: string;
action: () => void;
badge?: string | number;
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
closeable?: boolean;
onClose?: () => void;
}
// Menu group interface for organized menus
interface IMenuGroup {
name: string;
items: IMenuItem[];
collapsed?: boolean;
iconName?: string;
}
// View definition for app navigation
interface IViewDefinition {
id: string;
name: string;
iconName?: string;
content: string | (new () => HTMLElement) | (() => TemplateResult) | (() => Promise<any>);
secondaryMenu?: ISecondaryMenuGroup[];
contentTabs?: IMenuItem[];
route?: string;
badge?: string | number;
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
cache?: boolean;
}
// Activity log entry
interface IActivityEntry {
id?: string;
timestamp?: Date;
type: 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom';
user: string;
message: string;
iconName?: string;
data?: Record<string, unknown>;
}
// Bottom bar widget
interface IBottomBarWidget {
id: string;
iconName?: string;
label?: string;
status?: 'idle' | 'active' | 'success' | 'warning' | 'error';
tooltip?: string;
loading?: boolean;
onClick?: () => void;
position?: 'left' | 'right';
order?: number;
}
// Bottom bar action button
interface IBottomBarAction {
id: string;
iconName: string;
tooltip?: string;
onClick: () => void | Promise<void>;
disabled?: boolean;
position?: 'left' | 'right';
}
// View activation context (passed to onActivate)
interface IViewActivationContext {
appui: DeesAppui;
viewId: string;
params?: Record<string, string>;
}
// Thumbnail folder item (for DeesThumbnailFolder)
interface IThumbnailFolderItem {
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
thumbnailSrc?: string;
name: string;
}
Third-Party Notices
DeesChartArea uses TradingView Lightweight Charts under the Apache License 2.0. The built-in chart logo is disabled. The distributed third-party notices retain the upstream NOTICE and Apache license text.
TradingView Lightweight Charts™
Copyright (с) 2025 TradingView, Inc. https://www.tradingview.com/
DeesPdfViewer and DeesThumbnailPdf bundle Mozilla PDF.js 4.10.38 and its
module worker under the Apache License 2.0. The attribution and complete license text are
provided in third-party-notices.md and copied into
dist_bundle/ for standalone bundle distributions.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.