Timelines
Activity timeline used on quote, transaction, partner, and user detail pages. Entries are grouped by date with sticky headers and avatars. On desktop, entries alternate left/right by person.
Activity timeline
Entries grouped by date with sticky day headers. Each person group has a sticky avatar on the timeline line. On mobile, all entries are on the right. On desktop (sm+), entries alternate sides per person.
-
Sarah Johnson accepted quote
Q-2024-001234 — 42 Maple Drive -
Sarah Johnson added a note to transaction
T-2024-005678 — 18 Oak LaneClient confirmed completion date of 15 April
-
James Wilson created quote
Q-2024-001234 — 42 Maple Drive
-
Sarah Johnson updated transaction status
T-2024-005678 — 18 Oak LaneIn progress → Awaiting searches
@foreach (var group in Model)
{
var label = group.Key == today
? "Today"
: group.Key == yesterday
? "Yesterday"
: group.Key.ToString("dddd, MMMM d");
<!-- Day group -->
<div>
<!-- Sticky day label -->
<div class="sticky z-10 -mx-6 px-6 text-center
pt-4 pb-4 mb-4 bg-gradient-to-b
from-white from-60% to-transparent
dark:from-gray-800"
style="top: calc(4rem + var(--env-banner-h, 0px))">
<span class="font-bold text-gray-900
dark:text-white">@label</span>
</div>
<!-- Timeline container -->
<div class="ms-5 sm:ms-0">
<!-- Person group -->
<div class="overflow-y-clip pt-2 pb-10">
<!-- Sticky avatar -->
<div class="sticky z-[9] h-0
pointer-events-none"
style="top: calc(7.5rem
+ var(--env-banner-h, 0px))">
<a href="@userUrl"
class="absolute -start-5
sm:start-1/2
sm:-translate-x-1/2
-top-2 flex items-center
justify-center w-10 h-10
rounded-full ring-4
ring-white dark:ring-gray-800
pointer-events-auto">
<img src="/api/avatars/@user.Id"
class="w-10 h-10 rounded-full
object-cover" />
<span style="display:none">
@initials
</span>
</a>
</div>
<!-- Activities -->
<ol class="relative">
<!-- Timeline line -->
<div class="absolute left-0 sm:left-1/2
top-0 bottom-0 w-px
bg-gray-300 dark:bg-gray-600
sm:-translate-x-px"></div>
<li class="ms-8 sm:ms-0
sm:grid sm:grid-cols-2">
<!-- Dot on timeline -->
<span class="absolute -start-1.5
sm:start-1/2
sm:-translate-x-1/2
flex items-center
justify-center w-3 h-3
rounded-full ring-4
ring-white dark:ring-gray-800
bg-secondary-100
dark:bg-gray-500
mt-1.5"></span>
<!-- Content: left side (even) -->
<div class="pt-0.5 sm:pt-2
sm:text-right sm:pe-8">
<time>@time</time>
<p>
<a href="@userUrl"
class="font-medium
text-fg-brand">
@user.FirstName
@user.LastName
</a>
@activity.Description
</p>
<a href="@entityUrl"
class="text-fg-brand">
@activity.EntityDisplayName
</a>
</div>
<!-- Content: right side (odd) -->
<div class="sm:col-start-2
pt-0.5 sm:pt-2
sm:ps-8">
...
</div>
</li>
</ol>
</div>
</div>
</div>
}
Change detail
When an activity has a ChangeDetail,
it is shown as an inline pill below the description.
In progress → Awaiting searches
@if (!string.IsNullOrEmpty(activity.ChangeDetail))
{
<p class="mt-1.5 text-sm text-gray-500
dark:text-gray-400 bg-gray-100
dark:bg-gray-700/50 rounded-lg
px-3 py-2 inline-block">
@activity.ChangeDetail
</p>
}
Layout anatomy
Key structural elements of the timeline.
| Element | Classes | Description |
|---|---|---|
| Day header | sticky z-10 text-center pt-4 pb-4 mb-4 bg-gradient-to-b from-white from-60% |
Sticky label with fade gradient so content scrolls beneath |
| Timeline line | absolute left-0 sm:left-1/2 top-0 bottom-0 w-px bg-gray-300 |
Vertical line connecting entries, centred on desktop |
| Dot | w-3 h-3 rounded-full ring-4 ring-white bg-secondary-100 |
Small dot on the timeline line per activity |
| Sticky avatar | sticky z-[9] h-0 pointer-events-none |
Avatar follows during scroll through a person's activities |
| Person group | overflow-y-clip pt-2 pb-10 |
Contains sticky avatar scope, pb-10 gives exit room |
| Entry (left) | sm:text-right sm:pe-8 |
Even person groups render on the left side |
| Entry (right) | sm:col-start-2 sm:ps-8 |
Odd person groups render on the right side |
Key rules
- Entries are grouped by date (Today, Yesterday, or
dddd, MMMM d) - Within each day, consecutive entries by the same user form a "person group"
- Person groups alternate sides on desktop (even = left/right-aligned, odd = right/left-aligned)
- The avatar is sticky within its person group via
overflow-y-clipon the group container - On mobile, all content is on the right (
ms-8) with the line on the left - User names link to the user profile page
- Entity display names link to the quote/transaction/partner page
- Change details use a subtle gray pill (
bg-gray-100 rounded-lg px-3 py-2) - Person group indices carry across day boundaries to maintain consistent side placement