Components

Banner

A dismissible message with optional actions, circular visuals, and swipeable stacks.

iOS
Android
Web
import BannerRoot from "swshd/Banner/Root";

Banner presents a message with optional actions and a leading avatar or icon. It stays visible until the caller removes it. The parent owns placement and width; BannerStack shows queued messages one at a time. Every control uses the generic SWSHD Button.

Like ActionMenu, each part is a default export from its own entry point, with its props type exported from the same module. Compose the message row inside Content and place Actions below it.

import BannerRoot from "swshd/Banner/Root";
import BannerContent from "swshd/Banner/Content";
import BannerAvatar from "swshd/Banner/Avatar";
import BannerMessage from "swshd/Banner/Message";
import BannerTitle from "swshd/Banner/Title";
import BannerDismiss from "swshd/Banner/Dismiss";
import BannerActions from "swshd/Banner/Actions";
import BannerAction from "swshd/Banner/Action";

<BannerRoot onDismiss={removeInvitation}>
  <BannerContent>
    <BannerAvatar name="Aidan Swan" />
    <BannerMessage>
      <BannerTitle>Aidan Swan</BannerTitle> invited you to join the tour album.
    </BannerMessage>
    <BannerDismiss accessibilityLabel={t("Dismiss invitation")} />
  </BannerContent>
  <BannerActions>
    <BannerAction label={t("Preview")} variant="secondary" onPress={previewAlbum} />
    <BannerAction label={t("Accept invite")} onPress={acceptInvite} />
  </BannerActions>
</BannerRoot>

Swipe horizontally in either direction, use the close button, or press Escape while a control is focused. Preview leaves the message in place; Accept invite removes it in this example. Reset the stack to try again.

Aidan Swan invited you to join the tour album.
3 banners queued. Swipe or close a banner to reveal the next.
import BannerStack from "swshd/Banner/Stack";

const remove = (id: string) =>
  setItems((current) => current.filter((item) => item.id !== id));

<BannerStack>
  {items.map((item) => (
    <BannerRoot key={item.id} onDismiss={() => remove(item.id)}>
      {/* Compose this item's Banner parts. */}
    </BannerRoot>
  ))}
</BannerStack>

The first child is active. Give every root a stable key and remove the dismissed item from your state. The next card stays mounted for promotion, while a backplate represents deeper items. Incomplete and canceled swipes return to rest, and vertical scrolling remains available.

Dismiss centers beside a single-line message and aligns to the top when the message wraps. Use Avatar for a person, Icon for a status, or omit the visual. Actions are optional and wrap when the parent is narrow.

Album ready.
Your album is ready to share with everyone who joined you at the show. Invite your friends to add their photos.
import BannerIcon from "swshd/Banner/Icon";
import IconCheck from "swshd/icons/IconCheck";

<BannerContent>
  <BannerIcon Icon={IconCheck} />
  <BannerMessage>Album ready.</BannerMessage>
  <BannerDismiss accessibilityLabel={t("Dismiss upload notification")} />
</BannerContent>

Swap the loaded content for Skeleton to reserve the circle, message lines, and action pills. Skeleton and loading actions register with Root automatically and block every action and dismissal until loading ends. Root's loading prop also blocks interaction when the caller is loading other content.

Show the loaded banner, then try Accept invite.
import BannerSkeleton from "swshd/Banner/Skeleton";

<BannerRoot loading={loading} onDismiss={removeInvitation}>
  {loading ? (
    <BannerSkeleton accessibilityLabel={t("Loading invitation")} />
  ) : (
    <>{/* Compose the loaded content and actions. */}</>
  )}
</BannerRoot>

Supply translated labels for Dismiss, Skeleton, and actions. The active Message is a polite live region; queued cards are hidden from assistive technology and their controls are disabled. Avatar and Icon are decorative. Skeleton exposes one named loading indicator while its individual shapes stay hidden.

The close button, Escape on web, and accessibility escape on native provide alternatives to swiping. Native gestures require GestureHandlerRootView. Reduced motion and automation skip automatic displacement, and there is no auto-dismiss timer.

BannerRoot

PropTypeDefaultDescription
childrenReactNode—Compose the Banner parts inside the surface.
onDismiss() => void—Required. Remove the banner from your state when dismissal completes.
loadingbooleanfalseBlocks actions and every dismissal path. Render Skeleton as a child while loading.
testIDstring—data-testid on the surface on web.

BannerAction

PropTypeDefaultDescription
labelstring—Required. The translated button label.
onPress / href(() => void) / string—Supply a callback or link. Both may be omitted only when disabled is explicitly true.
variant"primary" | "secondary" | "ghost" | "destructive""primary"The generic Button variant. Banner actions use the small pill size.
IconIconComponent—An optional glyph from swshd/icons.
loadingbooleanfalseShows the Button spinner and blocks all actions and dismissal in this banner.
disabledboolean—Disables this action.
target / relstring—Generic Button link behavior on web.
accessibilityLabelstring—Optional accessible name when the visible label needs more context.
testIDstring—data-testid on the button on web.

Content parts

Import each part from swshd/Banner/ followed by its name below. Render these parts inside Root. Title belongs inside Message.

PropTypeDefaultDescription
Contentchildren, testID?—The row containing a visual, message, and dismiss control.
Messagechildren, testID?—Body text. Measures wrapping to position Dismiss and announces the active message.
Titlechildren—Primary inline text inside Message, such as the sender's name.
Avatarname: string, imageUrl?, testID?—A decorative circular Avatar with caller-supplied imagery or initials.
IconIcon: IconComponent, testID?—A decorative SWSHD glyph in the same circular slot as Avatar.
Actionschildren—A wrapping row of BannerAction buttons.
DismissaccessibilityLabel: string, testID?—The close button. A translated accessible label is required.

BannerStack

PropTypeDefaultDescription
childrenReactElement<BannerRootProps> | readonly ReactElement<BannerRootProps>[] | null—Keyed Banner roots in display order, with the first child active.
testIDstring—data-testid on the stack on web.

BannerSkeleton

PropTypeDefaultDescription
accessibilityLabelstring—Required. A translated name for the loading indicator.
showVisualbooleantrueReserve the leading circle for an avatar or icon.
actionCount0 | 1 | 22The number of action pills to reserve below the message lines.
testIDstring—data-testid on the loading indicator on web.