Quickstart
A complete, working explorer in about five minutes. Copy each block in order — nothing is elided, and the result runs.
We'll start from a realistic API response, not tidy sample data.
1. Install
npm install @document-explorer/reactnpm install @document-explorer/angularnpm install @document-explorer/core @document-explorer/theme2. The data you already have
Say your endpoint returns this. Note the shape: snake_case keys, numeric ids, nested children, a kind field instead of type.
[
{
"document_id": 1,
"document_title": "Engineering",
"kind": "DIRECTORY",
"folder_id": null,
"children": [
{
"document_id": 100,
"document_title": "system-design.pdf",
"kind": "FILE",
"folder_id": 1,
"file_size": 2450000,
"modified": "2026-08-22T10:00:00Z",
"download": "/api/documents/100/download",
"extra": { "owner": "Ada Lovelace", "status": "approved" }
}
]
}
]You do not transform this. You describe it once.
3. Describe the shape
// mapper.ts
import type { DocumentMapper } from '@document-explorer/core';
export interface ApiDocument {
document_id: number;
document_title: string;
kind: 'DIRECTORY' | 'FILE';
folder_id: number | null;
file_size?: number;
modified?: string;
download?: string;
extra?: { owner?: string; status?: string };
children?: ApiDocument[];
}
export const mapper: DocumentMapper<ApiDocument> = {
id: 'document_id', // numbers are coerced to strings for you
name: 'document_title',
type: 'kind', // 'DIRECTORY' is recognised as a folder
parentId: 'folder_id',
size: 'file_size',
updatedAt: 'modified',
downloadUrl: 'download',
metadata: 'extra', // anything else you want to show
};Every field takes a key, a dot path ('audit.created_by.name'), or a function ((d) => d.kind === 'FILE' ? 'file' : 'folder'). Strings are enough here, and a string-only mapper has a bonus: it can cross a React Server Components boundary. See compatibility.
4. Render it
import { DocumentExplorer } from '@document-explorer/react';
import '@document-explorer/react/styles.css';
import { mapper, type ApiDocument } from './mapper';
export function Documents({ data }: { data: ApiDocument[] }) {
return (
<DocumentExplorer<ApiDocument>
data={data}
mapper={mapper}
className="documents"
/>
);
}import { Component, input } from '@angular/core';
import { DocumentExplorerComponent } from '@document-explorer/angular';
import { mapper, type ApiDocument } from './mapper';
@Component({
selector: 'app-documents',
standalone: true,
imports: [DocumentExplorerComponent],
template: `<doc-explorer [data]="data()" [mapper]="mapper" class="documents" />`,
})
export class DocumentsComponent {
readonly data = input.required<ApiDocument[]>();
protected readonly mapper = mapper;
}Angular needs the stylesheet registered once, in angular.json:
"styles": ["@document-explorer/angular/styles.css", "src/styles.css"]5. Give it a height
The explorer fills its container and scrolls internally, so it needs one:
.documents {
height: 480px;
}That is a working explorer — folder navigation, breadcrumbs, sorting, file type icons, formatted sizes and dates.
6. Turn on what you need
Nothing below is required; add only what your product actually has.
<DocumentExplorer<ApiDocument>
data={data}
mapper={mapper}
className="documents"
title="Documents"
search={{ scope: 'global', searchableFields: ['metadata.owner'] }}
selection={{ mode: 'multiple', folders: false }}
actions={{ view: true, download: true }}
views={['list', 'grid', 'tree']}
resizableColumns
onDocumentOpen={(item) => window.open(item.viewUrl)}
onDownload={(item) => download(item.downloadUrl!)}
onSelectionChange={setSelected}
/><doc-explorer
[data]="data()"
[mapper]="mapper"
class="documents"
title="Documents"
[search]="search"
[selection]="selection"
[actions]="actions"
[views]="views"
[resizableColumns]="true"
(documentOpen)="open($event)"
(documentDownload)="download($event)"
(selectionChange)="selected.set($event)"
/>Angular: declare object inputs as fields
An object literal written inline in a template is a new object on every change detection pass. Put search, selection and actions on the component.
7. Show your own columns
import { avatarColumn, badgeColumn } from '@document-explorer/react';
<DocumentExplorer
columns={[
'name',
avatarColumn({ key: 'metadata.owner', label: 'Owner' }),
badgeColumn({ key: 'metadata.status', label: 'Status' }),
'size',
'updatedAt',
]}
/>avatarColumn renders a person — initials or a photo, name beside it or on hover. badgeColumn gives approved a green tone with no configuration. Both are ordinary ColumnDefs, so anything they do you can also write by hand with render.
See custom rendering for the full picture.
8. Wire up the actions
The library never performs an action — it renders the menu and tells you what was clicked:
<DocumentExplorer
actions={[
{ id: 'view', label: 'View' },
{ id: 'download', label: 'Download' },
{ id: 'share', label: 'Share' },
]}
onAction={(actionId, item) => {
if (actionId === 'view') openViewer(item);
if (actionId === 'download') download(item.downloadUrl!);
if (actionId === 'share') openShareDialog(item);
}}
/>share means whatever you decide. The library needs no idea.
9. Make it yours
Override tokens; you should never need a rule against a .de-* class:
.documents {
--de-accent: #7c3aed;
--de-radius: 4px;
--de-row-height: 48px;
--de-font-size: 13px;
}Dark mode follows prefers-color-scheme on its own. For a toggle, set data-de-theme="dark" on any ancestor. Full list in theming.
Handling the messy parts
Loading and errors. The explorer fetches nothing, so tell it what yours is doing:
<DocumentExplorer data={data ?? []} loading={isLoading} error={error} />Bad rows. Duplicate ids, missing parents and cycles are repaired and reported rather than thrown. Inspect them when you want to know about upstream problems:
import { normalize } from '@document-explorer/core';
const { warnings } = normalize(data, { mapper });
warnings.forEach((w) => console.warn(w.code, w.message));Where to go next
- Mapping your data — dot paths, functions, nested payloads
- Sorting, columns and alignment — widths, resizing, alignment
- Custom rendering — avatars, badges, your own cells
- Compatibility — versions, Next.js, SSR
- The core API — driving the engine without a UI package