Skip to content
Product block

Support Chat Widget

The corner launcher, with real availability instead of a stock reassurance, article deflection before the form, no self-opening, and a way to make it go away.

307 lineslucide-reactAdded 13 Sept 2026
  • chat
  • support
  • widget
  • launcher
  • help

What's included

  • components/support-chat-widget.tsx
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add support-chat-widget

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Start a page with this section — add more, order them, and leave with the page source.

Preview

Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.

Source

components/support-chat-widget.tsx
'use client'

/**
 * <SupportChatWidget> — the bubble in the corner, and what it owes you.
 *
 * The support launcher is the most-shipped and least-designed component on the
 * web. The default version pops open unprompted, promises a reply in "a few
 * minutes" at 23:00 on a Sunday, and covers the checkout button on a phone.
 *
 * FOUR THINGS THIS ONE DOES DIFFERENTLY
 *
 *  - **It states real availability, in the visitor's terms.** Either an agent
 *    is online and the wait is a number, or nobody is and the honest offer is
 *    a message that gets answered in the morning. "We typically reply in a few
 *    minutes" outside working hours is the lie that turns one contact into
 *    three.
 *  - **It offers self-service before the form.** Three suggested articles
 *    drawn from the current page. Most contacts are a question with a
 *    published answer, and deflecting them is worth more than any routing.
 *  - **It never opens itself.** No proactive invitation, no timer. The
 *    launcher is a button, and a button is enough.
 *  - **It is dismissible for the session.** The bubble covers something on
 *    every mobile layout ever built, and there has to be a way to make it go
 *    away that is not "leave the site".
 *
 * WHY THE PANEL IS ANCHORED WITH `end-` AND NOT `right-`
 *
 * A launcher pinned to the physical right sits over the start of the text in
 * Arabic and Hebrew, exactly where the eye begins. Logical properties put it
 * in the trailing corner in both directions. The whole widget uses `end-4`
 * for that reason and the catalog's RTL check enforces it.
 *
 * ACCESSIBILITY: the launcher carries an accessible name that includes the
 * unread count; the panel is `role="dialog"` named by its heading; the
 * transcript is a `role="log"` with `aria-live="polite"`, so a new agent
 * message is announced without the composer losing focus; Escape closes the
 * panel and returns focus to the launcher, which is the part hand-rolled
 * widgets almost always miss.
 */

import * as React from 'react'
import { BookOpen, Clock, MessageCircle, Send, X } from 'lucide-react'

export interface SuggestedArticle {
  id: string
  title: string
  minutes: number
}

export interface WidgetMessage {
  id: string
  from: 'agent' | 'you'
  body: string
  at: string
}

export interface SupportChatWidgetProps {
  online?: boolean
  waitMinutes?: number
  articles?: SuggestedArticle[]
  messages?: WidgetMessage[]
  unread?: number
  className?: string
}

const DEFAULT_ARTICLES: SuggestedArticle[] = [
  { id: 'a1', title: 'Why my discount code was refused at checkout', minutes: 2 },
  { id: 'a2', title: 'Changing the delivery address after ordering', minutes: 1 },
  { id: 'a3', title: 'What happens if nobody is in for the delivery', minutes: 3 },
]

const DEFAULT_MESSAGES: WidgetMessage[] = [
  {
    id: 'm1',
    from: 'agent',
    body: 'Hi — Priya here. I can see order ORD-88410. What went wrong with it?',
    at: '14:02',
  },
  {
    id: 'm2',
    from: 'you',
    body: 'The second chair arrived with a cracked base plate.',
    at: '14:03',
  },
  {
    id: 'm3',
    from: 'agent',
    body: 'Sorry about that. I can send a replacement base plate today, or swap the whole chair — the plate is a ten-minute job with the allen key in the box. Which suits you?',
    at: '14:04',
  },
]

export function SupportChatWidget({
  online = true,
  waitMinutes = 3,
  articles = DEFAULT_ARTICLES,
  messages = DEFAULT_MESSAGES,
  unread = 1,
  className = '',
}: SupportChatWidgetProps) {
  const uid = React.useId()
  const [open, setOpen] = React.useState(true)
  const [dismissed, setDismissed] = React.useState(false)
  const [draft, setDraft] = React.useState('')
  const [thread, setThread] = React.useState(messages)
  const launcherRef = React.useRef<HTMLButtonElement>(null)

  function close() {
    setOpen(false)
    // Returning focus is the part hand-rolled widgets miss.
    window.requestAnimationFrame(() => launcherRef.current?.focus())
  }

  function send(event: React.FormEvent) {
    event.preventDefault()
    if (!draft.trim()) return
    setThread((t) => [
      ...t,
      { id: `m${t.length + 1}`, from: 'you', body: draft.trim(), at: '14:06' },
    ])
    setDraft('')
  }

  return (
    <section
      className={`relative min-h-[34rem] overflow-hidden bg-muted/30 p-4 sm:p-6 ${className}`}
      onKeyDown={(event) => {
        if (event.key === 'Escape' && open) close()
      }}
    >
      {/* Stand-in for the page the widget sits over. */}
      <div aria-hidden className="mx-auto max-w-2xl space-y-3">
        <div className="h-7 w-48 rounded bg-muted border border-transparent" />
        <div className="h-3 w-full rounded bg-muted border border-transparent" />
        <div className="h-3 w-5/6 rounded bg-muted border border-transparent" />
        <div className="h-3 w-2/3 rounded bg-muted border border-transparent" />
      </div>

      {/* end-4, not right-4 — see the docblock. */}
      <div className="absolute bottom-4 end-4 flex flex-col items-end gap-3">
        {open ? (
          <div
            id={`${uid}-panel`}
            role="dialog"
            aria-labelledby={`${uid}-title`}
            className="flex h-[26rem] w-[min(22rem,calc(100vw-3rem))] flex-col overflow-hidden rounded-2xl border border-border bg-card text-card-foreground shadow-lg"
          >
            <header className="flex items-start justify-between gap-3 border-b border-border p-4">
              <div>
                <h2 id={`${uid}-title`} className="text-sm font-semibold">
                  Support
                </h2>
                {/* Real availability, not a stock reassurance. */}
                <p className="mt-0.5 flex items-center gap-1.5 text-xs text-muted-foreground">
                  <span
                    aria-hidden
                    className={`h-2 w-2 rounded-full border border-transparent ${
                      online ? 'bg-emerald-500' : 'bg-muted-foreground'
                    }`}
                  />
                  {online
                    ? `Someone is here — about ${waitMinutes} min to a first reply`
                    : 'Nobody is on until 08:00 CET. Leave a message and it is answered first thing.'}
                </p>
              </div>
              <button
                type="button"
                onClick={close}
                aria-label="Close support"
                className="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <X aria-hidden className="h-4 w-4" />
              </button>
            </header>

            {/* Deflection before the form. */}
            <div className="border-b border-border bg-muted/40 p-3">
              <p className="flex items-center gap-1.5 text-xs font-semibold">
                <BookOpen aria-hidden className="h-3.5 w-3.5 text-primary" />
                Answers for this page
              </p>
              <ul className="mt-2 space-y-1">
                {articles.map((article) => (
                  <li key={article.id}>
                    <a
                      href="#"
                      className="flex items-start justify-between gap-2 rounded-lg px-2 py-1.5 text-xs transition-colors hover:bg-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                    >
                      <span className="min-w-0">{article.title}</span>
                      <span className="inline-flex shrink-0 items-center gap-1 text-muted-foreground">
                        <Clock aria-hidden className="h-3 w-3" />
                        {article.minutes} min
                      </span>
                    </a>
                  </li>
                ))}
              </ul>
            </div>

            <div
              role="log"
              aria-live="polite"
              aria-label="Conversation"
              className="min-h-0 flex-1 space-y-3 overflow-y-auto p-3"
            >
              {thread.map((message) => (
                <div
                  key={message.id}
                  className={`flex ${message.from === 'you' ? 'justify-end' : 'justify-start'}`}
                >
                  <div
                    className={`max-w-[85%] rounded-2xl px-3 py-2 text-xs ${
                      message.from === 'you'
                        ? 'rounded-ee-sm bg-primary text-primary-foreground'
                        : 'rounded-es-sm bg-muted text-foreground'
                    }`}
                  >
                    <p>{message.body}</p>
                    <p
                      className={`mt-1 text-[10px] ${
                        message.from === 'you'
                          ? 'text-primary-foreground/70'
                          : 'text-muted-foreground'
                      }`}
                    >
                      {message.from === 'you' ? 'You' : 'Priya'} · {message.at}
                    </p>
                  </div>
                </div>
              ))}
            </div>

            <form onSubmit={send} className="flex items-center gap-2 border-t border-border p-3">
              <label htmlFor={`${uid}-draft`} className="sr-only">
                Write a message
              </label>
              <input
                id={`${uid}-draft`}
                value={draft}
                onChange={(e) => setDraft(e.target.value)}
                placeholder={online ? 'Type a message' : 'Leave a message'}
                className="min-w-0 flex-1 rounded-lg border border-border bg-background px-3 py-2 text-xs outline-none transition-colors focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring"
              />
              <button
                type="submit"
                disabled={!draft.trim()}
                aria-label="Send message"
                className="rounded-lg bg-primary p-2 text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <Send aria-hidden className="h-4 w-4 rtl:rotate-180" />
              </button>
            </form>
          </div>
        ) : null}

        {dismissed ? (
          <p className="rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground">
            Hidden for this session.{' '}
            <button
              type="button"
              onClick={() => setDismissed(false)}
              className="font-semibold text-foreground underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
            >
              Bring it back
            </button>
          </p>
        ) : (
          <div className="flex items-center gap-2">
            {/* There has to be a way to make it go away. */}
            {!open ? (
              <button
                type="button"
                onClick={() => setDismissed(true)}
                className="rounded-full border border-border bg-card px-2.5 py-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                Hide
              </button>
            ) : null}
            <button
              ref={launcherRef}
              type="button"
              onClick={() => setOpen((v) => !v)}
              aria-expanded={open}
              aria-controls={`${uid}-panel`}
              className="relative inline-flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
            >
              <MessageCircle aria-hidden className="h-5 w-5" />
              <span className="sr-only">
                {open ? 'Close support' : 'Open support'}
                {!open && unread > 0 ? `, ${unread} unread` : ''}
              </span>
              {!open && unread > 0 ? (
                <span
                  aria-hidden
                  className="absolute -top-0.5 end-0 grid h-4 min-w-4 place-items-center rounded-full bg-destructive px-1 text-[10px] font-bold text-destructive-foreground"
                >
                  {unread}
                </span>
              ) : null}
            </button>
          </div>
        )}
      </div>
    </section>
  )
}

Before you paste

  • Styling is Tailwind utility classes on semantic tokens (bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.
  • Install: npm i lucide-react
  • Every prop has a default, so it renders standalone before you wire it up.

Where it goes

Drop it at components/support-chat-widget.tsx and import it where you need the section:

import { SupportChatWidget } from '@/components/support-chat-widget'

Customize

3 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.

Props

Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.

PropTypeDefault
onlinebooleantrue
waitMinutesnumber3
articlesSuggestedArticle[]DEFAULT_ARTICLES
messagesWidgetMessage[]DEFAULT_MESSAGES
unreadnumber1
classNamestring''

Not using React?

The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.

This block is interactive. The markup below is its initial state with the event handlers stripped — you will need to re-wire the behaviour in your framework.

support-chat-widget.html
<!--
  Support Chat Widget — markup from the Hoverlab catalog.

  This is the block rendered once to HTML and wrapped as a component
  file. It is not a port of the React source: the Tailwind classes carry
  the design, which is the part that took the work, and they are the same
  in every framework.

  This block is interactive in React and the handlers are NOT here.
  Buttons, toggles and menus render in their initial state and do
  nothing until you wire them up.
-->
<section class="relative min-h-[34rem] overflow-hidden bg-muted/30 p-4 sm:p-6 ">
  <div aria-hidden="true" class="mx-auto max-w-2xl space-y-3">
    <div class="h-7 w-48 rounded bg-muted border border-transparent"></div>
    <div class="h-3 w-full rounded bg-muted border border-transparent"></div>
    <div class="h-3 w-5/6 rounded bg-muted border border-transparent"></div>
    <div class="h-3 w-2/3 rounded bg-muted border border-transparent"></div>
  </div>
  <div class="absolute bottom-4 end-4 flex flex-col items-end gap-3">
    <div id="_R_0_-panel" role="dialog" aria-labelledby="_R_0_-title" class="flex h-[26rem] w-[min(22rem,calc(100vw-3rem))] flex-col overflow-hidden rounded-2xl border border-border bg-card text-card-foreground shadow-lg">
      <header class="flex items-start justify-between gap-3 border-b border-border p-4">
        <div>
          <h2 id="_R_0_-title" class="text-sm font-semibold">Support</h2>
          <p class="mt-0.5 flex items-center gap-1.5 text-xs text-muted-foreground"><span aria-hidden="true" class="h-2 w-2 rounded-full border border-transparent bg-emerald-500"></span>Someone is here — about 3 min to a first reply</p>
        </div>
        <button type="button" aria-label="Close support" class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x h-4 w-4" aria-hidden="true">
            <path d="M18 6 6 18"></path>
            <path d="m6 6 12 12"></path>
          </svg>
        </button>
      </header>
      <div class="border-b border-border bg-muted/40 p-3">
        <p class="flex items-center gap-1.5 text-xs font-semibold"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-open h-3.5 w-3.5 text-primary" aria-hidden="true"><path d="M12 7v14"></path><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"></path></svg>Answers for this page</p>
        <ul class="mt-2 space-y-1">
          <li>
            <a href="#" class="flex items-start justify-between gap-2 rounded-lg px-2 py-1.5 text-xs transition-colors hover:bg-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
              <span class="min-w-0">Why my discount code was refused at checkout</span>
              <span class="inline-flex shrink-0 items-center gap-1 text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-3 w-3" aria-hidden="true"><path d="M12 6v6l4 2"></path><circle cx="12" cy="12" r="10"></circle></svg>2 min</span>
            </a>
          </li>
          <li>
            <a href="#" class="flex items-start justify-between gap-2 rounded-lg px-2 py-1.5 text-xs transition-colors hover:bg-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
              <span class="min-w-0">Changing the delivery address after ordering</span>
              <span class="inline-flex shrink-0 items-center gap-1 text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-3 w-3" aria-hidden="true"><path d="M12 6v6l4 2"></path><circle cx="12" cy="12" r="10"></circle></svg>1 min</span>
            </a>
          </li>
          <li>
            <a href="#" class="flex items-start justify-between gap-2 rounded-lg px-2 py-1.5 text-xs transition-colors hover:bg-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
              <span class="min-w-0">What happens if nobody is in for the delivery</span>
              <span class="inline-flex shrink-0 items-center gap-1 text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-3 w-3" aria-hidden="true"><path d="M12 6v6l4 2"></path><circle cx="12" cy="12" r="10"></circle></svg>3 min</span>
            </a>
          </li>
        </ul>
      </div>
      <div role="log" aria-live="polite" aria-label="Conversation" class="min-h-0 flex-1 space-y-3 overflow-y-auto p-3">
        <div class="flex justify-start">
          <div class="max-w-[85%] rounded-2xl px-3 py-2 text-xs rounded-es-sm bg-muted text-foreground">
            <p>Hi — Priya here. I can see order ORD-88410. What went wrong with it?</p>
            <p class="mt-1 text-[10px] text-muted-foreground">Priya · 14:02</p>
          </div>
        </div>
        <div class="flex justify-end">
          <div class="max-w-[85%] rounded-2xl px-3 py-2 text-xs rounded-ee-sm bg-primary text-primary-foreground">
            <p>The second chair arrived with a cracked base plate.</p>
            <p class="mt-1 text-[10px] text-primary-foreground/70">You · 14:03</p>
          </div>
        </div>
        <div class="flex justify-start">
          <div class="max-w-[85%] rounded-2xl px-3 py-2 text-xs rounded-es-sm bg-muted text-foreground">
            <p>Sorry about that. I can send a replacement base plate today, or swap the whole chair — the plate is a ten-minute job with the allen key in the box. Which suits you?</p>
            <p class="mt-1 text-[10px] text-muted-foreground">Priya · 14:04</p>
          </div>
        </div>
      </div>
      <form class="flex items-center gap-2 border-t border-border p-3">
        <label for="_R_0_-draft" class="sr-only">Write a message</label>
        <input id="_R_0_-draft" placeholder="Type a message" class="min-w-0 flex-1 rounded-lg border border-border bg-background px-3 py-2 text-xs outline-none transition-colors focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring" value="" />
        <button type="submit" disabled="" aria-label="Send message" class="rounded-lg bg-primary p-2 text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send h-4 w-4 rtl:rotate-180" aria-hidden="true">
            <path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path>
            <path d="m21.854 2.147-10.94 10.939"></path>
          </svg>
        </button>
      </form>
    </div>
    <div class="flex items-center gap-2">
      <button type="button" aria-expanded="true" aria-controls="_R_0_-panel" class="relative inline-flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle h-5 w-5" aria-hidden="true">
          <path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"></path>
        </svg>
        <span class="sr-only">Close support</span>
      </button>
    </div>
  </div>
</section>
  • This is rendered HTML, not a translation of the React source. The Tailwind classes carry the design and work in any framework.
  • It is one frame: the component in its initial state, with no props applied beyond the defaults.
  • This block is interactive in React — toggles, menus or form state. None of that survives here; the markup is the closed/default state and the handlers are gone. Re-wire them in your own framework.
  • Requires Tailwind, and the design tokens the classes reference (bg-card, text-muted-foreground, and so on). The template ZIPs ship a globals.css that defines them.

What each framework gets across the whole catalog — effects convert properly; this rung is markup.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

Used in these pages

Want the whole screen instead of this one section? Open a page and copy it entire.

8 more blocks in Communication

All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.

Browse Communication

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once

More Communication blocks

View category
Open the full page for this block

Message Bubble Thread

Bubbles with the parts that get skipped: per-message delivery state, grouping by author and time, day separators, and a retry on the failed bubble itself.

Communication325 lines1 dep

Attachment Bubbles

Five kinds of thing that is not text - image, document, link, location and an upload still in flight - each answering the question its own kind raises.

Communication305 lines1 dep
Open the full page for this block

Inbox Thread List

The list half of a two-pane inbox, with unread and selected as visibly different states and bulk selection as a mode rather than a hover checkbox.

Communication333 lines1 dep
Open the full page for this block

Inbox Reading Pane

The reading pane with the composer always present, quoted history collapsed behind a count, and an internal-note mode whose send button names the recipient.

Communication311 lines1 dep
Open the full page for this block

Voice Message Player

A voice note you can navigate: a waveform that is a real seek control, a speed button, a transcript, and a played state that survives finishing.

Communication271 lines1 dep
Open the full page for this block

Video Call Layout

The call grid with the state that makes it usable - active speaker, per-tile connection quality, hide self-view, and the you-are-muted nudge.

Communication286 lines1 dep
Open the full page for this block

Call Device Settings

The green room, where every picker has a test beside it - an input meter, a test tone, a camera preview - and blocked permission is a state, not an error.

Communication315 lines1 dep
Open the full page for this block

Support Ticket Thread

The customer side of a ticket, where a named owner and a deadline replace Status: Open - including the waiting-on-you state that stops the argument about response times.

Communication340 lines1 dep