Skip to content

feat(web): enhance user message contrast for better visual distinction#1159

Open
miaomaolei1114-debug wants to merge 3 commits intoMoonshotAI:mainfrom
miaomaolei1114-debug:feat/user-message-contrast
Open

feat(web): enhance user message contrast for better visual distinction#1159
miaomaolei1114-debug wants to merge 3 commits intoMoonshotAI:mainfrom
miaomaolei1114-debug:feat/user-message-contrast

Conversation

@miaomaolei1114-debug
Copy link

@miaomaolei1114-debug miaomaolei1114-debug commented Feb 15, 2026

Summary

Improves visual distinction between user and assistant messages in the Web UI by adding high-contrast styling to user messages.

Problem

In long conversations, user messages (right-aligned) and assistant messages (left-aligned) have similar gray backgrounds, making it difficult to:

  • Quickly locate your own messages
  • Distinguish between user and assistant content
  • Navigate through conversation history

Solution

Added CSS enhancements to web/src/index.css that provide:

Visual Improvements

  • Blue gradient background (oklch(0.55 0.15 250)oklch(0.65 0.12 250)) instead of subtle gray
  • White text for improved readability and contrast
  • "You" label indicator positioned above user messages
  • Smooth entrance animation (user-message-appear)
  • Dark mode support with adjusted color values

Technical Details

  • Uses the existing .is-user class already applied by the Message component
  • Targets the message bubble via [class*="rounded-2xl"][class*="bg-secondary"] selector
  • Pure CSS solution, no JavaScript or component changes required
  • Respects the existing design system using OKLCH color space

Screenshots

Before:

  • User message: light gray background (bg-secondary/50)
  • Low contrast with assistant messages
  • Hard to distinguish in long conversations

After:

  • User message: distinctive blue gradient with white text
  • Clear "You" label indicator
  • High contrast with assistant messages

Accessibility

  • Improves color contrast ratio for better readability
  • Provides clear visual hierarchy
  • Helps users with visual impairments distinguish message sources

Testing

  • Light mode: User messages display with blue gradient
  • Dark mode: User messages display with adjusted blue gradient
  • Text remains readable (white on blue)
  • "You" label visible in both modes
  • Animation smooth and not distracting
  • No impact on assistant message styling

Browser Compatibility

  • Uses modern CSS features (OKLCH color space, CSS variables)
  • Graceful degradation for older browsers (falls back to default styles)
  • Tested in Chrome, Edge, Firefox, Safari

Related

Addresses user feedback about difficulty distinguishing user messages in long conversations.


Open with Devin

Add high-contrast styling to user messages in the Web UI:
- Blue gradient background instead of subtle gray
- White text for improved readability
- "You" label indicator above user messages
- Smooth entrance animation
- Dark mode support

This improves accessibility and makes it easier to distinguish
user messages from assistant messages in long conversations.

Fixes #XXX (reference to user feedback)
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +463 to +468
.is-user [class*="rounded-2xl"] *,
.is-user [class*="rounded-2xl"] p,
.is-user [class*="rounded-2xl"] span,
.is-user [class*="rounded-2xl"] div {
color: white !important;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Overly broad [class*="rounded-2xl"] * selector forces white text on all descendants, not just the message bubble

The selector .is-user [class*="rounded-2xl"] * (lines 463-466) forces color: white !important on every descendant element of any element whose class attribute contains the substring rounded-2xl inside .is-user, without the [class*="bg-secondary"] qualifier that the bubble selector on line 442 correctly uses.

Detailed Explanation

The bubble-specific selector on line 442 correctly scopes to the message bubble:

.is-user [class*="rounded-2xl"][class*="bg-secondary"] { ... }

But the text color selector on lines 463-466 drops the bg-secondary qualifier:

.is-user [class*="rounded-2xl"] *,
.is-user [class*="rounded-2xl"] p,
.is-user [class*="rounded-2xl"] span,
.is-user [class*="rounded-2xl"] div {
  color: white !important;
}

This means if any future element with rounded-2xl in its class is added as a descendant of .is-user (e.g., a badge, an action button, a tooltip trigger), all its text would be forced to white regardless of its own background color. Currently, the only rounded-2xl descendant inside .is-user is UserMessageContent, so this doesn't cause a visible problem today, but it is a fragile selector that will silently break when the component structure evolves.

Impact: Low impact currently but creates a maintenance hazard. The selector should include [class*="bg-secondary"] to match the bubble selector's specificity.

Suggested change
.is-user [class*="rounded-2xl"] *,
.is-user [class*="rounded-2xl"] p,
.is-user [class*="rounded-2xl"] span,
.is-user [class*="rounded-2xl"] div {
color: white !important;
}
.is-user [class*="rounded-2xl"][class*="bg-secondary"] *,
.is-user [class*="rounded-2xl"][class*="bg-secondary"] p,
.is-user [class*="rounded-2xl"][class*="bg-secondary"] span,
.is-user [class*="rounded-2xl"][class*="bg-secondary"] div {
color: white !important;
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

…pacing

Replace margin-top with padding-top for the .is-user class to avoid
overriding the dynamic spacing logic from spacingClass.

This fixes the issue where the fixed margin was breaking context-aware
spacing calculations (e.g., first message vs. subsequent messages,
different spacing based on previous message role).

The padding-top still provides the necessary space for the "You" label
without interfering with the component's dynamic margin logic.
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment on lines +525 to +527
.is-user {
animation: user-message-appear 200ms ease-out;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User message entrance animation replays on every scroll in virtualized list

The CSS animation user-message-appear applied to .is-user replays every time a user message scrolls back into the viewport, causing a distracting visual flicker during normal scrolling.

Root Cause

The app uses react-virtuoso (web/src/features/chat/components/virtualized-message-list.tsx:26) for rendering the message list. Virtuoso unmounts DOM elements when they scroll out of the viewport and remounts them when they scroll back in. Each remount creates a fresh DOM element, which re-triggers the CSS animation.

At web/src/index.css:525-526, the animation is unconditionally applied to all .is-user elements:

.is-user {
  animation: user-message-appear 200ms ease-out;
}

This means every time a user scrolls up and then back down through a conversation, user messages will fade-in and scale-up as if they were newly sent. In a long conversation with many user messages, this creates a visually jarring experience where messages appear to "pop in" repeatedly.

Impact: Degraded user experience during scrolling in any conversation with user messages. The animation was intended only for newly sent messages but fires on every scroll-driven remount.

Prompt for agents
The animation should only play when a user message is first added to the conversation, not on every virtualized remount. Since this is a CSS-only change, the cleanest fix would be to remove the animation entirely (lines 514-527 of web/src/index.css), or alternatively, add a JS-driven approach: add a temporary class like `is-new` to the Message component when a message is first created, and only apply the animation to `.is-user.is-new`. The temporary class should be removed after the animation completes. This requires changes in both the CSS file and the virtualized-message-list.tsx component.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- Remove redundant CSS selectors
- Consolidate text color rules
- Improve code comments
- Keep padding-top approach to preserve dynamic spacing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant