Skip to content

Timeline

<loomi-timeline-item> entries grouped in <loomi-timeline> — display events in chronological order, like an activity feed.

Terminal window
npm install @loomidev/timeline lit
import "@loomidev/timeline";
<loomi-timeline>
<loomi-timeline-item date="10 days ago" content="You signed up"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep called"></loomi-timeline-item>
<loomi-timeline-item content="Account is being reviewed"></loomi-timeline-item>
<loomi-timeline-item content="Account activated"></loomi-timeline-item>
</loomi-timeline>

The trailing connector line is removed from the last item automatically — there’s nothing to set.

content only takes plain text. For HTML — bold text, links, code, etc. — put it in the default slot instead; it overrides the content attribute.

You signed up with email user@example.com
<loomi-timeline>
<loomi-timeline-item date="10 days ago">
<strong>You signed up</strong> with email <code>user@example.com</code>
</loomi-timeline-item>
</loomi-timeline>
<loomi-timeline anchor="big">
<loomi-timeline-item date="10 days ago" content="You signed up"></loomi-timeline-item>
</loomi-timeline>

Filled circles mark a step as done; on a big anchor, a checkmark appears too.

<loomi-timeline anchor="big">
<loomi-timeline-item completed date="10 days ago" content="You signed up"></loomi-timeline-item>
<loomi-timeline-item completed date="8 days ago" content="Customer rep assigned"></loomi-timeline-item>
<loomi-timeline-item content="Account is being reviewed"></loomi-timeline-item>
</loomi-timeline>

stacked on the <loomi-timeline> wrapper puts dates above content instead of beside it, and is shared by every child item.

<loomi-timeline stacked>
<loomi-timeline-item date="just now" content="Database server restarted"></loomi-timeline-item>
<loomi-timeline-item date="30 minutes ago" content="2 endpoints are failing — check the logs"></loomi-timeline-item>
<loomi-timeline-item date="Yesterday" content="Data recovery completed with 2 errors"></loomi-timeline-item>
</loomi-timeline>

completed on the wrapper marks every item as done at once — override a single item by setting completed="false" directly on it.

<loomi-timeline stacked completed anchor="big">
<loomi-timeline-item date="just now" content="Database server restarted"></loomi-timeline-item>
<loomi-timeline-item date="Yesterday" content="Data recovery" completed="false"></loomi-timeline-item>
</loomi-timeline>

Icons and avatars only render when anchor="big".

<loomi-timeline anchor="big" completed>
<loomi-timeline-item date="10 days ago" content="You signed up" icon="bell-alert"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned" icon="bolt"></loomi-timeline-item>
<loomi-timeline-item content="Account is being reviewed" icon="key" completed="false"></loomi-timeline-item>
</loomi-timeline>
<loomi-timeline anchor="big">
<loomi-timeline-item date="10 days ago" content="You signed up" avatar="/avatars/ada.jpg"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned" avatar="/avatars/rep.jpg"></loomi-timeline-item>
</loomi-timeline>

Set placement on the <loomi-timeline> wrapper — it’s shared by every child item. Default is left (anchor on the left, content to its right).

<loomi-timeline placement="left" anchor="big">
<loomi-timeline-item date="10 days ago" content="You signed up"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned"></loomi-timeline-item>
</loomi-timeline>

right mirrors the whole layout — content on the left, anchor on the right:

<loomi-timeline placement="right" anchor="big">
<loomi-timeline-item date="10 days ago" content="You signed up"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned"></loomi-timeline-item>
</loomi-timeline>

alternate centers a single spine and alternates each item to its left and right — which side an item lands on is resolved from its position among its siblings, so there’s nothing to set per item:

<loomi-timeline placement="alternate" anchor="big">
<loomi-timeline-item date="10 days ago" content="You signed up"></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned"></loomi-timeline-item>
<loomi-timeline-item date="2 days ago" content="Account is being reviewed"></loomi-timeline-item>
<loomi-timeline-item content="Account activated"></loomi-timeline-item>
</loomi-timeline>
<loomi-timeline>
<loomi-timeline-item date="10 days ago" content="You signed up" color="error" completed></loomi-timeline-item>
<loomi-timeline-item date="8 days ago" content="Customer rep assigned" color="warning"></loomi-timeline-item>
<loomi-timeline-item content="Account is being reviewed" color="success"></loomi-timeline-item>
</loomi-timeline>

For the library-wide baseline, see Foundations — Accessibility.

For the shared container and viewport rules, see Foundations — Responsive behavior.

For theme activation, token overrides, and contrast guidance, see Foundations — Dark mode.

AttributeDefaultDescription
date(blank)Date string.
content(blank)Entry text (or use the default slot).
completedfalseFilled anchor (+ check when anchor="big"). (boolean)
anchorsmallsmall | big (big enables icons/avatars).
icon(blank)Anchor icon name (big anchor).
avatar(blank)Anchor image URL (big anchor).
stackedfalseDate above content vs. in a left column. (boolean)
placementleftleft | right | alternate — set on the <loomi-timeline> wrapper instead; it applies to every child.
colorprimaryAny loomi color.

The trailing connector line and alternate left/right placement are both resolved automatically from the item’s position among its siblings — there is no last or per-item side attribute to set.

Shares stacked, completed, anchor, icon, color, and placement with all children.

SlotDescription
(default)Content placed inside the component.
contentCustom body content.
<loomi-timeline stacked anchor="big" color="error" placement="alternate" completed>
<loomi-timeline-item
date="9 days ago"
avatar="/avatars/me.jpg"
content="I am a timeline"
completed
></loomi-timeline-item>
<loomi-timeline-item date="2 days ago" content="Still going" completed="false"></loomi-timeline-item>
</loomi-timeline>

<loomi-timeline-item> and <loomi-timeline> are standard custom elements, so the browser can use them in plain HTML, Blade, React, Vue, Angular, Svelte, Astro, and most other frameworks. The important beginner rule is: install the package, import it once before the tag is rendered, then write the Loomi tag in your template.

Run install commands from the app where you want to use this component. That means the folder that contains that app’s package.json. Do not run these install commands from packages/timeline unless you are editing LoomiUI itself.

Terminal window
cd /path/to/your-app
npm install @loomidev/timeline lit

If you are contributing to LoomiUI itself, first move to the top-level components folder. That is where the main package.json for all packages lives, and pnpm --filter ... commands should be run from there:

Terminal window
cd /path/to/your-copy-of-loomiui/components
pnpm --filter @loomidev/timeline build
pnpm --filter @loomidev/timeline typecheck

Use the CDN version for prototypes, documentation pages, or a quick reproduction. The import map tells the browser where to find Lit, which Loomi components use internally.

<script type="importmap">
{ "imports": { "lit": "https://esm.sh/lit@3.3.3", "lit/": "https://esm.sh/lit@3.3.3/" } }
</script>
<script type="module" src="https://esm.sh/@loomidev/timeline"></script>
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>

In Vite, Webpack, Parcel, Rollup, or a framework build pipeline, install the package and import it once in your main app JavaScript file. After that, you can use the Loomi tag anywhere in your app.

import "@loomidev/timeline";

Run the install command from your Laravel project root, then import the component in resources/js/app.js. If your project uses Laravel Vite, npm run dev and npm run build should also be run from the Laravel project root.

Terminal window
cd /path/to/your-laravel-app
npm install @loomidev/timeline lit
npm run dev
resources/js/app.js
import "@loomidev/timeline";
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>

React can render Loomi tags directly. If you are on React 18, or if you need to pass arrays, objects, or functions, use a ref and assign those values after the component mounts.

import "@loomidev/timeline";
export function LoomiExample() {
return (
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>
);
}

If TypeScript does not recognize the Loomi tag in JSX, add it to your app’s JSX type declarations.

Import the package in the component that uses it, or once in your main Vue file. Vue templates can use Loomi tags directly. For arrays, objects, or functions, pass the value as a JavaScript property instead of as plain text.

<script setup>
import "@loomidev/timeline";
</script>
<template>
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>
</template>

If Vue warns that the tag is an unknown component, configure compilerOptions.isCustomElement for tags that start with loomi- in your Vite or Vue config.

Import the package once and tell Angular to allow custom HTML tags with CUSTOM_ELEMENTS_SCHEMA. For NgModule apps, add the schema to the module instead of the standalone component.

app.component.ts
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
import "@loomidev/timeline";
@Component({
selector: "app-root",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>
`,
})
export class AppComponent {}

Svelte can import the package inside a component script. Astro can import it in the frontmatter of the page or layout where the tag appears.

<script>
import "@loomidev/timeline";
</script>
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>
---
import "@loomidev/timeline";
---
<loomi-timeline>
<loomi-timeline-item date="09:00" completed>Order placed</loomi-timeline-item>
<loomi-timeline-item date="10:30">Payment confirmed</loomi-timeline-item>
</loomi-timeline>

Frameworks such as Next.js, Nuxt, SvelteKit, and Astro sometimes render HTML on the server before browser-only code runs. If your framework complains, move the Loomi import to client-side code. In Next.js, that usually means a component with "use client"; in Nuxt, it often means a .client.ts plugin.

  • @loomidev/core
  • @loomidev/icons