agentsclimarketplace

Cometchat a11y

Skill cometchat/cometchat-skills/skills/cometchat-a11y

Add CometChat chat & messaging and voice & video calls to any React, Next.js, React Native, Angular, Android, iOS, or Flutter project through your AI coding agent. Works with Claude Code, Cursor, Codex, VS Code Copilot, Windsurf, Cline, Kiro, and 50+ more agents.

Install
npx -y skills add cometchat/cometchat-skills --skill cometchat-a11y

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Accessibility (a11y) for CometChat UI Kit integrations across all families — React, React Native, Angular, Android (V5/V6), iOS, Flutter. Covers WCAG 2.1 AA targets, keyboard navigation in chat, screen reader announcements (live regions for new messages), color contrast, focus management on call screens, motion-reduction support, and the cross-family checks that catch the common production a11y bugs. Cross-family — applies wherever the agent is checking accessibility.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

16.4 KB, as published. Nobody here has run it

Ground truth: per-platform UI Kit + docs/fundamentals. Official docs: https://www.cometchat.com/docs/fundamentals/overview · Docs MCP: claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp (or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.

Purpose

Accessibility for CometChat integrations. Out-of-the-box, the UI Kit components are mostly accessible — the kit's own buttons, inputs, and lists ship with semantic markup. Production gaps appear in the wiring around the kit: custom call surfaces, navigation, focus management on screen transitions, and contrast in custom themes.

Target: WCAG 2.1 AA. The skill writes code that meets this baseline.


The five gaps that trip integrations (any family)

  1. Color contrast in custom themes. A brand color picked for "looks nice in the brand book" may be 3.2:1 against the text — fails AA's 4.5:1 minimum.
  2. Focus management on chat screen entry. Tab/screen reader user lands on the chat screen but focus stays on the previous trigger button. They have to manually navigate into the message list every time.
  3. No live region announcement for new messages. Screen reader users don't know a new message arrived unless they navigate the message list and hear the new item.
  4. Keyboard-only users can't navigate the conversation list. Click handlers bound to <div> instead of <button> skip keyboard events.
  5. Reduced-motion users see decorative animations. Typing-indicator dots, message bubble entrance animations, transition effects — should respect prefers-reduced-motion.

This skill addresses each one across families.


1. Color contrast — the theme audit

CometChat themes are CSS variables (web/RN) or color tokens (native/Flutter). Override a single color and you might fail AA.

Web / Angular — CSS variable contrast check

// scripts/check-contrast.ts (run in CI or as a one-shot)
function contrastRatio(hex1: string, hex2: string): number {
  const luminance = (hex: string) => {
    const rgb = hex.match(/\w\w/g)!.map(c => parseInt(c, 16) / 255).map(c =>
      c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
    );
    return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
  };
  const l1 = luminance(hex1);
  const l2 = luminance(hex2);
  return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}

// Pull the values from your CSS variables
const fg = getComputedStyle(document.documentElement).getPropertyValue("--cometchat-text-color").trim();
const bg = getComputedStyle(document.documentElement).getPropertyValue("--cometchat-background-color").trim();
const ratio = contrastRatio(fg, bg);
if (ratio < 4.5) {
  console.warn(`Text/background contrast ${ratio.toFixed(2)}:1 fails WCAG AA (need ≥4.5:1)`);
}

In CI, add this to your test suite. The skill writes a starter version into tests/a11y/contrast.test.ts.

React Native / native / Flutter — manual audit at theme-design time

Use a contrast-checker tool (browser extensions, https://webaim.org/resources/contrastchecker/) on the theme tokens before shipping. There's no runtime DOM to audit on native.

The kit's default theme tokens pass AA. Custom palettes need the audit.

Common fail: brand purple #6750A4 against white background = 6.6:1 (passes). Same purple against #F0F0F0 light gray = 5.7:1 (passes). Same purple against #999999 muted gray = 2.8:1 (FAILS). Watch for muted backgrounds in dark-mode toggles, secondary buttons, and "subtle" surfaces.


2. Focus management on chat screen entry

When the user navigates to a chat screen (clicked a conversation, opened the chat tab, accepted a deep link), focus should land on a meaningful control — usually the message composer or the latest message.

React (web)

import { useEffect, useRef } from "react";

export function ChatScreen() {
  const composerRef = useRef<HTMLElement>(null);

  useEffect(() => {
    // After mount + animations, focus the composer
    const timer = setTimeout(() => {
      composerRef.current?.focus();
    }, 100);
    return () => clearTimeout(timer);
  }, []);

  return (
    <div>
      <CometChatMessageHeader />
      <CometChatMessageList />
      <CometChatMessageComposer ref={composerRef} />
    </div>
  );
}

The kit's CometChatMessageComposer is a plain function component (no forwardRef in v6), so passing it a ref is a no-op. Wrap it in a focusable container and reach the input through the DOM: composerRef.current?.querySelector("input, [contenteditable]")?.focus().

React Native

import { useRef, useEffect } from "react";
import { findNodeHandle, AccessibilityInfo } from "react-native";

export function ChatScreen() {
  const composerRef = useRef(null);

  useEffect(() => {
    const handle = findNodeHandle(composerRef.current);
    if (handle) {
      AccessibilityInfo.setAccessibilityFocus(handle);
    }
  }, []);

  return (
    <View>
      <CometChatMessageHeader />
      <CometChatMessageList />
      <CometChatMessageComposer ref={composerRef} />
    </View>
  );
}

Angular

@Component({...})
export class ChatComponent implements AfterViewInit {
  @ViewChild("composer") composer!: ElementRef;

  ngAfterViewInit() {
    setTimeout(() => this.composer.nativeElement.focus(), 100);
  }
}

Native Android (Kotlin)

override fun onResume() {
  super.onResume()
  composerView.requestFocus()
  composerView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}

Native iOS (Swift)

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  UIAccessibility.post(notification: .screenChanged, argument: composerView)
}

Flutter

final FocusNode _composerFocus = FocusNode();

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_) {
    _composerFocus.requestFocus();
  });
}

// Then on the composer widget: focusNode: _composerFocus

3. Live region for new messages

Screen reader users need an audible announcement when a new message arrives — otherwise they have to navigate to the message list and re-read it.

Web / Angular — ARIA live region

<!-- A visually-hidden region that screen readers announce -->
<div
  aria-live="polite"
  aria-atomic="true"
  style="position: absolute; left: -9999px; height: 1px; width: 1px; overflow: hidden;"
  id="message-announcer"></div>
// Listen for new messages and announce
import { CometChat } from "@cometchat/chat-sdk-javascript";

const listenerId = "a11y-message-announcer";
CometChat.addMessageListener(listenerId, new CometChat.MessageListener({
  onTextMessageReceived: (msg: CometChat.TextMessage) => {
    const senderName = msg.getSender().getName();
    const text = msg.getText();
    const region = document.getElementById("message-announcer");
    if (region) {
      // Clearing first ensures the same text re-announces
      region.textContent = "";
      setTimeout(() => {
        region.textContent = `New message from ${senderName}: ${text}`;
      }, 100);
    }
  },
}));

aria-live="polite" waits for the user to finish speaking before announcing. Use aria-live="assertive" only for urgent messages (like incoming calls) — too aggressive for chat.

React Native

import { AccessibilityInfo } from "react-native";

CometChat.addMessageListener(listenerId, new CometChat.MessageListener({
  onTextMessageReceived: (msg) => {
    const text = `New message from ${msg.getSender().getName()}: ${msg.getText()}`;
    AccessibilityInfo.announceForAccessibility(text);
  },
}));

Native Android / iOS / Flutter

Each platform has an equivalent — Android View.announceForAccessibility(text), iOS UIAccessibility.post(notification: .announcement, argument: text), Flutter SemanticsService.announce(text, TextDirection.ltr). Same shape; the SDK callback is the trigger.


4. Keyboard navigation

The kit's components are keyboard-accessible by default. Custom wrapping is what breaks it.

Anti-pattern — <div onClick> for clickable items

// ✗ WRONG — keyboard users can't activate
<div onClick={() => openConversation(c)}>{c.name}</div>

// ✓ RIGHT — `<button>` is keyboard + screen-reader native
<button onClick={() => openConversation(c)}>{c.name}</button>

// ✓ ALSO RIGHT — div with explicit ARIA + keyboard handlers
<div
  role="button"
  tabIndex={0}
  onClick={() => openConversation(c)}
  onKeyDown={(e) => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      openConversation(c);
    }
  }}
>
  {c.name}
</div>

Skip links

For long conversation lists, add a skip-to-message-composer link:

<a href="#message-composer" class="skip-link">Skip to message composer</a>
.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 999;
}
.skip-link:focus {
  left: 0;
  top: 0;
  background: white;
  padding: 8px;
}

The kit's components already include skip links where applicable; custom wrapping should preserve them.

Keyboard shortcuts

For productivity apps:

useEffect(() => {
  const handler = (e: KeyboardEvent) => {
    // Cmd/Ctrl + K → focus search
    if ((e.metaKey || e.ctrlKey) && e.key === "k") {
      e.preventDefault();
      searchRef.current?.focus();
    }
    // Escape → close any open thread / modal
    if (e.key === "Escape") {
      closeOpenThread();
    }
  };
  window.addEventListener("keydown", handler);
  return () => window.removeEventListener("keydown", handler);
}, []);

Document the shortcuts in your in-app help — discoverability matters.


5. Reduced motion

Animations help most users; they cause physical discomfort or distraction for users with vestibular disorders, ADHD, or who simply prefer less movement. WCAG 2.1 AA requires honoring the OS preference.

Web / Angular — CSS

@media (prefers-reduced-motion: reduce) {
  /* Disable kit animations + your custom ones */
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

React Native

import { AccessibilityInfo } from "react-native";

const [reduceMotion, setReduceMotion] = useState(false);

useEffect(() => {
  AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
  const sub = AccessibilityInfo.addEventListener("reduceMotionChanged", setReduceMotion);
  return () => sub.remove();
}, []);

// In animations
<Animated.View
  style={{
    transform: [{ scale: reduceMotion ? 1 : animatedValue }],
  }}
/>

Native iOS

let reduceMotion = UIAccessibility.isReduceMotionEnabled
if !reduceMotion {
  UIView.animate(withDuration: 0.3) { ... }
} else {
  // Apply final state without animation
}

Native Android

val reduceMotion = Settings.Global.getFloat(
  contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f
) == 0.0f

Flutter

final reduceMotion = MediaQuery.of(context).disableAnimations;

The skill defaults to wrapping the typing indicator + message-bubble entrance animations in reduce-motion guards. Other kit animations need similar treatment if you've customized them.


Calls a11y

Calls have specific a11y considerations beyond chat:

  1. Focus on call screen entry → end-call button. Avoids accidental hangup but ensures the user can quickly exit.
  2. Announce incoming calls. Live region (web/RN) or UIAccessibility.post(.announcement) (iOS) — "Incoming call from Alice."
  3. Mute/end button labels. "Mute microphone" not just "Mute" — clarify what's being toggled.
  4. No flashing — recording indicator uses dot, not strobe. WCAG 2.3 forbids more than 3 flashes/sec to prevent seizures.
  5. Caption support if recording. Production calling apps with live captioning hook in via the platform's speech-recognition API and overlay text on the call screen.

Testing — automated + manual

Web automated

npm install --save-dev @axe-core/playwright
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

test("chat screen passes axe AA", async ({ page }) => {
  await page.goto("/messages");
  const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa"]).analyze();
  expect(results.violations).toEqual([]);
});

Native automated

iOS: Xcode → Accessibility Inspector → Audit. Android: Accessibility Scanner app on a real device.

Manual

  • Keyboard-only navigation — unplug your mouse, complete a full chat flow. Tab through every control.
  • VoiceOver (iOS) / TalkBack (Android) / NVDA (Windows) / VoiceOver (macOS) — listen to the kit; verify announcements make sense.
  • Browser zoom 200% — kit should remain usable at 200% zoom on a 1280×720 viewport (WCAG 1.4.10 reflow).

Anti-patterns

  1. Custom themes without a contrast audit. Brand colors silently fail AA.
  2. <div onClick> for clickable items. Keyboard users can't activate.
  3. No live region for new messages. Screen reader users miss messages.
  4. Auto-playing video on call screen. Some users browse with autoplay disabled — kit handles this; custom UI must too.
  5. Focus stays on the trigger button after opening chat. User has to manually re-navigate.
  6. Ignoring prefers-reduced-motion. Vestibular-disorder users get disoriented.
  7. aria-live="assertive" for chat messages. Interrupts the user mid-sentence; reserve for genuinely urgent (incoming call).
  8. Skipping label specificity. "Mute" alone is ambiguous; "Mute microphone" / "Unmute microphone" is clear.
  9. Color-only signals. "Read" status as just a checkmark color — add a visible text label for color-blind users.
  10. No test coverage for a11y. Regressions slip in. Automate the easy checks (axe-core); manual-test the rest per release.

Verification checklist

Cross-family:

  • Custom theme passes AA contrast (4.5:1 text, 3:1 large text + UI components)
  • Focus lands on a meaningful control on chat screen entry
  • Live region / accessibility announcement on new message receive
  • No <div onClick> patterns — buttons are buttons
  • prefers-reduced-motion / isReduceMotionEnabled honored for animations
  • Mute/end/camera labels are specific (not just "Mute")
  • Incoming-call announcement (assertive live region OR platform announcement API)
  • No flashing > 3 Hz (recording dot uses fade, not strobe)

Web/Angular:

  • axe-core / Playwright a11y test in CI; passes WCAG AA tags
  • <html lang="..."> set to current locale (consumes cometchat-i18n skill output)
  • Skip-to-composer link present
  • Browser zoom 200% smoke test

Native (Android/iOS/Flutter):

  • TalkBack / VoiceOver smoke test on a real device
  • Reduced motion preference observed (UIAccessibility.isReduceMotionEnabled, etc.)
  • Focus restored on screen pop (not just push)
  • Touch targets ≥ 44×44 pt (iOS) / 48×48 dp (Android) — kit defaults pass; custom UI must too

Pointers

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.