---
title: "StatusHint"
description: "A one-line hint that states the outcome of an action."
category: Feedback
status: beta
since: 0.6.1
tags: [hint, outcome, tone, morph, grain]
syncStatus: reviewed
verified: true
---

## Installation

:::TuffCodeBlock{lang="bash"}
---
code: |
  pnpm add @talex-touch/tuffex
---
:::

:::TuffCodeBlock{lang="typescript"}
---
code: |
  import { TxStatusHint } from '@talex-touch/tuffex/status-hint'
  import '@talex-touch/tuffex/status-hint/style.css'
  // It renders TxTextTransformer (which renders TxTextMorph) and TxIcon, whose sheets are separate
  import '@talex-touch/tuffex/text-transformer/style.css'
  import '@talex-touch/tuffex/text-morph/style.css'
  import '@talex-touch/tuffex/icon/style.css'
  import '@talex-touch/tuffex/base.css' // tokens + resets, once per app
---
:::

## Usage

### Basic
A new message morphs out of the old words; when the same message arrives again, a new `pulseKey` replays the emphasis.
:::TuffDemoWrapper{demo="StatusHintStatusHintDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const feedback = ref<{ id: number, tone: 'success' | 'danger', message: string } | null>(null)
  let lastId = 0
  let timer: number | undefined

  // A new message replaces the one on screen and restarts the 1.2s clock
  function show(message: string, tone: 'success' | 'danger' = 'success') {
    feedback.value = { id: ++lastId, tone, message }
    window.clearTimeout(timer)
    timer = window.setTimeout(() => { feedback.value = null }, 1200)
  }
  </script>

  <template>
    <TxButton size="sm" @click="show('Copied')">Copy</TxButton>
    <TxButton size="sm" @click="show('Could not pin', 'danger')">Simulate a failure</TxButton>

    <!-- No :key per message: the hint stays mounted, so a new message morphs -->
    <Transition name="tx-status-hint">
      <TxStatusHint
        v-if="feedback"
        :text="feedback.message"
        :tone="feedback.tone"
        :pulse-key="feedback.id"
        :live="false"
      />
    </Transition>
    <!-- The hint mounts with its message, so an always-mounted region announces it -->
    <span class="sr-only" role="status">{{ feedback?.message ?? '' }}</span>
  </template>
---
:::

### Tones and Sizes
`tone` colors only the wash and the icon; `md` is a status line of its own, and `sm` sits in a toolbar or header.
:::TuffDemoWrapper{demo="StatusHintTonesDemo" code-lang="vue"}
---
code: |
  <template>
    <!-- A static gallery: no message changes to announce, so no live regions -->
    <TxStatusHint tone="success" text="Copied" :live="false" />
    <TxStatusHint tone="warning" text="Saved locally" :live="false" />
    <TxStatusHint tone="danger" text="Could not pin" :live="false" />
    <TxStatusHint tone="info" text="Opened in browser" :live="false" />
    <TxStatusHint tone="muted" text="Nothing changed" :live="false" />

    <TxStatusHint size="sm" tone="success" text="Copied" :live="false" />
  </template>
---
:::

### Placement
To lay the hint flush along a bar, position it with a class heavier than one class (a scoped class is enough) and set two CSS variables.
:::TuffDemoWrapper{demo="StatusHintPlacementDemo" code-lang="vue"}
---
code: |
  <template>
    <div class="footer">
      <div v-if="!feedback" class="footer__item">Notes · Application</div>
      <Transition name="tx-status-hint">
        <TxStatusHint
          v-if="feedback"
          class="footer__feedback"
          :text="feedback.message"
          :tone="feedback.tone"
          :pulse-key="feedback.id"
          :live="false"
        />
      </Transition>
      <div class="footer__keys"><TxKbd>⌘K</TxKbd> Actions</div>
    </div>
  </template>

  <style scoped>
  /* The bar is the hint's containing block, and clips the wash to its corners */
  .footer {
    position: relative;
    display: flex;
    align-items: center;
    height: 44px;
    padding: 0 12px;
    overflow: hidden;
    border-radius: 10px;
  }

  /* A scoped class outweighs the root's one-class rule in any load order */
  .footer__feedback {
    --tx-status-hint-radius: 0;
    --tx-status-hint-pad-x: 12px;
    position: absolute;
    inset-block: 0;
    left: 0;
    width: 50%;
    pointer-events: none;
  }

  /* An auto margin, not space-between, keeps the keys put while the item is swapped out */
  .footer__keys {
    margin-left: auto;
  }
  </style>
---
:::

### Best Practices

- Keep it mounted while messages change: no `:key` per message, and `v-if` only for "no message", inside `<Transition name="tx-status-hint">`.
- Pass each message's id as `pulseKey`, or the same text arriving twice makes the second action look like it did nothing.
- Keep it to one line of two to four words; it never wraps, so put long detail, such as a provider's error text, where it can wrap.
- When the hint mounts with its message, or the host already has an announcer, pass `live=false` and announce from an always-mounted `role="status"` region; never put `role` or `aria-live` on the component, since attributes fall through to the root `div`.
- Use one hint per surface; when messages must queue, stack, or time out, use [Toast](/docs/dev/components/toast).

## API Reference

### Props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `text` | `string \| number` | - | Required; one short line. While `animated`, a new value morphs out of the old one. |
| `tone` | `'success' \| 'warning' \| 'danger' \| 'info' \| 'muted'` | `'success'` | Color of the wash and icon, reusing `StatusTone`; `info` reads the primary hue. |
| `size` | `'sm' \| 'md'` | `'md'` | `md`: 13px words, 16px icon; `sm`: 12px words, 14px icon. |
| `pulseKey` | `string \| number` | - | Replays the emphasis when it changes after mount; pass each message's id. |
| `animated` | `boolean` | `true` | `false` shows the end state with plain text; wire the host's own motion switch here. |
| `live` | `boolean` | `true` | `true` makes the words a polite live region; `false` leaves no live region in the component. |

### Slots

| Slot | Description |
|------|-------------|
| `icon` | Replaces the tone icon in the same `aria-hidden` box and motion; the only way to give `muted` an icon. |

### CSS Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `--tx-status-hint-radius` | `8px` | Corner radius of the hint and its wash; `0` for a flush placement. |
| `--tx-status-hint-pad-x` | `10px`; `sm` `8px` | Inline padding, and the width of the end fade. |
| `--tx-status-hint-accent` | the tone's colour | Color of the wash and icon, set by the tone; `muted` uses `--tx-text-color-secondary`. |
| `--tx-status-hint-wash-strength` | `0.26`; `0.2` under a dark theme | Wash alpha at the left edge; 0.45 of it at 38%, none at the right end. |

- The component never sets `--tx-status-hint-radius`, so a rule on the hint or any ancestor applies; override `--tx-status-hint-pad-x` with a rule heavier than one class.
- Override `--tx-status-hint-accent` and `--tx-status-hint-wash-strength` with an inline `style` or a selector heavier than two classes.
- `--tx-status-hint-pad-y`, `--tx-status-hint-icon-size`, `--tx-status-hint-spring`, and `--tx-status-hint-spring-duration` are set by the component; hosts leave them alone.

## Overview

- The words are `--tx-text-color-primary` at weight 600 in every tone; `muted` is a neutral grey with no default icon.
- It never wraps or adds an ellipsis: at most as wide as its container, a longer value fades into the end padding and is clipped.
- On mount the wash rises from the left edge and the icon and words land on a spring, readable from the first frame; a `text` or `pulseKey` change replays the emphasis, once per update.
- A `text` change morphs the words character by character (380ms), resizing an inline hint; on leave the words fade first and the wash follows, opacity only.
- `animated=false` and `prefers-reduced-motion: reduce` both show the end state; under reduced motion alone the morph engine stays mounted and writes new values directly.
- The wash, grain, and end fade are drawn left to right physically; right-to-left pages are not mirrored.

## Technologies

- The wash is the tone's color behind a mask: a gradient intersected with a 140px `feTurbulence` noise tile through `mask-composite: intersect`, and absent where unsupported; replays alternate two identical keyframe sets, `is-pulse-a` and `is-pulse-b`.
- The spring is `resolveTransition('bouncy')` from `liquid/src/spring.ts`, written after mount; server markup falls back to `620ms cubic-bezier(0.34, 1.56, 0.64, 1)`, and hydration matches.
- Source: `packages/tuffex/packages/components/src/status-hint/`; `StatusTone` comes from `status-badge`.

<TuffDocSourceLink />

## Use cases

- The outcome of an action, shown where it happened: "Copied", "Pinned", "Could not pin".
- CoreBox's action feedback, in its footer, or in its header when the footer is not showing.

## Related components

- [Toast](/docs/dev/components/toast): global notifications drawn by one host that stack and time out.
- [Alert](/docs/dev/components/alert): an inline banner with a title, body, and close button, announced with `role="alert"`.
- [StatusBadge](/docs/dev/components/status-badge): a status pill that stays on screen; StatusHint reuses its `StatusTone`.
- [TextTransformer](/docs/dev/components/text-transformer): the text engine behind the hint's words.
