# Banner## SummaryBanners are persistent messages used to communicate important changes, ongoing
conditions, or system errors. They appear at the top of a screen or near related
content and remain visible until dismissed or the condition is resolved.At Jobber, Banners help service providers (SPs) stay informed about updates that
impact their business, without unnecessarily interrupting their flow.## AnatomyA Banner typically includes: Icon: Adds visual context, usually for warning or error types&#x20;
&#x20;Message text: 1–2 clear, concise sentences&#x20;
&#x20;CTA or link (optional): For related action&#x20;
&#x20;Dismiss button (optional): For SP-controlled removal ## Behaviour- Alignment: Banner should align with the left and right of the content it
  relates to. In a modal or page layout, this means aligning with text, inputs,
  or other components beneath it. Use the [Content](../Content/Content.md) to
  ensure consistent vertical spacing between the Banner and adjacent elements.

- Dismissal: Banners remain visible until they are dismissed or the condition
  resolves. Include a dismiss button only when no further user action is
  required.#### Global Banner (Web and Mobile)Use Global banner for system-wide messages that need to persist beyond a
specific view or layout.It should be positioned above the top navigation bar
It should be full-width, flush with the screen edgesNote that Global Banner is a separate component (there's a version of it in
Jobber and Jobber Online repos).## VariantsBanners come in four types; `success`, `notice`, `warning` and `error`. Each
type has guidelines for usage:### Success```tsx
import type { ComponentProps } from "react";
import React from "react";
import { Banner } from "@jobber/components/Banner";

export function BannerSuccessVariantExample(
  props: Partial<ComponentProps<typeof Banner>>,
) {
  return (
    <Banner type="success" {...props}>
      You&apos;ve connected your bank account and can start receiving payouts.
    </Banner>
  );
}
```Success banners are appropriate when:A meaningful user action is complete and feedback is delayed or needs to
persistIt’s important for SPs to understand that a key change has been appliedOtherwise, use [Toast](../Toast/Toast.md) as the default for success messages.
Do not use the term "Success" when writing a success banner.### Notice (Info)```tsx
import type { ComponentProps } from "react";
import React from "react";
import { Banner } from "@jobber/components/Banner";

export function BannerNoticeExample(
  props: Partial<ComponentProps<typeof Banner>>,
) {
  return (
    <Banner type="notice" {...props}>
      Your visits are being scheduled
    </Banner>
  );
}
```Use notice (info) banners to provide information about:Informing SPs of background operations such as visits being scheduled
Communicating new features or non-blocking advice
Maintenance windows or known system statesNotices should get to the point and inform the user about why the functionality
or change is important, especially if the impact isn't obvious.### Warning```tsx
import type { ComponentProps } from "react";
import React from "react";
import { Banner } from "@jobber/components/Banner";

export function BannerWarningExample(
  props: Partial<ComponentProps<typeof Banner>>,
) {
  return (
    <Banner type="warning" {...props}>
      Your subscription will be automatically upgraded in 8 days
    </Banner>
  );
}
```Use warning banners when:An SPs action may cause unintended effects or a system state change
Additional action has to be taken by the SPWarning messages should be one to two short sentences that describe the possibly
unknown impact changes or actions will have.They should not use "alarmist" language such as start with "Warning" and should
avoid exclamation points. Additionally, they should not be used as a way of
blocking or redirecting a workflow.#### Warning banner vs. Confirmation modalOn web, you should use [ConfirmationModal](../ConfirmationModal/ConfirmationModal.md) when
you need to get explicit confirmation from the SP before they complete an action
that is difficult to reverse.| Use a **Warning Banner** when...                    | Use a **Confirmation Modal** when...                |
| --------------------------------------------------- | --------------------------------------------------- |
| You’re communicating a persistent or upcoming state | You need to confirm intent before proceeding        |
| The SP needs to act, but isn’t initiating the risk  | The SP is triggering a high-impact change           |
| The risk persists until resolved (e.g. paused plan) | The action could disrupt billing, payments, or data |
| You want the message visible across sessions        | You want to keep context to current action          |
| The SP may need time to decide or follow up         | The SP must make a choice before continuing         |### Error message```tsx
import type { ComponentProps } from "react";
import React from "react";
import { Banner } from "@jobber/components/Banner";

export function BannerErrorExample(
  props: Partial<ComponentProps<typeof Banner>>,
) {
  return (
    <Banner type="error" icon="alert" {...props}>
      Your changes couldn&apos;t be saved. Check your connection and try again.
    </Banner>
  );
}
```Use error banners when:an issue requires immediate attention
an issue is blocking the SP
inline validation can not be usedErrors should explain what happened, and how to address the issue in a concise
manner.System errors should avoid technical or intimidating language, and provide ways
to resolve or troubleshoot the issue if possible. Don't use error codes.Using an `icon` prop is optional, but can be used to provide additional visual
context to the user.#### Errors in forms (Web)If an error is directly related to an input, don't use a banner. Use the
[InputText validation message](/?path=/story/components-forms-and-inputs-inputvalidation--basic)
component or, if unavailable, [InputValidation](../InputValidation/InputValidation.md).
Scroll to the first affected field on submission.#### Errors in forms (Mobile)In forms, use the error Banner when the response from submitting the form has
returned errors. See
[Error Details](/storybook/mobile/?path=/story/components-status-and-feedback-banner--error-details)
as an example.In conjunction, each impacted input should display its own inline error
messaging to aid in "recognition over recall" as the user scrolls down through
the form to rectify the issues.If an input can be validated client-side (immediately, before the form is
submitted to the server), you don't need to provide a Banner that duplicates the
error messages. Use inline validation on the inputs to prevent the user from
submitting invalid data. If the user ignores client-side error messages and
submits anyways, show them the error Banner along with the errors on the
individual input(s).## Content guidelinesBanner messages should be concise, clear, and actionable. They inform users of
system states, requirements, or follow-ups—especially ones that persist until
resolved.This guidance helps ensure Banners are useful for all users, and particularly
for SPs who may be multitasking, working on mobile, or managing their business
on the go.#### General guidanceUse plain, direct language.
Limit to 1–2 short sentences (ideally under 200 characters).
Start with what’s happening, then what’s needed (if anything).
Be specific—avoid vague phrases like “something went wrong.”You can reuse key terms from the message in the CTA, especially to reinforce
the action.Use full sentences for clarity. Avoid fragments or trailing thoughts.#### SP specific tipsPrioritize clarity over cleverness. SPs need fast, scannable guidance.Avoid backend or system terms (e.g. “system error,” “processing failed”).Frame language around outcomes: payouts, scheduling, client experience.Keep urgency proportional to actual impact.#### Language RulesAvoid exclamation points. They add unnecessary emotion and visual noise.Don’t use “Heads up,” “FYI,” or “Just so you know.” These take space but say
nothing.Get to the point as quickly as possible. Start with the action required if
there is one.Avoid apologizing in system messages. Focus on clarity and resolution rather
than sentiment.#### Language examples| Type    | ✅ Better phrasing                                                                          | ❌ Less clear phrasing                           |
| ------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| Success | “You've connected your bank account and can start recieving payouts.”                      | “Bank connection successful!”                   |
|         | “Your logo has been added to your invoices.”                                               | “Update saved successfully.”                    |
| Notice  | “Client reminders now include a booking link.”                                             | “We’ve made updates to reminders.”              |
|         | “You can now double-book visits on your calendar.”                                         | “New scheduling feature added.”                 |
| Warning | “To receive payouts, you need to add your bank account details.” CTA: “Add bank account”   | “Missing payout details!”                       |
|         | “Your trial ends in 2 days. Select a plan to keep your account active.” CTA: “Select plan” | “Trial ending—don’t forget to upgrade!”         |
| Error   | “We couldn’t sync your calendar. Try reconnecting your account.”                           | “Calendar sync failed: error 503.”              |
|         | “We couldn’t save your changes. Check your connection and try again.”                      | “Something went wrong. Please try again later.” |Use these examples as starting points. Focus on clarity, relevance, and next
steps. For actions that must be taken, make the language outcome-focused, rather
than technical or emotional.## Dos and Don'tsIn summary, here are some general rules to follow when working with the banner
component:#### Do: ✅ Use for persistent and important messages&#x20;
&#x20;✅ Try to keep the content to under 200 characters&#x20;
&#x20;✅ Align messaging with SP context and workflows&#x20;
&#x20;✅ Provide a CTA if user action is needed&#x20;
&#x20;✅ Use inline validation for field-level form errors #### Don't: ❌ Use Banners for short-lived success feedback (use the Toast component instead)  ❌ Stack multiple Banners without clear visual hierarchy&#x20;
&#x20;❌ Add filler intros like “Heads up” or “FYI"&#x20;
&#x20;❌ Over-explain or apologize; stay focused and practical ## Accessibility notes Banners should be announced to screen readers when renderedEnsure high contrast between background and text—especially for error and
warning statesAvoid positioning mobile Banners over navigation or actionable UI## Related components- To provide low-priority, temporary feedback on the outcome of a user action,
  use [Toast](../Toast/Toast.md) instead.
- Use [ConfirmationModal](../ConfirmationModal/ConfirmationModal.md) when you need to get
  explicit confirmation from the user before they complete an action.
- As stated in the Behaviour section, Global Banner is a a separate component
  (shared component, a version exists in both the Jobber and Jobber Online
  repos).


## Configuration

### Actions (web)

It is discouraged to use a Button component when requiring an action within a
Banner. If you require an action, use the `primaryActions` prop. The
`primaryAction` accepts the same props as the Button component.

See
[Actions in Banners](/storybook/web/?path=/story/components-status-and-feedback-banner--actions-in-banners)
for some examples.

### Actions (mobile)

Use the `action` prop to specify an `onPress` callback and a `label`. Banner
will render the action using an [ActionLabel](/components/ActionLabel).

See
[Actions in Banners](/storybook/mobile/?path=/story/components-status-and-feedback-banner--actions-in-banners)
for an example.

## Component customization

### Composable usage

Banner exposes its internal building blocks as subcomponents: `Banner.Provider`,
`Banner.Content`, `Banner.DismissButton`, `Banner.Icon`, and `Banner.Action`.
This gives you more control over the Banner's appearance and behaviour.

Here's a basic example of how our current non-composable Banner is used:

```tsx
<Banner type="success">Your account was upgraded successfully</Banner>
```

Using Banner's built-in subcomponents, this UI can alternatively be expressed
as:

```tsx
<Banner.Provider type="success">
  <Banner.Content>Your account was upgraded successfully</Banner.Content>
</Banner.Provider>
```

If you want to completely disable the icon or dismiss button, you can set them
to false:

```tsx
<Banner.Provider type="success" icon={false} dismissButton={false}>
  <Banner.Content>No icon or dismiss button</Banner.Content>
</Banner.Provider>
```

If you need a custom icon, you can either configure `Banner.Icon` or pass in
your own element:

```tsx
<Banner.Provider
  type="notice"
  icon={
    <Banner.Icon
      name="sparkles"
      customColor="var(--color-base-purple--700)"
      backgroundColor="base-purple--300"
    />
  }
>
  <Banner.Content>Custom purple sparkles icon</Banner.Content>
</Banner.Provider>
```

If you need to customize the dismiss button, you can either configure
`Banner.DismissButton` or pass in your own element:

```tsx
<Banner.Provider
  type="success"
  dismissButton={<Banner.DismissButton onClick={() => alert("Dismissed")} />}
>
  <Banner.Content>Your account was upgraded successfully</Banner.Content>
</Banner.Provider>
```

For action buttons, you should use `Banner.Action`:

```tsx
<Banner.Provider type="warning">
  <Banner.Content>
    Changes to this visit will not be applied to future visits
  </Banner.Content>
  <Banner.Action label="More info" onClick={() => alert("More info...")} />
</Banner.Provider>
```

### UNSAFE\_ props (advanced usage)

General information for using `UNSAFE_` props can be found
[here](../customizing-components/customizing-components.md).

Banner has a few elements that can be targeted with classes or styles:
`Banner.Provider`, `Banner.Content`, `Banner.Icon`, `Banner.Action`, and
`Banner.DismissButton`.

**Note**: Use of `UNSAFE_` props is **at your own risk** and should be
considered a **last resort**. Future Popover updates may lead to unintended
breakages.

#### UNSAFE\_className

Use `UNSAFE_className` to apply custom classes to the Banner. This can be useful
for applying styles via CSS Modules.

```tsx
// YourComponent.module.css
.customContainer {
  background-color: var(--color-surface--background);
}

.customIconContainer {
  background-color: var(--color-base-red--600);
}

.customDismissButton {
  background-color: var(--color-base-blue--200);
}

.customContent {
  background-color: var(--color-base-pink--200);
}

.customActionButton {
  background-color: var(--color-base-green--600);
}

// YourComponent.tsx
<Banner.Provider
  type="error"
  UNSAFE_className={{
    container: styles.customContainer,
  }}
  icon={
    <Banner.Icon
      name="offline"
      backgroundColor="base-purple--300"
      UNSAFE_className={{
        container: styles.customIconContainer,
        icon: {
          /* See Icon's UNSAFE_className for more information */
        },
      }}
    />
  }
  dismissButton={
    <Banner.DismissButton
      UNSAFE_className={{
        container: styles.customDismissButton,
      }}
    />
  }
>
  <Banner.Content
    UNSAFE_className={{
      container: styles.customContent,
    }}
  >
    Banner content goes here
  </Banner.Content>
  <Banner.Action
    label="More info"
    onClick={() => alert("More info...")}
    UNSAFE_className={{
      container: styles.customActionButton,
      button: {
        /* See Button's UNSAFE_className for more information */
      },
    }}
  />
</Banner.Provider>
```

#### UNSAFE\_style

Use `UNSAFE_style` to apply inline custom styles to the Banner.

```tsx
<Banner.Provider
  type="error"
  UNSAFE_style={{
    container: { backgroundColor: "var(--color-surface--background)" },
  }}
  icon={
    <Banner.Icon
      name="offline"
      backgroundColor="base-purple--300"
      UNSAFE_style={{
        container: { backgroundColor: "var(--color-base-red--600)" },
        icon: {
          /* See Icon's UNSAFE_style for more information */
        },
      }}
    />
  }
  dismissButton={
    <Banner.DismissButton
      UNSAFE_style={{
        container: { backgroundColor: "var(--color-base-blue--200)" },
      }}
    />
  }
>
  <Banner.Content
    UNSAFE_style={{
      container: { backgroundColor: "var(--color-base-pink--200)" },
    }}
  >
    Banner content goes here
  </Banner.Content>
  <Banner.Action
    label="More info"
    onClick={() => alert("More info...")}
    UNSAFE_style={{
      container: { backgroundColor: "var(--color-base-green--600)" },
      button: {
        /* See Button's UNSAFE_style for more information */
      },
    }}
  />
</Banner.Provider>
```


## Props

### Web

#### Banner

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `type` | `BannerType` | Yes | — | Sets the status-based theme of the Banner |
| `controlledVisiblity` | `boolean` | No | `undefined` | When provided, Banner's visibility is controlled by this value |
| `dismissible` | `boolean` | No | `true` | Set to false to hide the dismiss button |
| `icon` | `IconNames` | No | — | Use to override the default status Icon |
| `onDismiss` | `() => void` | No | — | Callback to be called when the Banner is dismissed. |
| `primaryAction` | `{ onClick?: never; external?: never; readonly name?: string; submit: never; readonly type?: ButtonType; readonly value?: string; readonly disabled?: boolean; readonly loading?: boolean; ... 17 more ...; readonly children?: never; } | ... 34 more ... | { ...; }` | No | — | Accepts props for Button. Default action uses a 'subtle' Button |

#### Banner.Action

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ariaControls` | `string` | No | — | Used for screen readers. Will override label on screen reader if present. |
| `ariaExpanded` | `boolean` | No | — |  |
| `ariaHaspopup` | `boolean` | No | — |  |
| `ariaLabel` | `string` | No | — |  |
| `disabled` | `boolean` | No | — |  |
| `external` | `boolean` | No | — |  |
| `fullWidth` | `boolean` | No | — |  |
| `icon` | `IconNames` | No | — |  |
| `iconOnRight` | `boolean` | No | — |  |
| `id` | `string` | No | — |  |
| `label` | `string` | No | — |  |
| `loading` | `boolean` | No | — |  |
| `name` | `string` | No | — |  |
| `onClick` | `(event: MouseEvent<HTMLAnchorElement | HTMLButtonElement, MouseEvent>) => void` | No | — |  |
| `onMouseDown` | `(event: MouseEvent<HTMLAnchorElement | HTMLButtonElement, MouseEvent>) => void` | No | — |  |
| `role` | `string` | No | — | Used to override the default button role. |
| `size` | `ButtonSize` | No | — |  |
| `submit` | `boolean` | No | — | Allows the button to submit a form |
| `to` | `LocationDescriptor<unknown> | ((location: Location<unknown>) => LocationDescriptor<unknown>)` | No | — | **Deprecated**: to will be removed in the next major version @deprecated |
| `type` | `ButtonType` | No | — |  |
| `UNSAFE_className` | `{ readonly container?: string; readonly button?: { container?: string; buttonLabel?: { textStyle?: string; }; buttonIcon?: { svg?: string; path?: string; }; }; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ readonly container?: CSSProperties; readonly button?: { container?: CSSProperties; buttonLabel?: { textStyle?: CSSProperties; }; buttonIcon?: { svg?: CSSProperties; path?: CSSProperties; }; }; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |
| `url` | `string` | No | — | Used to create an 'href' on an anchor tag. |
| `value` | `string` | No | — |  |
| `variation` | `ButtonVariation` | No | — |  |

#### Banner.Content

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `UNSAFE_className` | `{ readonly container?: string; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ readonly container?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |

#### Banner.DismissButton

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ariaLabel` | `string` | No | — |  |
| `onClick` | `(event: MouseEvent<HTMLAnchorElement | HTMLButtonElement, MouseEvent>) => void` | No | — |  |
| `UNSAFE_className` | `{ readonly container?: string; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ readonly container?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |

#### Banner.Icon

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `backgroundColor` | `"disabled" | "icon" | "event" | "invoice" | "job" | "quote" | "request" | "task" | "text" | "visit" | "warning" | "success" | "base-grey--100" | "base-grey--200" | "base-grey--300" | ... 275 more ... | "client--onSurface"` | No | — | Sets the background color of the icon. |
| `color` | `"disabled" | "icon" | "task" | "text" | "warning" | "success" | "blue" | "green" | "yellow" | "red" | "grey" | "white" | "greyBlue" | "lightBlue" | "orange" | "navy" | "interactive" | ... 33 more ... | "brandHighlight"` | No | — | Determines the color of the icon. Some icons have a default system colour like quotes, jobs, and invoices. Others tha... |
| `customColor` | `string` | No | — | Sets a custom color for the icon. Can be a rgb() or hex value. |
| `name` | `IconNames` | No | — | The icon to show. |
| `size` | `"base" | "large" | "small"` | No | `base` | Changes the size to small or large. |
| `testID` | `string` | No | — | Used to locate this view in end-to-end tests |
| `UNSAFE_className` | `{ readonly container?: string; readonly icon?: { svg?: string; path?: string; }; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ readonly container?: CSSProperties; readonly icon?: { svg?: CSSProperties; path?: CSSProperties; }; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |

#### Banner.Provider

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `type` | `BannerType` | Yes | — | Sets the status-based theme of the Banner. |
| `dismissButton` | `ReactNode` | No | `<Banner.DismissButton/>` | Dismiss button to be used for the Banner. If you want to remove the dismiss button, set to false. |
| `icon` | `ReactNode` | No | `<Banner.Icon/>` | Icon to be used for the Banner. If you want to remove the icon, set to false. |
| `onDismiss` | `() => void` | No | — | Callback to be called when the Banner is dismissed. |
| `UNSAFE_className` | `{ readonly container?: string; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ readonly container?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |
| `visible` | `boolean` | No | — | When provided, Banner's visibility is controlled by this value. |
