A native, framework-free Web Component (<openatlas-vocabulary-viewer>) that renders the
OpenAtlas type vocabulary as a searchable master-detail browser:
an expandable hierarchy tree on the left and a rich detail panel on the right.
- Vanilla JS (ES6 class, Custom Elements v1) — no React / Vue / jQuery.
- Shadow DOM for full CSS isolation.
- Bootstrap 5.3 styling injected inside the shadow root, with colors, typography,
and font sizing overridable from the host page via
--oa-*CSS custom properties, HTML attributes, or JavaScript API for seamless Corporate Identity (CI) integration. - Bilingual UI (English / German) via a
langattribute. - Configurable endpoints so a host can point at its own caching proxy.
- Emits custom events so the host page can react to selections and errors.
The component is a single self-contained file, openatlas-vocabulary-viewer.js. Include it as an
ES module and drop the custom element onto your page:
<!-- Load the component -->
<script type="module" src="./openatlas-vocabulary-viewer.js"></script>
<!-- Use it -->
<openatlas-vocabulary-viewer
lang="en"
tree-endpoint="http://127.0.0.1:5000/api/1/vocabulary/tree"
detail-endpoint="http://127.0.0.1:5000/api/1/vocabulary/{id}">
</openatlas-vocabulary-viewer>On connection the component automatically fetches the tree and renders it. See index.html for a
full working demo (language switch, theming, custom endpoint, event log).
The tree endpoint returns a large payload. This component does not cache — caching is delegated to the host application. A typical setup is a Flask app with a Redis-backed URL proxy; simply point the component at your proxy URLs:
<openatlas-vocabulary-viewer
tree-endpoint="/api/proxy/vocabulary/tree"
detail-endpoint="/api/proxy/vocabulary/{id}">
</openatlas-vocabulary-viewer>The {id} placeholder in detail-endpoint is replaced with the selected type id.
Because Shadow DOM blocks outer page CSS, Bootstrap is injected into the shadow root. Colors and
a few layout values are exposed via --oa-* custom properties, which pierce the shadow boundary and
can be set from the host page (inline, a class, or :root):
openatlas-vocabulary-viewer {
--oa-primary: #0d6efd;
--oa-active-bg: #0d6efd;
--oa-active-text: #ffffff;
--oa-hover-bg: #e7f0ff;
}| Custom property | Default | Purpose |
|---|---|---|
--oa-bg |
#ffffff |
Component background |
--oa-text |
#212529 |
Primary text color |
--oa-muted |
#6c757d |
Muted / secondary text (labels, hints) |
--oa-primary |
#559f55 |
Primary accent (mapped to --bs-primary) |
--oa-border |
#dee2e6 |
Borders and dividers |
--oa-hover-bg |
#f1f5f1 |
Tree row hover background |
--oa-active-bg |
#559f55 |
Selected node background |
--oa-active-text |
#ffffff |
Selected node text |
--oa-link |
#2c6e2c |
Link color |
--oa-badge-bg |
#e7f1e7 |
Badge background (category / external refs) |
--oa-badge-text |
#2c6e2c |
Badge text |
--oa-class-badge-bg |
var(--oa-hover-bg) |
Entity class badge background |
--oa-class-badge-text |
var(--oa-text) |
Entity class badge text |
--oa-font-family |
'Inter', system-ui, … |
Component font family (inherited by all child elements) |
--oa-font-size |
1rem |
Base font size (headings, labels and controls scale proportionally) |
--oa-height |
600px |
Overall component height |
--oa-master-width |
350px |
Base/fixed width of the left tree sidebar (alias: --oa-sidebar-width) |
You can also fully swap Bootstrap by pointing bootstrap-url at a different (Bootstrap-compatible)
stylesheet.
To apply your organization's Corporate Identity (typography, font sizing, brand colors), three interfaces are available:
openatlas-vocabulary-viewer.my-brand {
--oa-font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, sans-serif;
--oa-font-size: 14px; /* All nested text & headings scale proportionally */
--oa-primary: #7b1fa2;
--oa-active-bg: #7b1fa2;
--oa-active-text: #ffffff;
--oa-link: #6a1b9a;
}<openatlas-vocabulary-viewer
font-family="'Segoe UI', Roboto, sans-serif"
font-size="15px"
lang="de">
</openatlas-vocabulary-viewer>const viewer = document.querySelector('openatlas-vocabulary-viewer');
// Using property setters:
viewer.fontFamily = 'Georgia, serif';
viewer.fontSize = '14px';
// Using individual methods:
viewer.setFontFamily('Georgia, serif');
viewer.setFontSize('14px'); // accepts strings ('14px', '1rem') or numbers (14 -> 14px)
// Using the combined helper method:
viewer.setFont({
fontFamily: "'Segoe UI', Roboto, sans-serif",
fontSize: '15px'
});| Attribute | Default | Description |
|---|---|---|
tree-endpoint |
http://127.0.0.1:5000/api/1/vocabulary/tree |
URL of the vocabulary tree endpoint. Overrides DEFAULT_TREE_ENDPOINT. Changing it reloads the tree. |
detail-endpoint |
http://127.0.0.1:5000/api/1/vocabulary/{id} |
URL template for type details; {id} is replaced with the type id. Overrides DEFAULT_DETAIL_ENDPOINT. |
title-endpoint |
"" |
Optional API endpoint URL to dynamically fetch and override the header title (JSON or plain text). |
header-title |
(localized default: OpenAtlas Vocabulary) |
Custom header title text overriding the default localized title. Aliases: custom-title, title. |
sidebar-width |
350px |
Fixed base width of the left tree sidebar (e.g. '350px', '24rem'). Alias: master-width. |
resizable |
true |
Whether the user can drag the splitter bar to resize the left tree sidebar ("true" / "false"). |
bootstrap-url |
https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/.../bootstrap.min.css |
Bootstrap 5.3 stylesheet injected into the shadow root. Overrides DEFAULT_BOOTSTRAP_URL. |
lang |
en |
UI language: en or de. Can be changed at runtime. |
exclude-ids |
"" |
Comma-separated or JSON array of IDs to ignore/exclude (e.g. "209204, 213222"). Mutually exclusive with include-ids. |
ignored-ids |
"" |
Alias for exclude-ids. |
include-ids |
"" |
Comma-separated or JSON array of IDs to include (e.g. "[209204, 213222]"). Mutually exclusive with exclude-ids. |
included-ids |
"" |
Alias for include-ids. |
font-family |
(CSS default) | Custom font family (e.g. 'Segoe UI', sans-serif). Updates --oa-font-family. |
font-size |
(CSS default: 1rem) |
Base font size (e.g. '14px', '1rem', or number 14). Updates --oa-font-size. |
| Method / Property | Description |
|---|---|
setTitle(title) |
Sets the header title at runtime, overriding the localized default. Pass null to reset. |
getTitle() |
Returns the current effective title (custom title if set, or localized default). |
resetTitle() |
Resets the header title to the default localized string (OpenAtlas Vocabulary). |
setTitleEndpoint(url) |
Sets a custom endpoint URL and triggers loadTitle(). |
loadTitle(url?) |
Asynchronously fetches the title from the configured title-endpoint. Returns Promise<string>. |
headerTitle / title |
Property accessors to get or set the header title override. |
titleEndpoint |
Property accessor for the dynamic title endpoint URL. |
setSidebarWidth(width) |
Sets the left column (master tree) width at runtime (accepts numbers e.g. 400 or '400px'). |
getSidebarWidth() |
Returns the current configured sidebar width. Alias: getMasterWidth(). |
setMasterWidth(width) |
Alias for setSidebarWidth(width). |
sidebarWidth / masterWidth |
Property accessors for getting / setting the left column width. |
setResizable(boolean) |
Enables or disables user-interactive splitter dragging (true / false). |
isResizable() / resizable |
Checks or sets whether the splitter bar is interactive/resizable. |
loadTree() |
Fetches the tree from tree-endpoint and renders it. Returns a Promise<void>. |
renderTree(data) |
Renders (or re-renders) the tree from externally supplied data — no network needed. |
loadDetail(id) |
Fetches a type's detail, renders the detail panel, and dispatches oa-vocabulary-selected. |
setIncludedIds(ids) |
Sets the list of IDs to include (Array, Set, comma-separated string, or number). |
getIncludedIds() |
Returns the current array of included IDs (number[]). |
addIncludedId(...ids) / addIncludedIds(...) |
Adds one or more IDs to the include list. |
removeIncludedId(...ids) / removeIncludedIds(...) |
Removes one or more IDs from the include list. |
clearIncludedIds() |
Clears all included IDs. |
isIncluded(id) |
Checks if a specific ID is configured as included. |
includedIds / includeIds |
Property accessors for getting / setting included IDs. |
setIgnoredIds(ids) / setExcludeIds(ids) |
Sets the list of IDs to ignore/exclude. |
getIgnoredIds() / getExcludeIds() |
Returns the current array of ignored/excluded IDs (number[]). |
addIgnoredId(...ids) / addIgnoredIds(...) |
Adds one or more IDs to the ignore/exclude list. |
removeIgnoredId(...ids) / removeIgnoredIds(...) |
Removes one or more IDs from the ignore/exclude list. |
clearIgnoredIds() / clearExcludeIds() |
Clears all ignored/excluded IDs and restores visibility. |
isIgnored(id) / isExcluded(id) |
Checks if a specific ID is currently ignored/excluded. |
ignoredIds / excludeIds |
Property accessors for getting / setting ignored/excluded IDs. |
isVisible(id) |
Checks if an ID is visible according to active filter rules. |
clearFilters() |
Clears both inclusion and exclusion filters. |
setLanguage(lang) |
Switches the UI language ('en' | 'de') at runtime. |
setFontFamily(fontFamily) |
Sets the font family at runtime (e.g. 'Roboto, sans-serif'). |
setFontSize(fontSize) |
Sets the base font size at runtime (e.g. '14px', '1rem', or 14). |
setFont({ fontFamily, fontSize }) |
Convenience helper to set font family and font size simultaneously. |
const viewer = document.querySelector('openatlas-vocabulary-viewer');
// Exclude top-level hierarchies or specific types and all their descendant subtrees:
viewer.setIgnoredIds([209204, 213222, 218845, 22777, 196063]);
// Add / remove dynamically:
viewer.addIgnoredIds(12345);
viewer.removeIgnoredIds(209204);
// Re-render the tree from your own data (e.g. server-rendered / cached):
viewer.renderTree({ standard: [...], place: [...], custom: [], value: [], system: [] });
// Programmatically open a type:
viewer.loadDetail(12);
// Switch language:
viewer.setLanguage('de');All events bubble and are composed (they cross the shadow boundary).
| Event | detail payload |
Fired when |
|---|---|---|
oa-tree-loaded |
{ data: VocabularyTreeResponse } |
The tree has been fetched and rendered. |
oa-vocabulary-selected |
{ id: number, data: VocabularyFlatItem } |
A type is selected and its detail loaded. |
oa-title-loaded |
{ title: string, url: string } |
The header title has been fetched from title-endpoint. |
oa-sidebar-resized |
{ width: number, widthCss: string } |
The left sidebar has been resized by the user or programmatically. |
oa-vocabulary-error |
{ error: Error, context: 'tree'|'detail'|'title' } |
A tree, detail, or title fetch fails. |
viewer.addEventListener('oa-vocabulary-selected', (e) => {
console.log('Selected type', e.detail.id, e.detail.data);
});
viewer.addEventListener('oa-sidebar-resized', (e) => {
console.log('Sidebar resized to', e.detail.width, 'px');
});
viewer.addEventListener('oa-vocabulary-error', (e) => {
console.warn('Vocabulary error in', e.detail.context, e.detail.error);
});The component provides two powerful, mutually exclusive filtering strategies to tailor the taxonomy displayed in the viewer:
- Exclude (
exclude-ids/ignored-ids): Hides targeted type IDs along with all their descendant subtrees from the hierarchy tree, breadcrumbs, and subtypes listings. - Include (
include-ids/included-ids): Displays only the targeted type IDs (and their subtrees), keeping necessary ancestor branches navigable while pruning all unrelated categories and sibling nodes.
Mutual Exclusivity Rule:
include-idsandexclude-ids(ignored-ids) are mutually exclusive. Only one filtering mode can be active at a time. If both are specified on the component, passed to the adapter, or set simultaneously, an explicitErroris thrown.
// Example list to exclude or include:
const typeIds = [209204, 213222, 218845, 22777, 196063];Configure inclusion or exclusion directly in HTML:
<!-- Exclude specific types and their descendants -->
<openatlas-vocabulary-viewer
exclude-ids="[209204, 213222, 218845, 22777, 196063]"
tree-endpoint="http://127.0.0.1:5000/api/1/vocabulary/tree"
detail-endpoint="http://127.0.0.1:5000/api/1/vocabulary/{id}">
</openatlas-vocabulary-viewer>
<!-- OR Include only specific types and their subtrees -->
<openatlas-vocabulary-viewer
include-ids="[209204, 213222]"
tree-endpoint="http://127.0.0.1:5000/api/1/vocabulary/tree"
detail-endpoint="http://127.0.0.1:5000/api/1/vocabulary/{id}">
</openatlas-vocabulary-viewer>Accepts JSON arrays ("[209204, 213222]"), comma-separated strings ("209204, 213222, 218845"), or whitespace-delimited lists.
Manage include and exclude filters dynamically on the custom element:
import { OpenAtlasVocabularyViewer } from './openatlas-vocabulary-viewer.js';
const viewer = document.querySelector('openatlas-vocabulary-viewer');
// --- Inclusion Filtering ---
viewer.setIncludedIds([209204, 213222]);
viewer.addIncludedId(218845);
viewer.removeIncludedId(218845);
console.log(viewer.isIncluded(209204)); // true
viewer.clearIncludedIds();
// --- Exclusion Filtering ---
viewer.setIgnoredIds([209204, 213222, 218845, 22777, 196063]);
viewer.addIgnoredIds(22777);
viewer.removeIgnoredIds(22777);
console.log(viewer.isIgnored(209204)); // true
viewer.clearIgnoredIds();
// --- Mutual Exclusivity Error Example ---
try {
viewer.setIncludedIds([100]);
viewer.setIgnoredIds([200]); // Throws Error: mutually exclusive!
} catch (err) {
console.error(err.message);
}The module exports createVocabularyFilterAdapter for client-side use or backend proxies / middleware:
import { createVocabularyFilterAdapter } from './openatlas-vocabulary-viewer.js';
// 1. Create an inclusion adapter:
const includeAdapter = createVocabularyFilterAdapter({ includeIds: [209204, 213222] });
// OR create an exclusion adapter:
const excludeAdapter = createVocabularyFilterAdapter({ excludeIds: [209204, 213222, 218845] });
// 2. Wrap standard fetch as a transparent interceptor/adapter:
const filteredFetch = includeAdapter.wrapFetch(window.fetch);
// 3. Direct data filtering:
const filteredTree = includeAdapter.filterTree(rawTreeData);
const filteredDetail = includeAdapter.filterDetail(rawDetailData);For lightweight data pipeline transformations:
import {
filterVocabularyTree,
filterVocabularyItem,
filterVocabularyDetail,
parseIds
} from './openatlas-vocabulary-viewer.js';
// Include filter on tree payload:
const includedTree = filterVocabularyTree(treeResponse, { includeIds: [209204, 213222] });
// Exclude filter on tree payload:
const excludedTree = filterVocabularyTree(treeResponse, { excludeIds: [209204, 213222] });
// Filter detail payload:
const filteredDetail = filterVocabularyDetail(detailResponse, { includeIds: [209204] });- Hierarchy tree grouped by the six vocabulary categories (Standard, Place, Custom, Value, Tools, System), with expandable / collapsible nodes. Branches are rendered lazily — a node's children are only built when it is expanded — so even very large trees (the live vocabulary has ~20,000 nodes) load and stay responsive.
- Entity counts in the tree —
entityCount(direct entities) andentityCountSubs(descendant entities) are rendered as clear badges next to each tree item, allowing immediate identification of whether and how many entities are linked across the taxonomy. - Selectable vs. structural grouping nodes — Types with
selectable: falseserve as category folders / grouping nodes and are visually highlighted with a folder icon and status badges, clearly indicating that entities cannot be directly assigned to them. - Interactive two-way hierarchy navigation — Breadcrumbs are built from
parents(LinkedTypeItem) and directsubTypesare displayed as interactive buttons; selecting any parent or child automatically navigates to that type and smoothly scrolls and expands to it in the tree. - Real-time search — case-insensitive matching on type names and entity classes (evaluated against the loaded data), rendering only the matches and their ancestors fully expanded, and showing a localized "no results" state when nothing matches.
- Rich detail panel — fully compliant with the latest OpenAtlas OpenAPI schema:
- Name, category, copy-to-clipboard buttons (Name, URL, UUID)
- Concept download dropdown for Linked Open Data export (JSON-LD, Turtle, RDF/XML, N-Triples)
- Selectable status badge (
Assignable/Structural node (not assignable)) and entity counts - Entity classes (
classes) showing which classes the type can be assigned to (e.g.artifact,place) - Direct sub-types list (
subTypes) with click-to-open - Temporal validity / usage period (
timespanwith start, end dates and derivation notes) - Description with line break preservation
- External authority references with direct entity links (
url), match type (match_type), and descriptions - Bibliographic citations with automatic clickable URL linkification
- Primary image with link to full-resolution file, creator/copyright, and license attribution
- Robust error handling — failed fetches show an in-component error state (with retry for the
tree) and fire
oa-vocabulary-error; the UI stays usable.
- Endpoints & Defaults:
DEFAULT_TREE_ENDPOINT,DEFAULT_DETAIL_ENDPOINT, andDEFAULT_BOOTSTRAP_URLare fallback defaults that can be overridden per instance via HTML attributes (tree-endpoint,detail-endpoint,bootstrap-url) or properties.CATEGORY_ORDERdefines the standard OpenAtlas category sequence (['standard', 'place', 'custom', 'value', 'tools', 'system']). - No client-side caching: By design, caching is the host's responsibility (e.g. a Redis-backed proxy). Just point
tree-endpoint/detail-endpointat your proxy or feed data directly viarenderTree(data). - Clipboard: Uses
navigator.clipboardin secure contexts (HTTPS /localhost) and gracefully falls back todocument.execCommandotherwise.
If you want to use this component across multiple websites/projects, choose the integration approach that best fits your workflow:
You do not even need to publish to public npm; npm/yarn/pnpm can install directly from GitHub:
npm install git+https://github.com/thanados-network/vocabulary-viewer.git
# or pin to a specific branch/tag/commit:
npm install git+https://github.com/thanados-network/vocabulary-viewer.git#v1.0.0Then import it in your build setup (Vite, Webpack, Rollup, etc.):
import 'openatlas-vocabulary-viewer';Best for versioned releases and CI/CD pipelines:
package.jsonis already initialized with"main": "openatlas-vocabulary-viewer.js"and"name": "openatlas-vocabulary-viewer"(or adjust the package name/scope if publishing under an organization).- Publish to npm (
npm publish) or GitHub Packages. - In both client websites:
npm install openatlas-vocabulary-viewer(or@openatlas/vocabulary-viewer).
Add this repository as a submodule in each website's repository:
git submodule add https://github.com/thanados-network/vocabulary-viewer.git vendor/vocabulary-viewerInclude the script in HTML:
<script type="module" src="/vendor/vocabulary-viewer/openatlas-vocabulary-viewer.js"></script>Updates can be pulled in each website with git submodule update --remote.
Host openatlas-vocabulary-viewer.js on a shared server, CDN (such as jsDelivr/unpkg via GitHub release/npm tag), or reverse-proxy:
<script type="module" src="https://cdn.example.org/js/openatlas-vocabulary-viewer.js"></script>Released under the MIT License. See LICENSE.