Unread Visual Guide - nself-org/nchat GitHub Wiki
Visual reference for all unread indicator components and their usage contexts.
Usage: Show unread count on channel items in sidebar
<UnreadBadge unreadCount={5} mentionCount={2} size="sm" position="inline" />Visual States:
ββββββββββββββββββββββββββββββββββ
β # general [5] β β Regular unread (blue/gray badge)
β # random [3] β
β # design β β β Mention (red dot, no count)
ββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββ
β # announcements [2] β β Mention with count (red badge)
β # support β β β Unread dot (gray)
β # feedback β β No unread
ββββββββββββββββββββββββββββββββββ
Color Coding:
- π΄ Red = Has mentions (
@user,@everyone,@here) - π΅ Blue/Gray = Has unread (no mentions)
- βͺ None = No unread
Sizes:
-
sm: 16px height (default for sidebar) -
md: 20px height (for headers) -
lg: 24px height (for emphasis)
Usage: Subtle indicator when count isn't needed
<UnreadDot unreadCount={3} mentionCount={1} size="sm" />Visual States:
ββββββββββββββββββββββββββββββββββ
β # general β β β Red dot (mentions)
β # random β β β Blue dot (unread)
β # design β β No dot (all read)
ββββββββββββββββββββββββββββββββββ
Best For:
- Muted channels (show indicator but not count)
- Compact views
- Mobile interfaces
- Overflow menus
Usage: Visual separator showing where unread messages start
<UnreadLine count={10} label="New Messages" />Visual Appearance:
βββββββββββββββββββββββββββββββββββββββββββββ
β β
β [Alice] Hey everyone! β
β [Bob] What's up? β
β β
ββββββββββββ π 10 New Messages ββββββββββββ€ β Unread line
β β
β [Charlie] Just joined β
β [Dave] Welcome! β
β β
βββββββββββββββββββββββββββββββββββββββββββββ
Styling:
- Red horizontal line
- Centered label with background
- Bell icon
- Count + custom text
Animation:
- Fades in from above
- Subtle slide down
Usage: Complete channel list item with integrated unread badge
<SidebarUnread
channelName="general"
channelType="channel"
unreadCount={5}
mentionCount={2}
isMuted={false}
isActive={false}
onClick={() => {}}
/>Visual States:
Active Channel:
ββββββββββββββββββββββββββββββββββ
β β # general [5] β β Blue accent bar
ββββββββββββββββββββββββββββββββββ
Unread Channel:
ββββββββββββββββββββββββββββββββββ
β # random [3] β β Bold text
ββββββββββββββββββββββββββββββββββ
Mention:
ββββββββββββββββββββββββββββββββββ
β # design [2] β β Red badge, bold
ββββββββββββββββββββββββββββββββββ
Muted:
ββββββββββββββββββββββββββββββββββ
β # support β β β Dimmed, dot only
ββββββββββββββββββββββββββββββββββ
Read:
ββββββββββββββββββββββββββββββββββ
β # feedback β β Normal text, no badge
ββββββββββββββββββββββββββββββββββ
States:
- Active: Highlighted background + accent bar
- Unread: Bold text + badge
- Mention: Bold text + red badge
- Muted: Reduced opacity + tooltip
- Read: Normal appearance
Usage: Floating button to jump to first unread message
<JumpToUnreadButton
hasUnread={true}
unreadCount={5}
mentionCount={2}
onJumpToUnread={handleJump}
variant="default"
/>Visual Variants:
Default (Full featured):
βββββββββββββββββββββββββββββββββββ
β β
β Message List β
β β
β βββββββββββββββ β
β β π 2 mentionsβ β β Floating button
β βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββ
Compact (Icon only):
βββββββββββββββββββββββββββββββββββ
β β
β Message List β
β β
β βββββ β
β β β¬ β β β Compact button
β βββββ β
β β
βββββββββββββββββββββββββββββββββββ
Minimal (Subtle):
βββββββββββββββββββββββββββββββββββ
β β
β Message List β
β β
β β¬ Jump to 5 unread β β Minimal text
β β
βββββββββββββββββββββββββββββββββββ
Colors:
- π΄ Red background = Has mentions
- π΅ Blue background = Has unread (no mentions)
- βͺ Default background = Jump to latest
Position Options:
-
bottom-center(default) bottom-rightbottom-left
Usage: Highlight messages that mention current user
<MentionHighlight isMentioned={true}>
<MessageItem message={message} />
</MentionHighlight>Visual Effect:
Normal Message:
βββββββββββββββββββββββββββββββββββββ
β [Alice] Hey team! β
βββββββββββββββββββββββββββββββββββββ
Mentioned Message:
βββββββββββββββββββββββββββββββββββββ
β [Alice] Hey @you, check this! β β Red left border
β (background: red-500/10) β β Subtle red tint
βββββββββββββββββββββββββββββββββββββ
Styling:
- Left border: 4px solid red
- Background:
bg-red-500/10(light) /bg-red-500/20(dark) - Full message width
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β SIDEBAR β MAIN CHAT β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β β Header β
β CHANNELS β ββββββββββββββββββββββββββββββ β
β β β # general β β
β # general [5] β β β¬β¬ 3 unread channels β β
β # random [3] β ββββββββββββββββββββββββββββββ β
β # design [2] β β
β # support β β MESSAGES β
β # feedback β ββββββββββββββββββββββββββββββ β
β β β [Alice] Hey! β β
β DMs β β [Bob] What's up? β β
β β β β β
β Alice [2] β βββββ π 5 New Messages βββββ€ β
β Bob β β β β β
β Charlie β β [Dave] Hey @you! β β β Mention
β β β [Eve] Anyone there? β β
β β ββββββββββββββββββββββββββββββ β
β β β
β β ββββββββββββββββββββ β
β β β π 2 mentions β β β Jump button
β β ββββββββββββββββββββ β
β β β
β β Message Input β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββ
β β° # general [5] β β Header with badge
ββββββββββββββββββββββββββββ€
β β
β Messages β
β β
β ββββ π 5 New ββββ β β Unread line
β β
β [Alice] Hey! β
β β
β ββββββββββββββββ β
β β β¬ 5 unread β β β Compact jump button
β ββββββββββββββββ β
β β
β [Input] β
ββββββββββββββββββββββββββββ
1. No unread
ββββββββββββ
β # generalβ
ββββββββββββ
2. First unread arrives
ββββββββββββββββ
β # general [1]β β Badge appears (animated)
ββββββββββββββββ
3. More unreads accumulate
ββββββββββββββββ
β # general [5]β β Count increases
ββββββββββββββββ
4. Mention arrives
ββββββββββββββββ
β # general [2]β β Turns red
ββββββββββββββββ
5. Channel opened, scrolled
ββββββββββββ
β # generalβ β Badge fades out
ββββββββββββ
1. At bottom, no unread
[Hidden]
2. New message arrives
ββββββββββββββββ
β β¬ 1 new β β Appears (slide up)
ββββββββββββββββ
3. More messages
ββββββββββββββββ
β β¬ 5 new β β Count updates
ββββββββββββββββ
4. Mention received
ββββββββββββββββ
β π 2 mentionsβ β Turns red
ββββββββββββββββ
5. Button clicked
[Scrolls to unread]
[Hidden after scroll]
/* Regular unread */
--unread-bg: rgba(59, 130, 246, 0.1) /* blue-500/10 */ --unread-badge: rgba(59, 130, 246, 1)
/* blue-500 */ --unread-text: rgba(59, 130, 246, 1) /* blue-500 */ /* Mentions */
--mention-bg: rgba(239, 68, 68, 0.1) /* red-500/10 */ --mention-badge: rgba(239, 68, 68, 1)
/* red-500 */ --mention-text: rgba(255, 255, 255, 1) /* white */
--mention-border: rgba(239, 68, 68, 1) /* red-500 */;/* Regular unread */
--unread-bg: rgba(59, 130, 246, 0.2) /* blue-500/20 */ --unread-badge: rgba(59, 130, 246, 1)
/* blue-500 */ --unread-text: rgba(96, 165, 250, 1) /* blue-400 */ /* Mentions */
--mention-bg: rgba(239, 68, 68, 0.2) /* red-500/20 */ --mention-badge: rgba(239, 68, 68, 1)
/* red-500 */ --mention-text: rgba(255, 255, 255, 1) /* white */
--mention-border: rgba(239, 68, 68, 1) /* red-500 */;// Framer Motion
<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }} exit={{ scale: 0 }}>
<Badge />
</motion.div>Effect: Pop in from center
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.9 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.9 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
>
<Button />
</motion.div>Effect: Slide up with spring bounce
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
<UnreadLine />
</motion.div>Effect: Slide down fade in
- Full badges with counts
- Default jump button variant
- Sidebar always visible
- Hover states active
- Compact badges
- Compact jump button
- Collapsible sidebar
- Touch-friendly targets
- Dot indicators (no counts in tight spaces)
- Minimal jump button
- Swipe gestures
- Larger touch targets (min 44px)
// Badge
<Badge aria-label="5 unread messages" />
<Badge aria-label="2 mentions" />
// Jump Button
<Button
aria-label="Jump to 5 unread messages"
aria-keyshortcuts="Alt+Shift+U"
/>
// Unread Line
<div
role="separator"
aria-label="Unread messages below"
/>Tab β Focus next unread channel
Shift+Tab β Focus previous unread channel
Enter/Space β Activate focused item
Alt+Shift+U β Jump to unread
Alt+Shift+β β Previous unread channel
Alt+Shift+β β Next unread channel
Esc β Mark channel as read
All interactive elements have visible focus rings:
.focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}-
Memoization
- Use
useMemofor count calculations - Use
useCallbackfor handlers - Prevent unnecessary re-renders
- Use
-
Virtual Lists
- Unread indicators work with virtualized lists
- Only render visible items
- Maintain scroll position
-
Debouncing
- Mark as read debounced (1s default)
- Storage saves debounced (100ms)
- Scroll event throttled
-
Lazy Loading
- Load unread counts on demand
- Progressive enhancement
- Skeleton states for loading
Create stories for each variant:
export const UnreadBadgeStory = {
args: {
unreadCount: 5,
mentionCount: 0,
size: 'sm',
},
}
export const MentionBadgeStory = {
args: {
unreadCount: 5,
mentionCount: 2,
size: 'sm',
},
}- Percy (visual regression)
- Chromatic (Storybook)
- Playwright (E2E screenshots)
export const unreadTokens = {
badge: {
size: {
sm: '16px',
md: '20px',
lg: '24px',
},
fontSize: {
sm: '10px',
md: '12px',
lg: '14px',
},
padding: {
sm: '2px 4px',
md: '2px 6px',
lg: '4px 8px',
},
},
colors: {
unread: 'blue-500',
mention: 'red-500',
muted: 'gray-400',
},
animation: {
duration: '200ms',
easing: 'ease-out',
},
}This visual guide provides a comprehensive reference for implementing and using unread indicators throughout nself-chat. Refer to component documentation for detailed API information.