2026-09-08 08:10:03 +00:00
2026-09-08 08:10:03 +00:00
2026-09-08 08:10:03 +00:00
2026-09-08 08:10:03 +00:00

@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

  1. Find the task above and search the tag index below. Read the linked API and the actual implementation; similarly named controls have different contracts.
  2. Open its catalog demo with pnpm run watch (the running workspace uses port 3002). Check selection, events, form participation, keyboard behavior and sizing.
  3. 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.
  4. 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

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 H1H3
  • 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 ObservablesviewChanged$ and viewLifecycle$ for reactive programming
  • 🏷️ TypeScript-first — Typed IViewActivationContext passed 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>`
  • heading1 is the title, heading2 its description, and label a compact alternative. Headings default to empty. The footer reports visible, total, and selected rows; filtered-out selections remain counted when their IDs still exist in data.
  • Columns accept align: 'start' | 'center' | 'end' (default start). End-aligned values use tabular numerals. Alignment is explicit and never inferred from formatted content. A displayFunction can supply columns, or augment a partial schema with augmentFromDisplayFunction.
  • Quiet row separators are the default. Set showGrid, showVerticalLines, or showHorizontalLines for stronger grid lines.
  • Search stays visible when searchable is true. Its menu selects searchMode: table searches visible column values, data searches original row values, and server emits searchRequest with { query, mode: 'server' }. The application owns server results; filterText is not applied again in that mode. Local search supports Lucene-like syntax and emits filterChange with { text, columns }. showColumnFilters enables per-column inputs; a column can opt out with filterable: false.
  • Native header buttons sort and expose aria-sort. Shift-click builds a multi-column sort; the header context menu controls sort priority. sortBy and sortChange expose the descriptors.
  • selectionMode accepts none, single, or multi. Use a stable rowKey to retain selection and editing through fresh data arrays. showSelectionCheckbox includes select-all for visible rows.
  • Action type accepts header, inRow, contextmenu, footer, doubleClick, preview, and keyCombination. Use actionRelevancyCheckFunc for 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.
  • fixedHeight uses internal scrolling with sticky headers and --table-max-height; the default uses a floating header while the page scrolls. virtualized limits rendered rows and retains spacer geometry.
  • Set a column's editor to text, number, checkbox, dropdown, date, or tags; editable: true defaults to text. Use editorOptions, format, parse, and validate for value handling. Double-click edits a cell; Enter/Tab commit and move, Escape cancels. cellEdit reports { row, key, oldValue, newValue }; cellEditError reports 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, unknown types

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 }).

  • IStep accepts title, optional description, content, menuOptions, allowBack, validationFunc, onReturnToStepFunc, and progressStep.
  • The last menu action is primary. A nested dees-form gates that action on its required fields and dispatches collected formData before 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's AbortSignal.
  • Progress steps use dees-progressbar and advance after successful validation unless progressStep.autoAdvance is false. Use updateProgressStep(), appendProgressStepLine(), and resetProgressStep() to report work.
  • goNext() and goBack() preserve mounted form contents. Back honors allowBack: 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: false disables those dismissal paths. destroy() closes the flow; stepper-close fires 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-width controls 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>

Configurationpasskey (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-register or provider-login and stops. Report progress back with setBusy() / 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.

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.

S
Description
components for building sane web applications
Readme
24 MiB
Languages
TypeScript 99.8%
JavaScript 0.2%