Timeline
oge-timeline draws a chronological sequence along an axis — an order history, an activity feed, release notes.
It renders an ordered list: the order is the meaning, and a list announces its size and each position. There is no APG timeline pattern and nothing in it is interactive by itself, so it adds no roles and no keyboard model — links inside an entry stay in the Tab order, and the marker and connector are aria-hidden.
Vertical timeline
The default: content on the end side of a vertical axis. A Date time is formatted in the app locale (medium date + short time, or dateFormat) and written into <time datetime> from its local fields; a string time is shown verbatim.
- Order placedPayment confirmed by card ending 4242.
- PackedTwo parcels, 3.4 kg.
- ShippedHanded to the carrier in Istanbul.
- Delivery expected
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeTimeline } from '@oge-ui/layout';
import type { OgeTimelineItem } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeTimeline],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- An ordered list: screen readers announce "list, 4 items" and each
position. Date times are formatted in the app locale and written into
<time datetime>; a string time is shown verbatim. -->
<oge-timeline [items]="order" ariaLabel="Order history" />
`,
})
export class Demo {
protected readonly order: OgeTimelineItem[] = [
{
key: 'placed',
title: 'Order placed',
description: 'Payment confirmed by card ending 4242.',
time: new Date(2026, 2, 14, 9, 30),
severity: 'success',
},
{
key: 'packed',
title: 'Packed',
description: 'Two parcels, 3.4 kg.',
time: new Date(2026, 2, 14, 16, 5),
},
{
key: 'shipped',
title: 'Shipped',
description: 'Handed to the carrier in Istanbul.',
time: new Date(2026, 2, 15, 8, 0),
},
{
key: 'delivered',
title: 'Delivery expected',
time: 'Tomorrow, 10:00–14:00',
variant: 'outlined',
severity: 'neutral',
},
];
}Alignment & alternating
start / end keep one side (logical, so RTL mirrors); alternate swaps per entry starting at the end side, alternate-reverse at the start side, and each entry's time — or its opposite text — moves to the other side of the axis.
- Order placedPayment confirmed by card ending 4242.
- PackedTwo parcels, 3.4 kg.
- ShippedHanded to the carrier in Istanbul.
- Delivery expected
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeTimeline } from '@oge-ui/layout';
import type { OgeTimelineAlign, OgeTimelineItem } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeTimeline],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- start / end keep one side (logical, so RTL mirrors);
alternate swaps per entry and moves each time — or the entry's
opposite text — to the other side of the axis. -->
<oge-timeline [items]="order" [align]="align()" ariaLabel="Order history" />
<label>
Align
<select (change)="align.set($any($event.target).value)">
<option value="end">end</option>
<option value="start">start</option>
<option value="alternate" selected>alternate</option>
<option value="alternate-reverse">alternate-reverse</option>
</select>
</label>
`,
})
export class Demo {
protected readonly align = signal<OgeTimelineAlign>('alternate');
protected readonly order: OgeTimelineItem[] = [
{
key: 'placed',
title: 'Order placed',
description: 'Payment confirmed by card ending 4242.',
time: new Date(2026, 2, 14, 9, 30),
severity: 'success',
},
{
key: 'packed',
title: 'Packed',
description: 'Two parcels, 3.4 kg.',
time: new Date(2026, 2, 14, 16, 5),
},
{
key: 'shipped',
title: 'Shipped',
description: 'Handed to the carrier in Istanbul.',
time: new Date(2026, 2, 15, 8, 0),
},
{
key: 'delivered',
title: 'Delivery expected',
time: 'Tomorrow, 10:00–14:00',
variant: 'outlined',
severity: 'neutral',
},
];
}Horizontal timeline
The entries share a row with the connector between the markers; alternating puts the opposite text above and the content below. When the row does not fit, the list scrolls inline instead of widening the page.
- Jan 20261.0Data grid
- Jun 20261.1React layer
- Oct 20261.2Feedback components
- Q1 20271.3Planned
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeTimeline } from '@oge-ui/layout';
import type { OgeTimelineItem } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeTimeline],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- The entries share a row and the list scrolls inline when they do
not fit, instead of widening the page on a phone. -->
<oge-timeline
orientation="horizontal"
align="alternate"
[items]="releases"
ariaLabel="Release history"
/>
`,
})
export class Demo {
protected readonly releases: OgeTimelineItem[] = [
{ title: '1.0', description: 'Data grid', opposite: 'Jan 2026' },
{ title: '1.1', description: 'React layer', opposite: 'Jun 2026' },
{ title: '1.2', description: 'Feedback components', opposite: 'Oct 2026' },
{ title: '1.3', description: 'Planned', opposite: 'Q1 2027', variant: 'outlined' },
];
}Markers, icons & severities
severity colours the marker with the suite vocabulary, variant: 'outlined' draws a ring for pending or future entries, and icon (SVG path data) draws a larger marker with the glyph inside. Markers are decoration — the text carries the meaning, so forced colours lose nothing.
- Build passed
- Canary at 5 %
- Error rate above budget
- Rolled back
- Retry scheduled
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeTimeline } from '@oge-ui/layout';
import type { OgeTimelineItem } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeTimeline],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- severity colours the marker, variant="outlined" draws a ring
(pending / future entries), icon (SVG path data) draws a larger marker
with the glyph inside. Markers are aria-hidden decoration, so the
meaning must also be in the text. -->
<oge-timeline [items]="deploys" ariaLabel="Deployments" />
`,
})
export class Demo {
protected readonly deploys: OgeTimelineItem[] = [
{ title: 'Build passed', time: '09:12', severity: 'success', icon: 'M20 6 9 17l-5-5' },
{ title: 'Canary at 5 %', time: '09:20', severity: 'accent', icon: 'M4 22V4m0 0h13l-2 4 2 4H4' },
{ title: 'Error rate above budget', time: '09:41', severity: 'warning', icon: 'M12 9v4m0 3.5h.01' },
{ title: 'Rolled back', time: '09:43', severity: 'danger', icon: 'M3 12a9 9 0 1 0 3-6.7L3 8m0-5v5h5' },
{ title: 'Retry scheduled', time: '14:00', severity: 'neutral', variant: 'outlined' },
];
}Custom templates
Structural slot directives replace the content, the marker or the opposite text per entry, with the item, its index, its side and first/last flags in the context. Content is ordinary flow content: the link stays in the Tab order.
- Create a workspaceName it and invite your team.Learn more
- Connect a data sourceREST, OData or a plain array.Learn more
- Publish a dashboardShare a read-only link.Learn more
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeTimeline, OgeTimelineContentTemplate, OgeTimelineMarkerTemplate } from '@oge-ui/layout';
import type { OgeTimelineItem } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeTimeline, OgeTimelineContentTemplate, OgeTimelineMarkerTemplate],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Templates replace the content and the marker per entry. Content
is ordinary flow content — the link stays in the Tab order (the
timeline adds no keyboard model). -->
<oge-timeline [items]="steps" ariaLabel="Onboarding">
<ng-template ogeTimelineMarkerTemplate let-index="index">
<span class="step-number">{{ index + 1 }}</span>
</ng-template>
<ng-template ogeTimelineContentTemplate let-item>
<strong>{{ item.title }}</strong>
<span>{{ item.description }}</span>
<a href="#timeline-help">Learn more</a>
</ng-template>
</oge-timeline>
`,
})
export class Demo {
protected readonly steps: OgeTimelineItem[] = [
{ title: 'Create a workspace', description: 'Name it and invite your team.' },
{ title: 'Connect a data source', description: 'REST, OData or a plain array.' },
{ title: 'Publish a dashboard', description: 'Share a read-only link.' },
];
}