Skip to content

Tree

Do not use in Production

Accessible tree container. Owns keyboard navigation (arrows, Home/End, typeahead), roving tabindex and focus for the <certara-tree-item> rows inside it, and reports user intent through events. It does not hold tree data: the parent app renders the visible rows (usually from a TreeModel) and updates open / active in response to the events.

Rows may be wrapped in other elements (e.g. a virtual-scroll viewport); navigation follows the DOM order of the rendered rows. The tree never moves its children, so frameworks and virtualizers can add, remove and reorder rows freely. Give the tree an accessible name with aria-label or aria-labelledby.

The tree is split into three parts so it behaves the same in plain HTML, React and Angular, and so it can be virtualized for very large data sets:

  • TreeModel (exported from @certara/certara-ui, and from @certara/certara-ui-react for React) holds the data: nodes, expansion, lazy loading, loading/empty/error placeholders and filtering. It produces visibleRows, a flat array of the rows that should be on screen.
  • <certara-tree-item> renders one row. Rows are flat siblings, not nested; depth comes from level.
  • <certara-tree> handles keyboard navigation, focus and ARIA for the rows inside it, and reports what the user asked for through events. It never changes rows itself: update open / active (usually through the model) in response to certaraTreeExpandedChange and certaraTreeItemSelect.

Because your framework renders the rows, any row content works (checkboxes, menus, drag handles), and any virtualizer can decide which rows exist in the DOM.

HTML
React
Angular
Documents report.csv summary.xlsx Drafts Images
<certara-tree aria-label="Files" toggle-on-click>
<certara-tree-item item-id="docs" level="1" expandable open set-size="2" pos-in-set="1">
<certara-icon slot="icon" variant="regular" icon="folder-open" class="text-tertiary" role="presentation">
</certara-icon>
Documents
</certara-tree-item>
<certara-tree-item item-id="report" level="2" set-size="3" pos-in-set="1">
report.csv
</certara-tree-item>
<certara-tree-item item-id="summary" level="2" set-size="3" pos-in-set="2" active>
summary.xlsx
</certara-tree-item>
<certara-tree-item item-id="drafts" level="2" expandable set-size="3" pos-in-set="3">
<certara-icon slot="icon" variant="regular" icon="folder" class="text-tertiary" role="presentation">
</certara-icon>
Drafts
</certara-tree-item>
<certara-tree-item item-id="draft-1" level="3" set-size="3" pos-in-set="1" hidden>
draft-1.docx
</certara-tree-item>
<certara-tree-item item-id="draft-2" level="3" set-size="3" pos-in-set="2" hidden>
draft-2.docx
</certara-tree-item>
<certara-tree-item item-id="images" level="1" expandable set-size="2" pos-in-set="2">
<certara-icon slot="icon" variant="regular" icon="folder" class="text-tertiary" role="presentation">
</certara-icon>
Images
</certara-tree-item>
<certara-tree-item item-id="photo-1" level="2" set-size="3" pos-in-set="1" hidden>
photo-1.png
</certara-tree-item>
<certara-tree-item item-id="photo-2" level="2" set-size="3" pos-in-set="2" hidden>
photo-2.png
</certara-tree-item>
</certara-tree>
import { CertaraIcon, CertaraTree, CertaraTreeItem } from '@certara/certara-ui-react';
<CertaraTree aria-label="Files" toggleOnClick>
<CertaraTreeItem itemId="docs" level={1} expandable open setSize={2} posInSet={1}>
<CertaraIcon slot="icon" variant="regular" icon="folder-open" className="text-tertiary" role="presentation">
</CertaraIcon>
Documents
</CertaraTreeItem>
<CertaraTreeItem itemId="report" level={2} setSize={3} posInSet={1}>
report.csv
</CertaraTreeItem>
<CertaraTreeItem itemId="summary" level={2} setSize={3} posInSet={2} active>
summary.xlsx
</CertaraTreeItem>
<CertaraTreeItem itemId="drafts" level={2} expandable setSize={3} posInSet={3}>
<CertaraIcon slot="icon" variant="regular" icon="folder" className="text-tertiary" role="presentation">
</CertaraIcon>
Drafts
</CertaraTreeItem>
<CertaraTreeItem itemId="draft-1" level={3} setSize={3} posInSet={1} hidden>
draft-1.docx
</CertaraTreeItem>
<CertaraTreeItem itemId="draft-2" level={3} setSize={3} posInSet={2} hidden>
draft-2.docx
</CertaraTreeItem>
<CertaraTreeItem itemId="images" level={1} expandable setSize={2} posInSet={2}>
<CertaraIcon slot="icon" variant="regular" icon="folder" className="text-tertiary" role="presentation">
</CertaraIcon>
Images
</CertaraTreeItem>
<CertaraTreeItem itemId="photo-1" level={2} setSize={3} posInSet={1} hidden>
photo-1.png
</CertaraTreeItem>
<CertaraTreeItem itemId="photo-2" level={2} setSize={3} posInSet={2} hidden>
photo-2.png
</CertaraTreeItem>
</CertaraTree>
// In your module (e.g., app.module.ts)
import { CertaraUiAngularModule } from '@certara/certara-ui-angular';
@NgModule({
imports: [CertaraUiAngularModule]
})
// In your template:
<certara-tree aria-label="Files" toggle-on-click>
<certara-tree-item item-id="docs" level="1" expandable open set-size="2" pos-in-set="1">
<certara-icon slot="icon" variant="regular" icon="folder-open" class="text-tertiary" role="presentation">
</certara-icon>
Documents
</certara-tree-item>
<certara-tree-item item-id="report" level="2" set-size="3" pos-in-set="1">
report.csv
</certara-tree-item>
<certara-tree-item item-id="summary" level="2" set-size="3" pos-in-set="2" active>
summary.xlsx
</certara-tree-item>
<certara-tree-item item-id="drafts" level="2" expandable set-size="3" pos-in-set="3">
<certara-icon slot="icon" variant="regular" icon="folder" class="text-tertiary" role="presentation">
</certara-icon>
Drafts
</certara-tree-item>
<certara-tree-item item-id="draft-1" level="3" set-size="3" pos-in-set="1" hidden>
draft-1.docx
</certara-tree-item>
<certara-tree-item item-id="draft-2" level="3" set-size="3" pos-in-set="2" hidden>
draft-2.docx
</certara-tree-item>
<certara-tree-item item-id="images" level="1" expandable set-size="2" pos-in-set="2">
<certara-icon slot="icon" variant="regular" icon="folder" class="text-tertiary" role="presentation">
</certara-icon>
Images
</certara-tree-item>
<certara-tree-item item-id="photo-1" level="2" set-size="3" pos-in-set="1" hidden>
photo-1.png
</certara-tree-item>
<certara-tree-item item-id="photo-2" level="2" set-size="3" pos-in-set="2" hidden>
photo-2.png
</certara-tree-item>
</certara-tree>
CertaraTreeExample.jsx
import {useState} from 'react';
import {CertaraIcon, CertaraTree, CertaraTreeItem} from '@certara/certara-ui-react';
// Same rows as the HTML Default example in tree.mdx. Rows with `expandable` are folders.
const rows = [
{id: 'docs', level: 1, label: 'Documents', expandable: true, setSize: 2, posInSet: 1},
{id: 'report', level: 2, label: 'report.csv', setSize: 3, posInSet: 1},
{id: 'summary', level: 2, label: 'summary.xlsx', setSize: 3, posInSet: 2},
{id: 'drafts', level: 2, label: 'Drafts', expandable: true, setSize: 3, posInSet: 3},
{id: 'draft-1', level: 3, label: 'draft-1.docx', setSize: 2, posInSet: 1},
{id: 'draft-2', level: 3, label: 'draft-2.docx', setSize: 2, posInSet: 2},
{id: 'images', level: 1, label: 'Images', expandable: true, setSize: 2, posInSet: 2},
{id: 'photo-1', level: 2, label: 'photo-1.png', setSize: 2, posInSet: 1},
{id: 'photo-2', level: 2, label: 'photo-2.png', setSize: 2, posInSet: 2},
];
// Leaves out rows under a collapsed folder.
function getVisibleRows(allRows, openIds) {
let collapsedLevel = null;
return allRows.filter((row) => {
if (collapsedLevel !== null && row.level > collapsedLevel) {
return false;
}
collapsedLevel = row.expandable && !openIds.includes(row.id) ? row.level : null;
return true;
});
}
export function CertaraTreeExample() {
// The tree only reports what the user asked for, so the example keeps the open folders and the active row in state.
const [openIds, setOpenIds] = useState(['docs']);
const [activeId, setActiveId] = useState('summary');
const onExpandedChange = (e) => {
const {id, expanded} = e.detail;
setOpenIds((ids) => (expanded ? [...ids, id] : ids.filter((openId) => openId !== id)));
};
const onItemSelect = (e) => setActiveId(e.detail.id);
return (
<CertaraTree aria-label="Files" toggleOnClick onCertaraTreeExpandedChange={onExpandedChange} onCertaraTreeItemSelect={onItemSelect}>
{getVisibleRows(rows, openIds).map((row) => {
const open = openIds.includes(row.id);
return (
<CertaraTreeItem key={row.id} itemId={row.id} level={row.level} expandable={row.expandable} open={open} active={row.id === activeId} setSize={row.setSize} posInSet={row.posInSet}>
{row.expandable && <CertaraIcon slot="icon" variant="regular" icon={open ? 'folder-open' : 'folder'} className="text-tertiary" role="presentation" />}
{row.label}
</CertaraTreeItem>
);
})}
</CertaraTree>
);
}
Documents report.csv summary.xlsx Drafts Images
tree-example.component.ts
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
import {CertaraUiAngularModule} from '@certara/certara-ui-angular';
@Component({
selector: 'app-tree-example',
standalone: true,
imports: [CommonModule, CertaraUiAngularModule],
template: `
<certara-tree aria-label="Files" [toggleOnClick]="true" (certaraTreeExpandedChange)="expanded[$event.detail.id] = $event.detail.expanded" (certaraTreeItemSelect)="active = $event.detail.id">
<certara-tree-item itemId="docs" [level]="1" [expandable]="true" [open]="expanded['docs']" [setSize]="2" [posInSet]="1">
<certara-icon slot="icon" variant="regular" [name]="expanded['docs'] ? 'folder-open' : 'folder'" class="text-tertiary" role="presentation"></certara-icon>
Documents
</certara-tree-item>
<certara-tree-item *ngIf="expanded['docs']" itemId="report" [level]="2" [setSize]="3" [posInSet]="1" [active]="active === 'report'">report.csv</certara-tree-item>
<certara-tree-item *ngIf="expanded['docs']" itemId="summary" [level]="2" [setSize]="3" [posInSet]="2" [active]="active === 'summary'">summary.xlsx</certara-tree-item>
<certara-tree-item *ngIf="expanded['docs']" itemId="drafts" [level]="2" [expandable]="true" [open]="expanded['drafts']" [setSize]="3" [posInSet]="3">
<certara-icon slot="icon" variant="regular" [name]="expanded['drafts'] ? 'folder-open' : 'folder'" class="text-tertiary" role="presentation"></certara-icon>
Drafts
</certara-tree-item>
<certara-tree-item *ngIf="expanded['docs'] && expanded['drafts']" itemId="draft-1" [level]="3" [setSize]="2" [posInSet]="1">draft-1.docx</certara-tree-item>
<certara-tree-item *ngIf="expanded['docs'] && expanded['drafts']" itemId="draft-2" [level]="3" [setSize]="2" [posInSet]="2">draft-2.docx</certara-tree-item>
<certara-tree-item itemId="images" [level]="1" [expandable]="true" [open]="expanded['images']" [setSize]="2" [posInSet]="2">
<certara-icon slot="icon" variant="regular" [name]="expanded['images'] ? 'folder-open' : 'folder'" class="text-tertiary" role="presentation"></certara-icon>
Images
</certara-tree-item>
<certara-tree-item *ngIf="expanded['images']" itemId="photo-1" [level]="2" [setSize]="2" [posInSet]="1">photo-1.png</certara-tree-item>
<certara-tree-item *ngIf="expanded['images']" itemId="photo-2" [level]="2" [setSize]="2" [posInSet]="2">photo-2.png</certara-tree-item>
</certara-tree>
`,
})
export class TreeExampleComponent {
expanded: Record<string, boolean> = {docs: true, drafts: false, images: false};
active = 'summary';
}

The default example needs a script so folders open and close and the active row moves. Place it in the same container as the tree, right after </certara-tree>:

<script>
(() => {
const tree = document.currentScript.parentElement.querySelector('certara-tree');
// Hides a row when any folder above it is collapsed.
const syncVisibility = () => {
let collapsedLevel = null;
tree.querySelectorAll('certara-tree-item').forEach(row => {
if (collapsedLevel !== null && row.level > collapsedLevel) {
row.hidden = true;
return;
}
row.hidden = false;
collapsedLevel = row.expandable && !row.open ? row.level : null;
});
};
tree.addEventListener('certaraTreeExpandedChange', ({ detail }) => {
const row = tree.querySelector('[item-id="' + detail.id + '"]');
row.open = detail.expanded;
// The folder icon does not follow the open state on its own, so swap it here too.
const icon = row.querySelector('[slot="icon"]');
if (icon) icon.name = detail.expanded ? 'folder-open' : 'folder';
syncVisibility();
});
tree.addEventListener('certaraTreeItemSelect', ({ detail }) => {
tree.querySelectorAll('certara-tree-item').forEach(row => (row.active = row.itemId === detail.id));
});
})();
</script>

Listen for the tree’s events and update the rows yourself, or render rows from a TreeModel, which handles expansion and lazy loading for you:

import {TreeModel} from '@certara/certara-ui';
const model = new TreeModel({loadChildren: (id) => fetchFolder(id)});
model.setRoots(rootFolders);
tree.addEventListener('certaraTreeExpandedChange', (e) => model.setExpanded(e.detail.id, e.detail.expanded));
model.subscribe(() => render(model.visibleRows)); // keyed: reuse existing <certara-tree-item>s by row.id
HTML
React
Angular

Virtualization renders only the rows inside the scroll viewport. In plain HTML, keep the visible rows in a scroll container inside <certara-tree>, add spacer elements above and below so the scrollbar reflects the full list, and set --certara-tree-row-height to the fixed row height so the spacer math matches. Handle certaraTreeEdgeNavigate so Home, End, and arrow keys can reach rows outside the window: scroll the target into view, then call focusItem(id).

useTreeRows(model) subscribes a component to a TreeModel (React 16.8+).

import {useMemo, useState} from 'react';
import {CertaraTree, CertaraTreeItem, TreeModel, useTreeRows} from '@certara/certara-ui-react';
const placeholderText = {loading: 'Loading…', empty: 'Empty folder', error: 'Could not load folder'};
function FileTree({roots, fetchFolder}) {
const model = useMemo(() => {
const m = new TreeModel({loadChildren: (id) => fetchFolder(id)});
m.setRoots(roots);
return m;
}, []);
const rows = useTreeRows(model);
const [selectedId, setSelectedId] = useState(null);
return (
<CertaraTree aria-label="Files" onCertaraTreeExpandedChange={(e) => model.setExpanded(e.detail.id, e.detail.expanded)} onCertaraTreeItemSelect={(e) => setSelectedId(e.detail.id)}>
{rows.map((row) => (
<CertaraTreeItem key={row.id} itemId={row.id} level={row.level} expandable={row.expandable} open={row.expanded} loading={row.loading} placeholder={row.kind !== 'node'} setSize={row.setSize} posInSet={row.posInSet} active={row.id === selectedId}>
{row.kind === 'node' ? row.data.name : placeholderText[row.kind]}
</CertaraTreeItem>
))}
</CertaraTree>
);
}

Put the scroll container inside <certara-tree> and render only the virtualizer’s rows. Handle certaraTreeEdgeNavigate so Home/End (and arrows past the rendered window) can reach rows that are not in the DOM.

HTML
React
Angular
const viewportRef = useRef(null);
const treeRef = useRef(null);
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => viewportRef.current,
estimateSize: () => 32,
getItemKey: (index) => rows[index].id,
});
const onEdgeNavigate = (e) => {
const {key, fromId} = e.detail;
const index = key === 'Home' ? 0 : key === 'End' ? rows.length - 1 : model.indexOf(fromId) + (key === 'ArrowUp' ? -1 : 1);
if (!rows[index]) return;
virtualizer.scrollToIndex(index);
requestAnimationFrame(() => treeRef.current.focusItem(rows[index].id));
};
<CertaraTree ref={treeRef} aria-label="Files" onCertaraTreeEdgeNavigate={onEdgeNavigate} /* ...other handlers */>
<div ref={viewportRef} style={{height: 400, overflowY: 'auto'}}>
<div style={{height: virtualizer.getTotalSize(), position: 'relative'}}>
<div style={{transform: `translateY(${virtualizer.getVirtualItems()[0]?.start ?? 0}px)`}}>
{virtualizer.getVirtualItems().map(({index}) => {
const row = rows[index];
return <CertaraTreeItem key={row.id} itemId={row.id} level={row.level} /* ... */ />;
})}
</div>
</div>
</div>
</CertaraTree>;

Create the model, set its roots in ngOnInit, and subscribe to re-read visibleRows on every change.

The virtualized version wraps the rows in a cdk-virtual-scroll-viewport inside <certara-tree>, so only the rows in view are in the DOM. It also handles certaraTreeEdgeNavigate, so Home, End, and arrow keys can reach rows that are not rendered. Set templateCacheSize: 0 so the CDK does not recycle a focused row’s element for a different row.

file-tree-virtual.component.ts
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Input,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import {
CdkVirtualScrollViewport,
ScrollingModule,
} from '@angular/cdk/scrolling';
import {CertaraUiAngularModule} from '@certara/certara-ui-angular';
import {
TREE_ROW_HEIGHT,
TreeEdgeNavigateDetail,
TreeModel,
TreeNodeInput,
TreeRow,
} from '@certara/certara-ui';
interface Folder {
name: string;
}
@Component({
selector: 'app-file-tree-virtual',
standalone: true,
imports: [ScrollingModule, CertaraUiAngularModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<certara-tree
#tree
aria-label="Files"
(certaraTreeExpandedChange)="model.setExpanded($event.detail.id, $event.detail.expanded)"
(certaraTreeItemSelect)="selectedId = $event.detail.id"
(certaraTreeEdgeNavigate)="onEdgeNavigate($event.detail)">
<cdk-virtual-scroll-viewport
[itemSize]="rowHeight"
[style.--certara-tree-row-height]="rowHeight + 'px'"
style="height: 400px">
<certara-tree-item
*cdkVirtualFor="let row of rows; trackBy: trackById; templateCacheSize: 0"
[itemId]="row.id"
[level]="row.level"
[expandable]="row.expandable"
[open]="row.expanded"
[loading]="row.loading"
[placeholder]="row.kind !== 'node'"
[setSize]="row.setSize"
[posInSet]="row.posInSet"
[active]="row.id === selectedId">
{{ row.kind === 'node' ? row.data?.name : row.kind === 'loading' ? 'Loading…' : 'Empty folder' }}
</certara-tree-item>
</cdk-virtual-scroll-viewport>
</certara-tree>
`,
})
export class FileTreeVirtualComponent implements OnInit, OnDestroy {
// Row height in px, from the library so the virtualizer and the tree rows share one value.
// The virtualizer needs it before any row renders, so it cannot be measured from the DOM.
rowHeight = TREE_ROW_HEIGHT;
@Input() roots: TreeNodeInput<Folder>[] = [];
@Input() fetchFolder!: (id: string) => Promise<TreeNodeInput<Folder>[]>;
@ViewChild('tree', {read: ElementRef}) tree!: ElementRef<HTMLCertaraTreeElement>;
@ViewChild(CdkVirtualScrollViewport) viewport!: CdkVirtualScrollViewport;
model = new TreeModel<Folder>({loadChildren: (id) => this.fetchFolder(id)});
rows: TreeRow<Folder>[] = [];
selectedId: string | null = null;
private unsubscribe = this.model.subscribe(() => {
this.rows = this.model.visibleRows;
this.cdr.markForCheck();
});
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.model.setRoots(this.roots);
}
trackById = (_: number, row: TreeRow<Folder>) => row.id;
onEdgeNavigate({key, fromId}: TreeEdgeNavigateDetail) {
if (fromId === null) return;
const index = this.model.indexOf(fromId);
const parentId = this.model.getParentId(fromId);
let target: number;
switch (key) {
case 'Home':
target = 0;
break;
case 'End':
target = this.rows.length - 1;
break;
case 'ArrowUp':
target = index - 1;
break;
case 'ArrowDown':
target = index + 1;
break;
case 'ArrowLeft':
target = parentId === null ? -1 : this.model.indexOf(parentId);
break;
default:
return;
}
// Skip loading, empty, and error placeholders, which cannot take focus.
const step = key === 'ArrowUp' || key === 'End' ? -1 : 1;
while (this.rows[target] && this.rows[target].kind !== 'node') {
target += step;
}
const row = this.rows[target];
if (!row) return;
this.viewport.scrollToIndex(target);
requestAnimationFrame(() => this.tree.nativeElement.focusItem(row.id));
}
ngOnDestroy() {
this.unsubscribe();
}
}

Keys pressed inside embedded controls (checkboxes, buttons, inputs) keep their native behavior. Otherwise, keyboard navigation is handled by the <certara-tree> container (arrow keys, Home, and End).

activeId

Attributeactive-id
Typestring
Defaultnull
Requiredfalse
DescriptionId of the row that holds the roving tab stop.

selectionFollowsFocus

Attributeselection-follows-focus
Typeboolean
Defaultfalse
Requiredfalse
DescriptionWhen true, moving focus with the keyboard also emits certaraTreeItemSelect.

selectionMode

Attributeselection-mode
Type"none" | "single"
Default'single'
Requiredfalse
Descriptionsingle emits certaraTreeItemSelect on click / Enter / Space; none never does.

toggleOnClick

Attributetoggle-on-click
Typeboolean
Defaultfalse
Requiredfalse
DescriptionWhen true, clicking an expandable row toggles it instead of selecting it.
NameDescriptionBubbles
certaraTreeActiveChangeEmitted when the row holding the tab stop changes.true
certaraTreeEdgeNavigateEmitted on Home / End, and when an arrow key’s target row is not rendered (e.g. scrolled out of a virtualized window). Scroll the target into view, then call focusItem(id).true
certaraTreeExpandedChangeEmitted when the user asks to expand or collapse a row. Update the row’s open in response.true
certaraTreeItemSelectEmitted when the user selects a row (click, Enter, Space, or focus with selection-follows-focus).true
NameSignatureDescription
focusItem(id: string) => Promise<boolean>Move focus to the row with this id and scroll it into view. Resolves false when the row is not rendered.

There are no slots available for this component

NameDescription
--certara-tree-row-heightHeight of each tree row. Inherits from any ancestor, so set it on certara-tree or a scroll container, and use the same value in virtualizer spacer math. Falls back to 33px.

One row of a <certara-tree>. Rows are flat siblings (not nested): depth comes from level, so the parent app can render exactly the visible rows, including through a virtualizer. Use TreeModel.visibleRows to get the rows and their props.

Keyboard focus (roving tabindex), expansion and selection events are handled by the enclosing <certara-tree>; this component only renders the row and its ARIA state.

Children are never moved: the row is a flex container and the “slots” below are placed with CSS order, so React / Angular can freely add, remove and reorder row content.

level sets the indentation via 1-based depth.
Level 1 Level 2 Level 3
<certara-tree aria-label="Files">
<certara-tree-item item-id="report" level="1">Level 1</certara-tree-item>
<certara-tree-item item-id="report" level="2">Level 2</certara-tree-item>
<certara-tree-item item-id="report" level="3">Level 3</certara-tree-item>
</certara-tree>
import { CertaraTree, CertaraTreeItem } from '@certara/certara-ui-react';
<CertaraTree aria-label="Files">
<CertaraTreeItem itemId="report" level={1}>Level 1</CertaraTreeItem>
<CertaraTreeItem itemId="report" level={2}>Level 2</CertaraTreeItem>
<CertaraTreeItem itemId="report" level={3}>Level 3</CertaraTreeItem>
</CertaraTree>
// In your module (e.g., app.module.ts)
import { CertaraUiAngularModule } from '@certara/certara-ui-angular';
@NgModule({
imports: [CertaraUiAngularModule]
})
// In your template:
<certara-tree aria-label="Files">
<certara-tree-item item-id="report" level="1">Level 1</certara-tree-item>
<certara-tree-item item-id="report" level="2">Level 2</certara-tree-item>
<certara-tree-item item-id="report" level="3">Level 3</certara-tree-item>
</certara-tree>
The default slot is the row label. Plain text or inline markup both work.
Documents
<certara-tree aria-label="Files">
<certara-tree-item item-id="docs" level="1">Documents</certara-tree-item>
</certara-tree>
import { CertaraTree, CertaraTreeItem } from '@certara/certara-ui-react';
<CertaraTree aria-label="Files">
<CertaraTreeItem itemId="docs" level={1}>Documents</CertaraTreeItem>
</CertaraTree>
// In your module (e.g., app.module.ts)
import { CertaraUiAngularModule } from '@certara/certara-ui-angular';
@NgModule({
imports: [CertaraUiAngularModule]
})
// In your template:
<certara-tree aria-label="Files">
<certara-tree-item item-id="docs" level="1">Documents</certara-tree-item>
</certara-tree>

active

Attributeactive
Typeboolean
Defaultfalse
Requiredfalse
DescriptionWhether the row is selected.

disabled

Attributedisabled
Typeboolean
Defaultfalse
Requiredfalse
DescriptionDisabled rows can be focused but not selected or expanded.

expandable

Attributeexpandable
Typeboolean
Defaultfalse
Requiredfalse
DescriptionWhether the row has children. Renders the expander chevron.

itemId

Attributeitem-id
Typestring
Defaultundefined
Requiredtrue
DescriptionUnique id of the row.

level

Attributelevel
Typenumber
Default1
Requiredfalse
Description1-based depth controls indentation.

loading

Attributeloading
Typeboolean
Defaultfalse
Requiredfalse
DescriptionTrue while this row’s children are loading.

open

Attributeopen
Typeboolean
Defaultfalse
Requiredfalse
DescriptionWhether the row’s children are shown.

placeholder

Attributeplaceholder
Typeboolean
Defaultfalse
Requiredfalse
DescriptionMarks a non-navigable row such as a loading, empty or error placeholder. Arrow keys skip it, but focusable content inside it is still reachable with Tab key.

posInSet

Attributepos-in-set
Typenumber
Defaultundefined
Requiredfalse
Description1-based position among siblings.

setSize

Attributeset-size
Typenumber
Defaultundefined
Requiredfalse
DescriptionNumber of siblings at this level.

There are no events available for this component

There are no public methods available for this component

NameDescription
defaultRow label / content.
endTrailing content such as badges or actions.
expanderReplaces the default chevron on expandable rows. Clicking it still toggles the row.
iconLeading icon, rendered after the expander.