# Banner

> An inline status banner that slides down from under the edge above and pushes the content down, flings away sideways with the finger, and can collapse on its own behind a draining hairline; includes an offline preset.

- Type: Component (feedback)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/banner
- Markdown: https://goodui.dev/native/components/banner.md
- Registry item: https://goodui.dev/native/r/banner.json
- Dependencies: react-native-gesture-handler, react-native-reanimated, react-native-svg
- Keywords: react native banner, inline alert, status banner, offline banner, network status bar, dismissible notice, callout, expo alert banner

## When to use

- A state of the screen or account the person should know about while they keep working: card expiring, sync paused, trial ending.
- A failure that belongs to one place on the screen, with a way out: 'Couldn't load new messages' with Retry.
- Connectivity: OfflineBanner at the top of a feed or a form.
- A short confirmation tied to the content below it that can collapse on its own (duration).

## When not to use

- Confirming an action the person just took somewhere else on screen: let the control show its own result (button, swipe-to-confirm), or use toast for background events.
- A decision that blocks progress: use alert-dialog.
- A first-run walkthrough: use coach-mark.
- A whole screen with nothing to show: use empty-state.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/banner.json
npx expo install --fix
```

The component lands in `components/goodui-native/banner/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs icon-button, locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { useState } from "react";
import { Banner } from "@/components/goodui-native/banner/banner";

export function BillingNotice({ retryPayment }: { retryPayment: () => Promise<void> }) {
  const [visible, setVisible] = useState(true);
  return (
    <Banner
      visible={visible}
      onVisibleChange={setVisible}
      tone="danger"
      title="Payment failed"
      description="Your Visa ending 4242 was declined. Update it to keep Pro."
      action={{ label: "Retry payment", onPress: retryPayment }}
      spacing={16}
    />
  );
}
```

## Examples

### Offline preset wired to your reachability state

```tsx
<OfflineBanner offline={!isConnected} onRetry={() => refetch()} spacing={12} />
```

### Collapses on its own after 4 seconds

```tsx
<Banner tone="success" title="Booking moved to Friday, 10:30" duration={4000} visible={moved} onVisibleChange={setMoved} />
```

### Solid, not dismissible

```tsx
<Banner variant="solid" tone="warning" dismissible={false} title="Maintenance tonight from 01:00 to 02:00" />
```

## API reference

### Banner

`import { Banner } from "@/components/goodui-native/banner/banner";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` (required) | `string` | – | One line that says what happened. Sentence case. |
| `description` | `string` | – | Optional second line with the detail or the way out. |
| `tone` | `"success" \| "warning" \| "danger" \| "info"` | `"info"` | `info` uses the accent; the others are status colors and only mean status. Changing it crossfades the surface. |
| `variant` | `"solid" \| "soft" \| "outline"` | `"soft"` | `soft` is a tinted fill, `solid` a full status color for the one thing that matters, `outline` a hairline on the surface. |
| `visible` | `boolean` | – | Controlled visibility. Pair with `onVisibleChange`. |
| `defaultVisible` | `boolean` | `true` | Uncontrolled starting visibility. Default `true`. |
| `onVisibleChange` | `(visible: boolean) => void` | – | Called with `false` when the banner closes itself (× button, swipe, action, timer). |
| `action` | `BannerAction` | – | One action under the text. |
| `dismissible` | `boolean` | `true` | Shows an × button. Default `true`. |
| `swipeToDismiss` | `boolean` | – | Swipe the banner sideways to fling it away. Default: on when `dismissible`. |
| `duration` | `number` | – | Milliseconds before it collapses on its own. A hairline drains as the timer runs; touch or hover pauses it. Omit to keep it. |
| `icon` | `ReactNode \| ((color: string) => ReactNode) \| false` | – | Replaces the tone icon. A function receives the icon color. `false` hides it. |
| `spacing` | `number` | `0` | Space under the banner that opens and closes with it, so the content below never jumps. Default 0. |
| `haptic` | `boolean` | `true` | Warning and error haptics when a warning or danger banner appears after mount, tap on dismiss. Default `true`. |
| `announcement` | `string` | – | Announced to screen readers when the banner appears after mount. Defaults to the title and description. |
| `labels` | `Partial<BannerLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

### OfflineBanner

`import { OfflineBanner } from "@/components/goodui-native/banner/banner";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `offline` (required) | `boolean` | – | Whether the device is offline right now. Wire it to NetInfo or your own reachability check. |
| `onRetry` | `() => unknown` | – | Shows a Retry action while offline. Return a promise to show a spinner on it. |
| `labels` | `Partial<OfflineBannerLabels>` | – | Overrides for the built-in copy; a GooduiLocaleProvider pack sets it app-wide. |
| `onlineDuration` | `number` | `2500` | Milliseconds the "Back online" state stays before collapsing. Default 2500. |
| `spacing` | `number` | `0` | Space under the banner that opens and closes with it. Default 0. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- When a banner appears after mount its title and description (or `announcement`) are announced once; it is not a live region, so nothing is read twice.
- On iOS and Android warning and danger banners carry the alert role, info and success the summary role.
- The × is an IconButton labelled 'Dismiss'; the action is a button with busy and disabled in accessibilityState and as aria-busy and aria-disabled on the web.
- The action label sits on a plain surface pill (on the solid variant, a pill in the tone's on color), never on a tint of its own status color, so its 13 pt label keeps 4.5:1 in light and dark for every tone and accent.
- A failed async action announces '<label> failed. Try again.'.
- While collapsed the banner is hidden from screen readers (accessibilityElementsHidden, no-hide-descendants, aria-hidden) and ignores touches.
- Swipe to dismiss is a shortcut only: the × button does the same.

## Motion

- Show: the outer height opens to the measured content on the smooth spring while the card slides down from under the edge above (translateY from minus its height), so the content below is pushed, never jumps. Hide runs the same in reverse.
- Swipe: the card follows the finger 1:1 sideways and fades with distance; past 35 % of its width or a fast flick it springs off with the release velocity and the height closes behind it, otherwise it snaps back on the snappy spring.
- A tone or variant change crossfades the surface, text and icon disc colors on the UI thread (OfflineBanner uses this for offline to back online). The success check draws itself in each time the banner appears.
- duration: a 2 pt hairline drains across the bottom; a touch (or hover on the web) pauses it and it resumes where it stopped.
- A rejected action shakes the card.
- Reduced motion: the height changes at once and the card fades, no slide; the fling is a short timing; no shake; the check appears without drawing. The timer still runs.

## Haptics

- warning or error when a warning or danger banner appears after mount (not on first render)
- tap on dismiss and on the action; success or error when an async action settles
- haptic={false} turns all of them off for one banner; OfflineBanner plays none

## Performance

- Height, slide, swipe, colors and the timer run on the UI thread. The content is measured once per real size change.
- A hidden banner stays mounted at zero height; unmount it yourself if it will never come back.

## Notes for AI

- Do not put the banner in a parent with `gap`: the gap stays when it collapses. Use `spacing` for the space under it, which opens and closes with it.
- Controlled: pass visible and onVisibleChange; the banner calls onVisibleChange(false) on ×, swipe, a dismissing action and when duration runs out.
- action.onPress may return a promise: spinner while pending, the banner closes on resolve (unless action.dismiss is false) and shakes on reject. Do not add your own loading state.
- tone='info' uses the accent. Success, warning and danger only for status.
- OfflineBanner takes a boolean; wire it to @react-native-community/netinfo or your own check. It shows 'Back online' for onlineDuration ms when the boolean flips back.

## Related

- [Toast](https://goodui.dev/native/components/toast.md): Background confirmations that stack at the top, fan out on tap, swipe away up or sideways, and pause their timers while touched.
- [Alert dialog](https://goodui.dev/native/components/alert-dialog.md): A centered alert that springs in from slightly larger; touch an action and slide across the others with a tick per action, tap outside and it nudges, and async actions keep it open with a spinner until they settle.
- [Empty state](https://goodui.dev/native/components/empty-state.md): A designed empty, error or offline state: a tinted disc pops in, its line art draws itself stroke by stroke with the highlight landing last, then the title, description and actions rise in.
- [Icon button](https://goodui.dev/native/components/icon-button.md): A layered icon-only button whose surface and icon sink on touch down and release on different springs; it runs async actions itself, carries a badge and shows a hint on long press.

## Guidance for AI tools

Banner: An inline status banner that slides down from under the edge above and pushes the content down, flings away sideways with the finger, and can collapse on its own behind a draining hairline; includes an offline preset. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
