Koala logo Design

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.

Today
  1. Sarah Johnson accepted quote

    Q-2024-001234 — 42 Maple Drive
  2. Sarah Johnson added a note to transaction

    T-2024-005678 — 18 Oak Lane

    Client confirmed completion date of 15 April

  1. James Wilson created quote

    Q-2024-001234 — 42 Maple Drive
Yesterday
  1. Sarah Johnson updated transaction status

    T-2024-005678 — 18 Oak Lane

    In 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-clip on 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