048

Team Chat App Template

£10

A dark-mode team chat web-app UI whose conversation plays itself: a choreographed GSAP replay of launch morning with typing indicators, landing messages, popping reactions, a mention sweep and a thread that opens on cue, inside a full app shell with channel switching, a composer and a mobile drawer.

intermediate
6 more details
timed-streamstaggerhover-effectclick-toggletext-animation 4 colour palettes
Team Chat App Template - website template preview

This template reads better at your own screen size than in the frame below:

About this template

A complete dark-mode team chat interface: not a marketing page for a chat product, but the app itself, ready to drive a product demo, a hero screenshot that moves, an onboarding tour, or a portfolio piece that proves you can build real UI. The fictional product is Treeline, and the workspace belongs to Juniper Supply Co., a nine-person outdoor-gear studio on the morning they launch a new pack. The shell is all there: a slim workspace rail, a channel and DM sidebar with presence dots and unread badges, the message pane with hover toolbars, reactions, CSS-drawn attachment cards and a composer, and a thread panel that opens on the right.

Read the full template overview

The signature is the replay. The launch-morning conversation in #launch-day plays itself: a typing indicator appears for a named teammate, the message lands and shunts the list open, reactions pop on with an overshoot, an @mention sweeps a highlight across the row, the thread opens itself mid-sequence, and presence dots flip as people come and go. A pause control sits in the channel header, and when the sequence ends it becomes Replay. Because the full transcript ships in the markup and JavaScript only reveals it on schedule, the page degrades to a complete readable document with scripting off.

The art direction is its own: a dark olive-khaki ground with warm bone ink and a single apricot accent for active states, mentions and badges, deliberately refusing Slack aubergine, Discord blurple and hacker-terminal green. Funnel Display does the wordmark and channel headers, Funnel Sans does the UI, Oxygen Mono does timestamps and code.

To make it yours, rewrite the transcripts directly in the HTML (the conversation is markup, not a JS object), rename the people and channels, and re-point the nine avatar hues and the token block in one place. Four measured palettes ship: the olive default plus a warm paper light mode, a cool blue-slate Midnight and a plum Mulberry, each a complete restatement of every token that a single data-palette attribute on the html tag applies. The app deploys as static files with no build step.

What's included

14 items
  • Complete dark app shell: workspace rail, channel and DM sidebar, message pane with composer, and an openable thread panel
  • Signature self-playing conversation: typing indicators, messages that land and shunt the list, reactions popping with overshoot, a mention highlight sweep, a thread that opens itself and presence dots that flip, all on a choreographed GSAP schedule
  • Pause control in the channel header that becomes a Replay button when the sequence ends
  • The whole transcript ships in the markup: the replay only reveals it, so with JavaScript off the page reads as a complete plain document with every channel and the thread visible
  • Channel and DM switching with a quick GSAP transition, unread badges that clear with a pop, and per-channel composer placeholders
  • Working micro-interactions: hover (and focus, and tap) message toolbars, toggleable reaction chips that count you in and out, a send button that enables as you type, Enter to send
  • Nine coherent people with initials avatars, one measured hue per person, no photography anywhere; attachments are styled CSS/SVG cards
  • Mobile at 375px: the rail and sidebar collapse into an overlay drawer behind a hamburger, the thread becomes a full-screen overlay, and nothing pans horizontally
  • Keyboard complete: channels are real links, messages are focusable, focus shows what hover shows, Escape closes the thread and the drawer
  • Reduced motion gets the complete static app via both the CSS media query and the script's own motion branch: full transcript visible, replay control honestly inert
  • Four shipped palettes, each fully measured: the olive default, a warm paper light mode, a cool blue-slate Midnight and a plum Mulberry, applied with one data-palette attribute on the html tag
  • Olive-khaki, bone and apricot token block with RGB triples and a measured contrast table in the stylesheet header; a reskin is a token edit, never an rgba() hunt
  • Sections are independent: every selector is guarded, so the thread panel, the rail or whole channels can be deleted and the rest keeps working
  • Four measured palettes, each with its own display face (display sans, book serif, mono, rounded sans), swapped from one data-palette attribute

Perfect for

5 use cases
  • Product demos and interactive walkthroughs for chat, collaboration or community tools
  • A living hero or feature screenshot for a SaaS landing page, embedded as an iframe
  • Onboarding tours and explainer videos that need a believable, controllable conversation
  • Portfolio and case-study pieces that prove real app-UI craft, not another landing page
  • Learning pattern: choreographed DOM timelines, markup-first content with JS-only reveal, and app-shell responsive layout

How it works

5 sections

Structure

One script initialises the app with a single setup and teardown path, and one motion branch keyed on reduced motion plus a coarse-pointer flag, so exactly one behaviour branch runs on every device. Every querySelector is guarded and all listeners are stored in a Map for cleanup, so the thread panel, the rail or whole channels can be deleted and the rest keeps working. A synchronous head script adds a has-js class before first paint, and the main script removes it if GSAP never loads, so a blocked CDN degrades to the complete plain transcript.

The replay

The full launch-morning conversation ships in the markup; messages and reactions marked data-replay are pre-hidden by CSS only when JavaScript will replay them. A scheduler walks the steps in order: it shows a typing indicator for the named author, then reveals the message with a height tween that shunts the list open while the body drops in, pops reactions with a small overshoot, sweeps a gradient across the @mention row, flips presence dots, and opens the thread panel on cue. Pause kills the scheduler; at the end the same control resets the DOM and replays.

Switching channels

Every channel and DM is a real section in the markup and every sidebar entry is a real link to it. The script intercepts the click, swaps the active view with a quick fade, clears the channel's unread badge with a scale-out, updates the composer placeholder, and opens the list at its latest message. Without JavaScript those same links jump between the stacked transcripts as ordinary anchors.

Touch and keyboard

The hover toolbar is also reachable by keyboard focus, and on coarse pointers tapping a message toggles it instead. Channels are tabbable links, messages are focusable, the reaction chips are real buttons with aria-pressed, and Escape closes the thread panel and the mobile drawer. At phone widths the rail and sidebar become one overlay drawer behind a hamburger and the thread becomes a full-screen overlay.

Without JavaScript

The page is markup-first: with scripting off (or the CDN blocked) every channel's full transcript stacks into one readable document, the thread appears as a plain section, and nothing on the page is hidden. Reduced motion keeps the app shell live but static: the full transcript is visible from the start, nothing animates, and the replay control is disabled with an honest label.

Difficulty Intermediate
Colour palettes 4 ready-made palettes, each measured for AA contrast. Switch with one attribute, or copy the values you want into :root. Try them in the preview above.
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo, 9 September 2026. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Questions about this template

Is this a working chat app?

No, and it is important to be clear about that. It is a UI: a choreographed replay of one morning's conversation inside a complete app shell. Nothing sends, nothing connects, there is no backend. It is built as a product demo, a living hero for a SaaS landing page, or a portfolio piece proving app-UI craft.

Can I embed it in another page?

Yes, that is one of its intended uses: drop it in an iframe as a living feature screenshot on a landing page instead of a static image. The pause control in the channel header becomes a Replay button once the sequence ends, so an embedded demo does not just stop dead.

What does it look like with JavaScript off?

A complete plain document. The whole transcript ships in the markup and the replay only reveals it, so every channel and the thread are visible rather than an empty shell waiting for a script. Under reduced motion you get the same full transcript with the replay control honestly inert.

Does it work at phone size?

Yes, down to 375px. The workspace rail and sidebar collapse into an overlay drawer behind a hamburger, the thread becomes a full-screen overlay, and nothing pans horizontally. Escape closes both the thread and the drawer.

Do I need avatar photography for the people in it?

No. Nine coherent people are built from initials avatars with one measured hue each, and attachments are styled CSS and SVG cards, so there is no photography anywhere. Four fully measured palettes ship, applied with one attribute on the html tag.

Paid template

Purchase to unlock the code.

Buying Team Chat App Template opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£10 Standard license, unlimited projects

Browse free effects

Documentation

Quick Start

  1. Copy the whole folder to your web server (or any static host). There is no build step.

  2. The page works as-is. Open index.html and check the script tags before </body>:

Code snippet omitted: it ships with the download.

GSAP core is the only dependency. The page never scrolls the window (the message list is its own native scroller), so there is no smooth-scroll library to configure. The message lists carry data-lenis-prevent, so if you embed this app inside a page that runs Lenis, their wheel scrolling stays native.

  1. Make it yours:

    • Rename the product (rail logo, <title>, meta description) and the workspace (.ws-name).
    • Rewrite the transcripts. The conversation lives in the markup, not in a JavaScript object — see Editing the conversation below.
    • Rename the people: each message's .avatar class, initials, and .msg-name; the DM entries in the sidebar; the avatar hue tokens in :root.
    • Re-point or delete channels: each is one <section class="channel-view"> plus one sidebar link.
  2. For production, swap assets/script.js for assets/script.min.js.

Editing the conversation

Everything the reader sees ships in index.html, which is why the page still reads as a complete document with JavaScript disabled. There is no message data in the script.

A message is one <li class="msg"> inside a channel's <ol class="msg-list">:

Code snippet omitted: it ships with the download.

Optional blocks inside .msg-body: a <ul class="reactions"> of reaction buttons, an .attach card (the checklist and image-preview cards are plain CSS/SVG — restyle or delete them), and a .thread-bar button that opens the thread panel.

The replay is driven entirely by attributes on those same elements:

  • data-replay="N" — this message (or reaction <li>, or thread bar) is a replay step, landing in ascending order of N. Elements without it are visible from the start.
  • data-typing="Rosa Camacho" — show the typing indicator with this name for a beat before the message lands.
  • data-mention — sweep the highlight across this message when it lands (pair it with class="msg is-mention" and a <b class="mention">@Name</b> in the text).
  • data-thread-open — on a .thread-bar button: the replay opens the thread panel shortly after this step lands.
  • data-presence-flip="rosa:on" — flip every presence dot with data-presence="rosa" to online (or :away) as this step lands.

To re-choreograph, renumber data-replay in the order you want. The pacing dials are in assets/script.js: the typing beat (1.5 s), and gapAfter() (2.1 s after a message, 1.0 s before a reaction pops). If a reset needs different presence defaults than Rosa-away/June-on, update the two flipPresence calls in resetReplay().

Channels and DMs: each is a <section class="channel-view" id="view-NAME" data-view="NAME"> with its own header and list, paired with a sidebar <a data-channel-link="NAME" href="#view-NAME">. The href matters: without JavaScript the links jump between the stacked transcripts as ordinary anchors. Add a channel by copying a section and a link; delete one by deleting both. Unread counts are <span class="badge" data-badge="NAME"> in the link and clear when the channel is visited.

People: nine avatar hues live in :root (--av-priya--av-noor), used by the .av-* classes. The initials are plain text, so a new person is one token, one class and their initials. All nine fills are measured for the bone ink on top (6.57:1 to 8.71:1). The composer posts as Priya Nair; change the name, initials and class in sendMessage() in assets/script.js.

Palettes

The template ships with four palettes, each paired with its own display face so the four read as four identities rather than one design in four colours. Olive is the default; the other three live in the ALTERNATE PALETTES block at the end of assets/style.css.

Palette Character Ground Accent Display face
Olive (default) dark olive-khaki #22261A apricot Funnel Display 700, a display sans
Light warm paper light mode #FAF8F2 rust Literata 700, a warm book serif
Midnight cool blue-slate dark #141C2E ice blue JetBrains Mono 700, a mono
Mulberry warm plum dark #261A23 honey Nunito 800, a rounded sans

The face and the colours are independent tokens (--font-display and --font-display-weight sit in each block next to the colours), so keep the palette and swap the face, or the other way round. The display face carries the workspace name, channel and thread titles, the section labels and the avatar initials; the UI face (Funnel Sans) and the mono face (Oxygen Mono) are the same in every palette. All four display faces are in the Google Fonts <link> in index.html; a browser only downloads the one the page uses, but once you have settled, trim the link to the family you kept.

To use one, set the attribute on the <html> tag in index.html:

Code snippet omitted: it ships with the download.

That is the whole change. Each palette block restates every colour token and its RGB triple — the five surfaces, three inks, both accents, the presence green, the scrim and all nine avatar hues — and everything on the page already draws from those tokens, so nothing else needs touching. Every alternate is measured to the same standard as the default; the full contrast tables are in the header comment at the top of assets/style.css. (The theme picker you may have seen on the live demo is preview chrome on gsapvault.com only; it is not part of the template, which is why your download has no picker to remove.)

Once you have settled on one, the tidier end state is to copy its values into the :root block at the top of style.css and delete the alternates section, so the stylesheet states one palette rather than four.

Light is the interesting one. The token names are semantic, so the inversion needs no extra rules: the surfaces run the other way (the rail is the most toned, the message canvas near-white), --panel sits slightly deeper than the ground so row hovers stay visible, and the nine avatar fills flip to light tints because the near-black --ink is still the colour of the initials on them. The scrim keeps its own dark triple (--scrim-rgb) in every palette, because a light wash cannot dim the page behind the mobile drawer.

The default scheme

The scheme is five olive surfaces, three inks and two accents in the :root block of assets/style.css, each with an RGB triple beside it:

Code snippet omitted: it ships with the download.

Change a hex, change its triple, and the app reskins: every wash, rule and tint derives from the triples, so nothing is left half-repainted.

Constraints, all measured (the full table is in the stylesheet header):

  • The apricot measures 6.86:1 on the ground, so it may carry small text on the ground, rail, shade and panel — mentions, the thread bar and the replay control all rely on this. It never carries text on its own washes (5.25:1 on the mention tint, AA with no headroom) and never on a solid apricot fill: fills (the active channel, badges, the send button) always take --accent-ink at 7.48:1. If you darken --accent, re-measure it on --ground first.
  • --faint is the small-print floor (timestamps, labels). It sits at 4.78:1 on --panel-2, which clears AA by a hair; keep it off any surface lighter than that.
  • The leaf green is presence dots only, never text (5.54:1 worst case against --panel-2, past the 3:1 non-text floor).

Type

  • Funnel Display does the identity moments: wordmark tiles, workspace name, channel headers, avatar initials, section labels.
  • Funnel Sans does every message and control.
  • Oxygen Mono does the data: timestamps, unread counts, file names, code, keyboard hints.

All three load from Google Fonts in the <head> of index.html and thumbnail.html.

Accessibility

  • Respects prefers-reduced-motion via both the CSS media query and a gsap.matchMedia branch: the full transcript is visible from the start (the replay pre-hide only applies when motion is allowed), nothing animates, and the replay control is disabled with an honest label.
  • Fully readable with JavaScript disabled: every channel's transcript stacks into one plain document, the thread appears as an ordinary section, and the sidebar links work as in-page anchors. If the GSAP CDN is blocked, the script removes the has-js class and the page degrades the same way.
  • Keyboard mirrors pointer everywhere: channels and DMs are real links, messages are focusable and focus shows the hover toolbar, reactions are buttons with aria-pressed, and Escape closes the thread panel and the mobile drawer. Focus rings are apricot at 6.86:1 on the ground.
  • On touch devices, tapping a message toggles its toolbar (hover is never the only path), and the drawer and thread become full-screen overlays at phone widths.
  • The typing indicator is aria-live="polite"; avatars and decorative glyphs are aria-hidden with the person's name always adjacent in real text.

Browser Support

Modern browsers (ES5-compatible script, flexbox, custom properties, 100svh). Not compatible with IE11.

Dependencies

  • GSAP 3.12+ (core only; no plugins)

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

Everything in the catalogue, plus everything we release next.