---
title: Alert
description: The Markdown callout, reachable from JSX - application news in the same box the docs already use.
source: https://adamjurek.com/components/alert
---

## Home


Alert is the Markdown callout (`> [!NOTE]`) usable from JSX, for
application-state news - a failed save, a quota warning - that is not
authored in a Markdown file. Give it a `tone` of note, tip, or caution; `live`
makes it announced to screen readers as an interruption.

<!-- ::start:showcase demo="alert" height="380" -->
<!-- ::end:showcase -->

## Why it is built this way

Markdown already renders `> [!NOTE]` as this exact box; Alert exists so an
application state can wear the same box without being written in Markdown
first. `role="alert"` is opt-in through `live` rather than the default,
because most alerts are read in place as part of the page, and a page full of
assertive regions is a page that never stops talking to a screen reader.

## What it does not do

It does not dismiss itself, queue, or stack. It renders while its condition
is true and disappears when the caller stops rendering it - timing and
dismissal are the host's job, the same way the failed save or the quota is.

> [!NOTE] Install commands are not written here
> Anything in `packages/ui/registry.ts` gets its TanStack and shadcn commands
> attached automatically, so there is nothing to keep in sync.


## Install

<!-- ::start:tabs -->

### TanStack

```shell
tanstack add https://adamjurek.com/r/tanstack/alert.json
```

### shadcn

```shell
pnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/alert.json
```

### pnpm

```shell
pnpm add @sushindustries/ui @sushindustries/atoms
```

<!-- ::end:tabs -->

## What you get

| | |
| --- | --- |
| Version | 0.1.0 |
| Category | content · Feedback |
| Files | `alert.tsx` |
| Dependencies | None |
| Tags | callout, no-deps |

> [!NOTE] No runtime dependencies
> It brings nothing with it beyond the stylesheet.

## Get Started


Install commands are on Home, attached from the registry - they are not written
here, because a second copy is a copy that goes stale. This tab starts after the
install worked.

## Use it

```tsx
import { Alert } from "@sushindustries/ui";

export function Example() {
	return (
		<Alert title="Draft saved" tone="tip">
			Changes are kept locally until you publish.
		</Alert>
	);
}
```

## What you should see

A left-bordered box with an uppercase title line and, underneath it, the
body text. The border colour follows `tone`: the default `note` is the
calm one, `tip` and `caution` change only the accent stripe on the left
edge - the box stays quiet by design, not a coloured panel.

## If nothing happens

If the box renders with no colour on the left edge, check `tone` is one
of `note`, `tip` or `caution` - anything else falls back to `note`. An
alert that should interrupt but reads silently to a screen reader is
missing `live`; without it the box is announced only if the reader
happens to land on it.


## Guides


## Variants

`tone` selects a modifier class - `markdown-alert-note`, `-tip` or
`-caution` - rather than a `data-*` attribute like most toned components
here. That is deliberate: `> [!NOTE]` blocks in Markdown are parsed into
these exact class names already, and `Alert` exists so JSX-rendered news
wears the identical box. Matching the parser's own classes is what keeps
the two indistinguishable on the page.

## Composing it

`Alert` has no minimum height or width - it sizes to its parent and wraps
its own text. Leave `live` unset for anything that is page furniture (a
permanent notice, documentation content) and set it only for state that
just happened - a save that failed, a quota just hit - because
`role="alert"` interrupts a screen reader mid-sentence, and a page with
several of them announcing at once talks over itself.


## API


<!-- generated:api -->

## Props

| Prop | Type | Default | Does |
| --- | --- | --- | --- |
| `title` | `string` | - |  |
| `children?` | `ReactNode` | - |  |
| `tone?` | `"note" \| "tip" \| "caution"` | `"note"` | What kind of news this is. `note` is the calm default. |
| `live?` | `boolean` | - | Only interruptions are announced; page furniture is not. |

<!-- /generated:api -->

## Notes

TypeScript restricts `tone` to `"note" | "tip" | "caution"`, but the
runtime check is stricter still: anything that reaches the component as
neither `"tip"` nor `"caution"` renders as `note`, which only matters if
the prop is ever set from unchecked data. `children` is optional - an
alert with only a `title` renders a one-line box with no body, which is
correct for news that needs no elaboration.


## Examples


Examples are the tab where the component is shown doing a job, not
demonstrating a prop. The API tab already lists the props.

<!-- ::start:showcase demo="alert" height="420" -->
<!-- ::end:showcase -->

Press Compare. The frames are real viewports, so a layout that breaks at 320
breaks here too rather than in somebody's hands.

## In a page

```tsx
import { useState } from "react";
import { Alert, Button } from "@sushindustries/ui";

export function SaveForm() {
	const [error, setError] = useState<string | null>(null);

	async function onSubmit() {
		try {
			await save();
		} catch {
			setError("Could not save. Try again.");
		}
	}

	return (
		<form className="flex col gap-3">
			{error ? (
				<Alert title="Save failed" tone="caution" live>
					{error}
				</Alert>
			) : null}
			<Button type="submit" onClick={onSubmit}>
				Save
			</Button>
		</form>
	);
}
```

## What this example is not

The alert here is conditionally rendered on failure, with `live` set
because it reports something that just happened. It is not a toast: it
stays in the form's own layout rather than floating above the page, and
it does not dismiss itself - clearing `error` on the next successful save
is the form's job, not the component's.
