Urgent.News

What's breaking now, across thousands of outlets.

Culture

Build a Modern Chat UI with Just CSS and JavaScript

Chat interfaces are everywhere now — support widgets, team tools, AI assistants, in-app messaging. They all share the same underlying patterns, and none of them actually require a framework or a component library to get right. A well-built chat UI is mostly a handful of CSS decisions and a couple of small, deliberate JavaScript behaviors. This walks through building one from scratch: the markup,…

Abstract editorial illustration

Building a modern chat interface requires only a few CSS decisions and some small JavaScript behaviors. Here is a step-by-step guide on how to create one from scratch, covering markup, bubble styling, a typing indicator, smart auto-scrolling, message grouping, dark mode, and accessibility details.

The core ideas behind a well-built chat UI are distinct sent vs. received messages, visually grouped consecutive messages from the same person, new messages not fighting for scroll position, a typing indicator that communicates presence without being distracting, and screen readers being informed about new messages without interrupting the user's current task.

To build a chat UI, start with semantic HTML. Use a `<section>` element with an `aria-label` of "Conversation" to wrap the chat log. Inside this section, create an `<ul>` element with the class "chat-log" and the attribute `aria-live="polite"`. Within this list, each message should be an `<li>` element with the class "message" and an additional class of either "message--received" or "message--sent" to indicate the sender.

The list item should contain an `<img>` element for the avatar, an empty `<div>` element for the bubble containing the message text and time using a `<p>` and `<time>` element respectively.

For styling, remove the default list styling and display the chat log as a flex container. Use flex-direction: column to stack the messages vertically and add some gap and padding for spacing. Each message should also be a flex container with align-items set to flex-end to align the text and time at the bottom. Limit the max-width to 75% to prevent messages from stretching edge-to-edge, creating a more "conversation-like" reading experience.

Differentiate sent and received messages by flipping the flex-direction of the message container for sent messages and adjusting the border radius on the bubble container to create a "tail" effect. Use CSS custom properties to define background colors and text colors for each message type. Add a time element within the bubble with a slightly reduced font size and opacity to create a subtle visual cue that the message includes a timestamp.

A typing indicator can be achieved with pure CSS. Create a `.typing` element to hold the dots and apply animation to make them bounce. Use `@keyframes` to define the animation sequence that includes staggered delays for each dot. The animation runs on the compositor thread, ensuring smooth performance without layout reflows. This animation should only run when a typing event is detected.

For auto-scrolling, don't scroll to the bottom automatically on every new message. Instead, only scroll when the user is actively reading the chat, such as when the scrollbar is visible or when the user focuses on the chat window. Implement this by adding event listeners for scroll or focus events and only adjusting the scroll position if the conditions are met.

Finally, ensure the chat UI is accessible by using proper ARIA attributes, providing visual cues for new messages, and implementing the typing animation in a way that is perceivable by screen readers. By focusing on these five core ideas and implementing them with semantic HTML and CSS, you can create a chat interface that feels considerate and user-friendly without relying on any frameworks or component libraries.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Culture

More from Monday 3 August →