@design.estate/dees-catalog
v23.0.0
Published
A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.
Maintainers
Keywords
Readme
@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-button with pressed, dees-input-toggle | A toolbar or app-bar command that stays on uses a button with pressed; a labelled setting uses the switch. 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. Routes by hash, or by path beside the host's server routes. |
| Give a view its page header: title, summary, actions, tabs | dees-appui-page | One max-width and padding for every view; the primary action at the end of the title row. Do not hand-code headers. |
| Head a view with its title, summary and actions above content that scrolls on its own | dees-appui-contentheading, or contentHeading in the view definition | A title with its badges, a line of facts joined with " · ", the view's tools and actions at the end of the row and a hairline across the content area; it stays while the view scrolls. dees-appui-page draws its header with it. |
| Report a page's warnings and errors from its header instead of stacking hints above its content | dees-statusbutton | One button beside the page's actions shows the worst status and a count; its speech-bubble panel lists the statuses, and an error or a severe warning opens it by itself. Keep dees-hint for a note next to the thing it explains. |
| Say that a tile, panel or list is loading, empty or failed | dees-state | One pattern for all three: icon or spinner, sentence, the way on, Retry and folded technical detail. A dees-table says the same through loading, error and 'empty' actions. |
| 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. |
| Reach any screen, menu item or host command by name | dees-appui-commandpalette | The shell opens it with the platform modifier and K and with the app bar's search button. Host commands come from IAppConfig.commands / setCommands(). |
| 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. |
| Choose, photograph or drop files, and show each file's upload | dees-input-fileupload, dees-dropzone-overlay | The field selects files (capture opens a phone camera) and shows the upload state the host reports with setFileState(); the overlay receives files dropped anywhere in the window. |
| Edit multiline plain text | dees-input-textarea | Notes and descriptions as strings, with validation and form reset; no rich-text editor. |
| Track epics, tasks and subtasks across workflow columns | dees-kanban | Board, ordering, projects, filters, ticket detail with links, source and history, discussion, live refresh and stable ticket references. |
| Show a conversation about a record: comments by people and agents, what happened, a reply | dees-thread | Timeline of comments and events, Markdown composer with close/reopen actions, pending and refused posts, read-only and failed states. The host owns posting and adds the posted entry. |
| Show who wrote, did or owns something: a picture, else initials; an agent apart from a person | dees-avatar | Round for a person, a rounded square with its own glyph for an agent or the system; a picture that fails falls back to the initials. decorative beside a visible name. |
| Compose comments with searchable inline references | dees-input-richtext searchReferences, dees-input-reference | In Markdown, # at the start of a word searches the host's records at the caret and links the one chosen; dees-input-reference writes plain text with references, at #_. Both keep stable reference IDs. |
| 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. |
| Offer an action about what a tile shows: open its details, export it, add to it | dees-tile actions | Footer or header actions, drawn and held while they run exactly like a table's header and footer actions. Never a footer bar of buttons built by hand; actions on a table's rows or on the table itself are the table's own. See Where actions go. |
| Filter a view's rows: an account, a month, "Include private" | dees-table filters | Behind the filter button at the end of the table's search field, which counts the active filters and opens them in the speech bubble; each active one is a removable chip in the bar above the column headers. Never a row of dropdowns and checkboxes in the header. See Where actions go. |
| Put a table's search field in a page header, beside the page's own buttons | dees-table-search | The table's own field and filter button, linked to the table; the table hides its built-in field. Do not wire a plain input to setSearchQuery(). |
| 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 resource list to arrange conversations and resources, its session list for a read-only overview of sessions. |
| Ask for a focused decision or show contextual commands | dees-modal, dees-contextmenu | Modal task flow versus contextual action menu. Use existing overlay ownership. |
| Put the actions of a dialog, a speech bubble or a card of your own at its bottom edge | dees-actionfooter | Divider, tinted strip, the primary rightmost, one action at a time. A dialog's menuOptions and a bubble's actions are drawn by it already; never a row of buttons built by hand. Not the countdown question bar, which is dees-actionbar. |
| Show completion, progress or transient feedback | dees-stepper, dees-progressbar, dees-toast | Workflow stages, measurable progress, or a brief notification. |
Before adding a control
- Find the task above and search the tag index below. Read the linked API and the actual implementation; similarly named controls have different contracts.
- Open its catalog demo with
pnpm run watch(the running workspace uses port 3002). Check selection, events, form participation, keyboard behavior and sizing. - Compose the existing component. If it lacks a needed reusable capability, extend its owner and verify existing consumers instead of copying its markup and styles into each view.
- When adding or extending a component, update its task entry, tag/source index, API example and demo together. Keep internal anchor and source links valid.
Documentation map
- Installation and quick start
- Languages: the components' words in the reader's language
- Component index: exact tags, API sections and source files
- Component API: properties, examples and behavior
- TypeScript interfaces
- UI Components Playbook: library implementation patterns
- Third-party notices and license
Installation
The examples below import DeesElement directly, so install both packages:
pnpm add @design.estate/dees-catalog @design.estate/dees-element@^4.0.1The catalog runs on @design.estate/dees-element 4 and @design.estate/dees-domtools 5. A host
that depends on either package itself uses those majors (^4.0.1 and ^5.0.0); an older major
puts a second copy in its bundle.
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>
`;
}
}Entry points
| Import | What it is |
| --- | --- |
| @design.estate/dees-catalog | Every component, registered on import, with the types of their properties and events. |
| @design.estate/dees-catalog/icon | dees-icon alone (see DeesIcon). |
| @design.estate/dees-catalog/cdn | Where the catalog loads Monaco from, as constants, and monacoVsDirectoryUrl() for a server that serves Monaco itself. Imports no component, Lit or DOM API, so it runs in plain Node. |
| @design.estate/dees-catalog/richtext-markdown | The richtext editor's Markdown format without the editor: richtextMarkdownToDoc, richtextDocToMarkdown and richtextMarkdownRenderOptions, and its references: richtextReferenceHref, richtextReferenceId, richtextMarkdownToReferences and richtextReferencesToMarkdown. Imports no component, Lit or DOM API, so it runs in plain Node. |
Name a component's event and property types from the package root, never from a
dist_ts_web/... path:
import type { ICellEditDetail } from '@design.estate/dees-catalog';
table.addEventListener('cellEdit', (event: CustomEvent<ICellEditDetail<IInvoiceRow>>) => {
save(event.detail.row, event.detail.key, event.detail.newValue);
});The prebuilt bundle
dist_bundle/bundle.js is the whole catalog as one ES module, for a page that loads it without a
bundler of its own. It imports its shared code, and on first use ECharts, xterm.js, highlight.js,
Lightweight Charts, the rich-text editor and the PDF.js worker, from dist_bundle/chunks/ by
relative URL, so a server publishes the whole dist_bundle/ directory, chunks/ beside
bundle.js; bundle.js alone fails at its first import. The chunks come from the same origin,
so script-src 'self' covers them.
Content Security Policy
One library still loads from jsdelivr by default when it is first needed, through
DeesServiceLibLoader: Monaco, for the workspace editors. Everything else ships inside the
catalog: ECharts for the ECharts-based charts (with only the series and components they draw),
xterm.js and its addons for terminals, Lightweight Charts, and highlight.js with the languages
its code views name. Charts, terminals and code views therefore work under script-src 'self'.
A server whose CSP lists Monaco's CDN takes the URL from the cdn entry, which follows the
catalog's pin, instead of writing it by hand:
import { CDN_MONACO_BASE_URL } from '@design.estate/dees-catalog/cdn';
// a directory: the trailing slash makes each source match the files below it
const scriptSrc = ["'self'", `${CDN_MONACO_BASE_URL}/`].join(' ');
const styleSrc = ["'self'", `${CDN_MONACO_BASE_URL}/`].join(' ');
const connectSrc = ["'self'", `${CDN_MONACO_BASE_URL}/`].join(' ');
const workerSrc = 'blob:';Monaco is not one module: its loader.js and the modules it requires load from below
CDN_MONACO_BASE_URL (script-src). Its editor.main links editor/editor.main.css into
the document head (style-src), and the catalog reads the same file with fetch()
(connect-src) for the editors' shadow roots, once that link has loaded, from the browser's
cache: the stylesheet loads once. Under a style-src that blocks the link the editors still
get the stylesheet through fetch(), but Monaco's rules for the document itself (its
screen-reader live region) are missing. Monaco starts its language workers from blob: URLs
(worker-src), and each worker loads its script from below CDN_MONACO_BASE_URL under the
page's script-src. Without worker-src blob: the browser blocks the workers: the editors
still open, but without what the workers compute, such as JSON validation.
A host that serves Monaco from its own origin needs no CDN source at all; see Serving Monaco from your own origin.
CDN_SCRIPT_URLS and CDN_VERSIONS are deprecated, every entry of them: nothing loads those
URLs any more (xterm, ECharts and highlight.js are bundled; the echarts pin still names the
5.6.0 file the catalog once loaded, while the bundled ECharts is 6.x), so a CSP needs none of
them. They stay exported until the next major. A host that already ships xterm or highlight.js
hands its own in with provideXtermModules or provideHighlightJs, and the catalog uses it
instead of its bundled copy. The bundled highlight.js languages are Bash (also shell,
console), C, C++, C#, CSS, diff, Dockerfile, Go, GraphQL, HTML/XML, INI/TOML, Java,
JavaScript, JSON, Kotlin, Less, Markdown, PHP, Python, Ruby, Rust, SCSS, SQL, Swift,
TypeScript and YAML, under highlight.js's names and aliases; an unlabelled fence in a harness
message is detected among them, and code in another language is shown plain. The same
constants are exported from the package root, beside CDN_BASE and CDN_VERSIONS.
Serving Monaco from your own origin
A host whose CSP forbids CDN scripts, or that must work offline, serves Monaco's min/vs
directory itself and tells the catalog where, before the first editor mounts. The cdn entry
locates that directory, from the monaco-editor the catalog itself depends on, so the copy
matches the catalog's pin even when the host has no monaco-editor of its own:
// server, in Node
import { fileURLToPath } from 'node:url';
import { monacoVsDirectoryUrl } from '@design.estate/dees-catalog/cdn';
app.use('/assets/monaco/min/vs', express.static(fileURLToPath(monacoVsDirectoryUrl())));
// or copy it into the static files at build time:
// await fs.cp(monacoVsDirectoryUrl(), './public/assets/monaco/min/vs', { recursive: true });// page, before the first editor mounts
import { DeesServiceLibLoader } from '@design.estate/dees-catalog';
DeesServiceLibLoader.getInstance().provideMonacoBaseUrl('/assets/monaco/min/vs');monacoVsDirectoryUrl() is a file: URL ending in /. Monaco then loads its scripts, its
worker scripts and its stylesheet from the page's own origin, so 'self' covers them in
script-src, style-src and connect-src; its workers still start from blob: URLs, so the
CSP keeps worker-src blob:. The CDN stays the default for now; the next major serves Monaco
from the host's own origin by default.
Languages
The words the components say to a reader — button texts, names for assistive
technology, tooltips, empty states — come from one typed
@push.rocks/smarti18n catalog,
exported as dcMessages (namespace @design.estate/dees-catalog). English is the
source; German, with the formal "Sie", ships complete. A page provides its language
once, through provideI18n from @design.estate/dees-element, and every component
re-renders when it changes:
import { createI18n } from '@push.rocks/smarti18n';
import { provideI18n } from '@design.estate/dees-element';
import { dcMessages } from '@design.estate/dees-catalog';
const i18n = createI18n({ locale: 'de-DE', formats: { number: 'de-DE' } });
provideI18n(document.documentElement, i18n, { reflect: true }); // sets <html lang dir>
await i18n.setLocale('en-GB');
// a word of your own, for one language
i18n.override(dcMessages, 'de', { 'appui.skipLink': 'Direkt zum Inhalt' });Without a provider, the components speak English in neutral formats: numbers ungrouped (
5000), inside messages too, at most three fraction digits, file sizes inIntlunits (2 kB), and weeks from Monday. A component whose provider goes away returns to this.Modals, menus and toasts appended to
document.bodyfind the provider on<html>. An overlay opened from a subtree that speaks another language passes that language through the overlay helper'si18noption (DeesModal.createAndShow({ …, i18n }),DeesModal.confirm,DeesModal.prompt,DeesToast.show). The overlay's own words (its default buttons, help and close labels) speak it as well as the content it renders, and follow that instance rather than the page.Numbers and dates follow the instance's formats (
formats.number,formats.date), which a host sets apart from the language.The app shell's
labels(see dees-appui) still override the catalog's words in every language.The date picker names months and weekdays in the date format's language and starts the week where that format does (Monday in
de-DE, Sunday inen-US; Monday without a provider); itsweekStartsOn, when set, wins.A helper outside an element (the block editor's slash menu, the storage browser's
formatSizeandvalidateMove, the agent chat'sharnessFormat*helpers, the PDFformatFileSize) takes the words or formats to speak in and defaults to English;dcSourceWordsare the catalog's English source words.The agent chat's tool descriptors get the showing element's words as the optional second argument of
label,subtitleandsummary, and custom body renderers find them ashelpers.t, so a registered tool can speak the reader's language too.Every component's words are in the catalog.
scripts/check-literal-ratchet.cjs, part ofpnpm test, counts the English literals left in component templates and lets the count only go down; the two it still counts are URL examples in the block editor's link and video fields, which no language changes.An error a host throws for a component to show (a stepper action's refusal) can speak the reader's language after it was thrown:
@push.rocks/smarti18n'sLocalizedError(≥ 1.5.0) keeps a catalog key and its parameters with the words they come from, and itsmessagesays them in the language of the moment it is read. A component that renders again on a change of language, as the stepper's alert does, shows the new language. Itskeyandargsare typed ast()types them:import { LocalizedError } from '@push.rocks/smarti18n'; throw new LocalizedError(words, 'claim.amountInvalid', { amount }); // words = i18n.bind(catalog)The pseudo-locales
en-XA(longer, accented words) andar-XB(right to left) show clipped text and direction faults:createI18n({ locale: 'en-XA', pseudo: true }).
Development Guide
For developers working on this library, please refer to the UI Components Playbook for comprehensive patterns, best practices, and architectural guidelines.
Repository development notes, including readme.plan.md and readme.hints.md,
live in docs/development/. These notes are retained in Git and excluded from the
published package.
Before publishing, verify the installed package as well as the source tests:
pnpm test
node scripts/check-packed-consumer.mjspnpm test rebuilds the package first. The consumer check installs its tarball in
an isolated temporary project and checks the table, settings, modal, PDF viewer,
PDF thumbnail, harness message-list, datepicker, textarea and composer declarations with TypeScript 5.9.3 and
6.0.3, strict: true and skipLibCheck: false. It also runs Chromium construction,
reconnect, selection/copy and real keyboard checks against the installed modules
and bundle. The packed tests also exercise strict calendar validation, multiline
form values and attachment capabilities. Trusted keyboard entry inside a native
dialog is checked in UTC, Los Angeles, Berlin and São Paulo. No source aliases or
vendor declarations are used. Pass an existing
release tarball as the script's argument to verify those exact bytes.
Test sets
pnpm test runs every test file and is the check before a release. While
working on a change, run the test files that change can affect:
pnpm test:changed # what changed since origin/main, committed or not
pnpm test:changed --list # which test files, and the change that picks each, without running them
pnpm test:set element dees-table # the test files that cover one element
pnpm test:set group dataview # ... or an element group; `pnpm test:set groups` lists them
pnpm test:set path ts_web/elements/00theme.ts
pnpm test:set why test/test.chips.chromium.ts ts_web/elements/00group-utility/dees-icon/dees-icon.tsA set runs the ratchet checks pnpm test runs, builds the package when a picked
test file reads the build, and then runs the picked files with the options of
pnpm test. Arguments after -- go to tstest. Tests are bundled without a type
check; pnpm run build checks the types.
scripts/test-set.mjs picks a test file when the change touches what the file
covers: the modules it imports, followed through their own imports, and every
element whose tag it or one of those modules uses in a template or creates by
name. A barrel such as ts_web/index.ts is followed only for the names imported
from it, and a component's demo only for a test file that imports or renders it.
A change to package.json, pnpm-lock.yaml, pnpm-workspace.yaml,
tsconfig.json or .smartconfig.json runs every test file; documentation runs
none. What neither imports nor tags show, a test file names in a comment line,
// test-set:covers <path> ... (the notices and entry-point tests do).
The full run starts four test files at a time (concurrency in the
@git.zone/tstest section of .smartconfig.json), each in a Chromium or Node.js
process of its own. A test file therefore keeps to its own process: a server it
starts binds port 0 and reads the port it got, and it waits for an element's own
events rather than for the clock. A file has 30 seconds (--timeout 30); one
that needs more is split by subject.
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-actionfooter | button | DeesActionFooter | 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-commandpalette | appui | DeesAppuiCommandpalette | Source |
| dees-appui-contentheading | appui | DeesAppuiContentheading | Source |
| dees-appui-location | appui | DeesAppuiLocation | Source |
| dees-appui-maincontent | appui | DeesAppuiMaincontent | Source |
| dees-appui-mainmenu | appui | DeesAppuiMainmenu | Source |
| dees-appui-page | appui | DeesAppuiPage | Source |
| dees-appui-profiledropdown | appui | — | Source |
| dees-appui-secondarymenu | appui | DeesAppuiSecondarymenu | Source |
| dees-appui-tabs | appui | DeesAppuiTabs | Source |
| dees-appui-tray | appui | DeesAppuiTray | Source |
| dees-audio-viewer | media | DeesAudioViewer | Source |
| dees-avatar | dataview | DeesAvatar | 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-line | chart | DeesChartLine | 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 | DeesDashboardgrid | Source |
| dees-dataview-codebox | dataview | DeesDataviewCodebox | Source |
| dees-dataview-statusobject | dataview | DeesDataviewStatusobject | Source |
| dees-dropzone-overlay | overlay | DeesDropzoneOverlay | Source |
| dees-form | form | DeesForm | Source |
| dees-form-section | form | DeesFormSection | Source |
| dees-form-submit | form | DeesFormSubmit | Source |
| dees-formatting-menu | input | — | Source |
| dees-grouped-list | dataview | DeesGroupedList | Source |
| dees-harness-chat | harness | DeesHarnessChat | Source |
| dees-harness-composer | harness | DeesHarnessComposer | Source |
| dees-harness-content-blocks | harness | DeesHarnessContentBlocks | Source |
| dees-harness-conversation-picker | harness | DeesHarnessConversationPicker | 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-resource-list | harness | DeesHarnessResourceList | Source |
| dees-harness-session-list | harness | DeesHarnessSessionList | Source |
| dees-harness-session-sidebar | harness | DeesHarnessSessionSidebar | Source |
| dees-harness-sidebar | harness | DeesHarnessSidebar | Source |
| dees-harness-status | harness | DeesHarnessStatus | Source |
| dees-harness-thread-cut | harness | DeesHarnessThreadCut | 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-harness-work-run | harness | DeesHarnessWorkRun | 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-reference | input | DeesInputReference | Source |
| dees-input-richtext | input | DeesInputRichtext | Source |
| dees-input-tags | input | DeesInputTags | Source |
| dees-input-text | input | DeesInputText | Source |
| dees-input-textarea | input | DeesInputTextarea | Source |
| dees-input-toggle | input | DeesInputToggle | Source |
| dees-input-typelist | input | DeesInputTypelist | Source |
| dees-input-wysiwyg | input | DeesInputWysiwyg | Source |
| dees-kanban | kanban | DeesKanban | Source |
| dees-kanban-activity | kanban | DeesKanbanActivity | Source |
| dees-kanban-card | kanban | DeesKanbanCard | Source |
| dees-kanban-column | kanban | DeesKanbanColumn | Source |
| dees-kanban-discussion | kanban | DeesKanbanDiscussion | Source |
| dees-kanban-ticket-detail | kanban | DeesKanbanTicketDetail | Source |
| dees-label | layout | DeesLabel | Source |
| dees-legend | chart | DeesLegend | 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-popover | overlay | DeesPopover | Source |
| dees-preview | media | DeesPreview | Source |
| dees-profilepicture-modal | input | — | Source |
| dees-progressbar | feedback | DeesProgressbar | Source |
| dees-resize-handle | layout | DeesResizeHandle | 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-state | feedback | DeesState | Source |
| dees-statsgrid | dataview | DeesStatsGrid | Source |
| dees-statusbutton | feedback | DeesStatusbutton | 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-table-search | dataview | DeesTableSearch | Source |
| dees-terminal-view | terminal | DeesTerminalView | Source |
| dees-theme | utility | DeesTheme | Source |
| dees-thread | dataview | DeesThread | 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 | DeesTile | 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
Reflected properties
A component's public configuration is one surface in markup, in devtools and in CSS: every
public property of a string, number or boolean type reflects to its attribute, whether it was
set from markup or through a property binding. A page can style around a configuration, such
as dees-panel[variant='outline'] or dees-spinner[bnw], and a component's own
:host([...]) rules hold however the value arrived. Attribute names are the property name in
lower case unless the property declares another one, such as dees-table's
show-column-filters.
Four kinds of property stay unreflected, each listed with its reason in
scripts/reflect-allowlist.json: user data and state that changes per keystroke, pointer move,
scroll or data tick (a field's value, a search query, progress; a toggle's on/off value
reflects, since its own styles select it); objects, arrays and
functions, which have no attribute; names that are global HTML or ARIA attributes with native
behaviour on the host (title, aria-label); and large text or encoded data (a whole file,
a diff, a data URL). pnpm test runs scripts/check-reflect-ratchet.cjs, which fails on a
new public primitive property that neither reflects nor is listed there.
Failed actions
Components that run an application callback — dees-modal's menuOptions[].action,
dees-table's dataActions[].actionFunc and onRetry, dees-tile's actions[].actionFunc,
dees-appui-page's and dees-appui-contentheading's actions[].action, dees-state's actions[].action and onRetry,
dees-popover's actions[].action and dees-actionfooter's actions[].action —
await it. A rejected callback is
never dropped: the component dispatches the cancelable dees-action-error event
(bubbling and composed) and, unless a listener cancels it, shows the error through
dees-toast. Successful callbacks behave exactly as before and dispatch nothing.
element.addEventListener('dees-action-error', (event: CustomEvent<IActionErrorDetail>) => {
const { error, action, source } = event.detail;
// source: 'modal-menu' | 'modal-dismiss' | 'stepper-cancel' | 'table-header' | 'table-footer' | 'table-empty'
// | 'table-row' | 'table-rowmenu' | 'table-contextmenu' | 'table-doubleclick' | 'table-rowclick'
// | 'table-keycombination'
// | 'tile-header' | 'tile-footer' | 'page-action' | 'heading-action' | 'state-action' | 'state-retry'
// | 'popover-action' | 'actionfooter' (a footer's own `source` when it sets one)
event.preventDefault(); // render your own error surface instead of the toast
reportToCrashService(error);
});IActionErrorDetail, actionErrorEventName, actionErrorMessage,
reportActionError and runAction are exported from the package root, so a
component outside the catalog can announce failures the same way.
Where actions go
An action sits on the thing it acts on, in that thing's own place for actions, so every implementation offers its actions the same way. Choose the place by what the action is about:
| The action is about | Its place | For example |
| --- | --- | --- |
| A row of a table | the table's dataActions with type: ['inRow', 'contextmenu'] | "Open", "Approve" |
| A table as a whole, or its selected rows | the table's dataActions with type: ['header'] or ['footer']; ['empty'] for the way on from no rows | "Import", "Export", "Approve selected", "New service" |
| What a tile shows: a summary, a status, a chart, a list that is not a table | the tile's actions, type: ['footer'] for the way on from the content, ['header'] for one that adds to or refreshes it | "Open the audit trail", "Export CSV", "New invoice" |
| A whole page or view | dees-appui-page actions, or the view's contentHeading actions | "New service" |
| A decision a dialog asks for | dees-modal menuOptions | "Cancel", "Delete" |
| What a speech bubble shows, such as its filters | the bubble's actions | "Clear filters" |
| A place that loads, is empty or failed | dees-state actions | "Retry", "New service" |
| Not an action: a standing explanation of a view — how a feature works, what a list holds | the table's or the tile's info: an info button (ⓘ) at the end of the footer bar, after the footer actions, that opens it in the speech bubble | "A GoBD year archive holds one fiscal year: …" |
| Not an action: the view's filters | the table's filters, behind the filter button at the end of its search field; what is active shows as chips in the active-filter bar above the column headers | "Account", "Month", "Include private" |
- Never build a bar of buttons into a tile's footer or header slot. Tile actions and a table's header and footer actions are one standard, drawn by one renderer: they share their size, type, spacing, wrapping, glyph, busy face, keyboard behaviour and accessible name.
- A dialog's and a speech bubble's actions stand in their action footer,
dees-actionfooter, at the bottom edge: a divider, a tinted strip, the primary rightmost. Never build a row of buttons into a dialog's or a bubble's content; a surface of your own that asks like a dialog, a card centred in a view, places the same footer. - A tile whose content is a table offers the actions about the table's rows as the table's own header or footer actions, never in a second footer under the table's: the table's footer already carries its count and its actions.
- The slots are for content: a status line, a legend, statistics, a chip. Next to actions, slotted content takes the bar's leading side.
- An action that applies but cannot be taken yet stays visible and
disabled; one that never applies is not offered. - A standing explanation of a view never goes in a banner or a hint above its table or tile: it
goes in the
infoof the table or tile it explains. The info button is the footer bar's last control, named in the reader's language ("About this list"; on a tile "About {heading}"), and shows even where the bar has no other actions; its words are the host's, in the host's language. Hints stay for what is true now (a missing statement), the status button for a page's statuses. - Where filters go: into the filter button. A view's filters — a dropdown, a checkbox, a day —
are the table's
filters, never a row of fields in the page or table header. The filter button is the sliders button at the end of the table's search field, in the table's header or indees-table-searchon the page header: one entry point, never a second filter control beside the field. It counts the active filters and opens them in the speech bubble, with "Filter by column" and where the search looks, and "Clear filters" in the bubble's action footer; a choice applies as it is made. Each filter declares itsdefault, the value that means "not filtered" (every account, any month, private movements included): only a filter at another value is active, counts on the button, shows as a chip ("Account: Sparkasse", "Without private") in the active-filter bar between the header and the column headers, and "Clear all" puts it back to its default. The search counts in its own field, not on the button, and is a chip in the bar.
Frameless components
A component that draws its own box keeps it by default. Where the host already owns the
surface — a modal, a preview stage, a transcript row, a table cell, a fullscreen view, a
coloured panel — set frameless: the component drops its outer border, its corner radius and
its surface, so the host's surface shows through its header, content and footer. Content,
padding, separators, focus rings and behavior stay as they are, and so do the backgrounds that
belong to what the component shows: controls, a terminal, a code editor, a PDF page, a video
stage. A selected table row becomes a tint over the host's surface.
A part that content scrolls beneath — a table's sticky column header or sticky footer, a pinned
action column — paints an opaque backdrop while content passes beneath it, in
--dees-tile-surface (the catalog's content surface unless set). A host whose surface is not
that one sets the property on the frameless component, so the backdrop matches:
<dees-table frameless fixed-height style="--dees-tile-surface: var(--my-panel-surface)"></dees-table>frameless is a reflected boolean, so it works from markup, from a property binding, and
from the host's own CSS through :host([frameless]).
// The modal owns the window, so the diff fills it without a second border.
const modal = await DeesModal.createAndShow({
heading: 'config.ts',
content: html`<dees-dataview-codebox frameless .unifiedDiff=${patch}></dees-dataview-codebox>`,
});Supported by dees-chart-area, dees-chart-bar, dees-chart-donut, dees-chart-gauge,
dees-chart-log, dees-chart-radar, dees-dataview-codebox, dees-dataview-statusobject,
dees-input-code, dees-input-dropdown, dees-input-fileupload, dees-input-list,
dees-input-richtext, dees-input-text, dees-input-wysiwyg, dees-kanban-ticket-detail, dees-pdf-viewer, dees-preview, dees-settings,
dees-simple-login, dees-table, dees-tile, dees-video-viewer and
dees-workspace-terminal-preview.
dees-tile owns the rule for every component built on it; a new boxed component
joins the convention by forwarding .frameless=${this.frameless} to its tile and painting no
surface of its own over it in frameless mode. On
dees-input-text and dees-input-dropdown — what dees-table uses for inline cell editing —
the same flag also hides the label and validation row, because the cell owns that chrome.
dees-simple-login drops its card's surface and the page backdrop around it. On
dees-video-viewer, frameless means filling the enclosing frame: the stage keeps its black
letterbox and the controls bar its surface, because both sit over the video.
dees-kanban-ticket-detail draws no frame to drop: frameless only says that its edges are its
host's, so its toolbar, its main column and the outer side of its fields take the region inset;
the board sets it in its modal.
dees-dashboardgrid, dees-statsgrid and dees-stepper draw one card per item instead of a
frame of their own, so they deliberately have no frameless.
A frameless component also takes the region inset at its edges.
Region inset
--dees-inset-inline is the inline inset of the region an element sits in, measured to that
region's edges. It is how content lines up with the heading of the page or pane around it: a
page whose heading sits 32px in declares 32px on its body, and a frameless table in that body
puts its first and last content 32px in as well.
- Who sets it. A container that gives its content its full width — a page frame with a
flush body, a pane, a drawer or modal body that runs to its edges — sets it there, to its own
content inset. The theme sets it nowhere: unset, no region inset is declared. In the catalog,
dees-modaldeclares its heading's inset on a body withcontentPadding: 0. - Who reads it. Components drawn
frameless. Their edges are their host's edges, so the content at their inline edges — cells, header and footer bands, toolbars, notices, list rows, skeleton rows, labels — sits atvar(--dees-inset-inline, <own inset>). Unset, nothing moves. A framed component sits inside its host's padding and keeps its own inset; taking the region inset as well would inset it twice. - No leaking. A component that reads the token reads it once, on its host, and resets it for
everything it renders and slots, so a component nested in it — a table in a table cell, a
component in a settings value — sees a region inset only when a nearer container declares one.
Two pass it on instead, because their content runs to their own edges with no padding of its
own: a frameless
dees-tileto its slots, and a framelessdees-previewto the viewer in its content area. A tile's header or footer bar (tile actions) pads to the inset itself, so what it slots sees none.
// A page frame with a flush body: the heading's inset is the region inset of the body.
@customElement('my-page')
export class MyPage extends DeesElement {
public static styles = [themeDefaultStyles, css`
:host { display: flex; flex-direction: column; height: 100%; }
h1 { margin: 0; padding: var(--dees-spacing-xl) var(--dees-spacing-2xl) var(--dees-spacing-md); }
.body { --dees-inset-inline: var(--dees-spacing-2xl); flex: 1; min-height: 0; display: flex; flex-direction: column; }
`];
public render() {
return html`<h1>Services</h1>
<div class="body"><dees-table frameless fill .columns=${columns} .data=${services}></dees-table></div>`;
}
}Here the table's header band, its first and last cells, its footer, its skeleton rows and its
notice sit 32px from the body's edges: a selection checkbox starts at 32px and the row actions
end there. A fill or fixed-height table takes no room for its scrollbar, whose rail lies
over the end of its rows while they scroll (see Scrollbars). A narrow page
declares its narrow inset, such as var(--dees-spacing-lg), which the table takes in place of
its own narrow values.
A few places keep part of their own inset, each for a reason:
- an inline cell editor and a column filter field pad their text themselves, so their cell adds only the rest and the text stays where the cell showed it;
dees-input-wysiwygkeeps at least the 28px its block handles sit in;- ECharts charts (
dees-chart-line,-bar,-donut,-gauge,-radar) inset their plot by 16px themselves, so the plot's frame moves in by what the region inset adds; - a split diff in
dees-dataview-codeboxand the thumbnail strip ofdees-pdf-viewertake it on their outer sides only, not at the divider; - content centred in its box — an empty, loading or error state, the
dees-simple-logincard — has no edge to line up and keeps its padding.
A component outside the catalog joins the standard with insetInlineStyles, exported from the
package root. It reads the token on the host into the component's own property while the host
is [frameless], leaves that property unset otherwise, and resets the token for the shadow tree:
import { insetInlineStyles, themeDefaultStyles } from '@design.estate/dees-catalog';
public static styles = [
themeDefaultStyles,
insetInlineStyles('--panel-inset-inline'),
css`.toolbar { padding-inline: var(--panel-inset-inline, 12px); }`,
];Scroll edges
Where content scrolls beneath fixed chrome — a header row above it, a search field, pinned rows
or a footer below it — the edge content lies past casts a shadow: scrolled down, the top edge
casts one; with content below the fold, the bottom edge does; content that fits casts none. Each
shadow is a 16px band of --dees-color-scroll-edge-shadow painted from the edge inward (a 35%
stop at 6px), drawn over the content without taking its room or its pointer input, and
