diff --git a/packages/ui-banner/.gitignore b/packages/ui-banner/.gitignore
new file mode 100644
index 0000000000..02125f1a5b
--- /dev/null
+++ b/packages/ui-banner/.gitignore
@@ -0,0 +1,4 @@
+lib/
+.babel-cache/
+es/
+types/
diff --git a/packages/ui-banner/babel.config.js b/packages/ui-banner/babel.config.js
new file mode 100644
index 0000000000..647821c390
--- /dev/null
+++ b/packages/ui-banner/babel.config.js
@@ -0,0 +1,36 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+module.exports = {
+ presets: [
+ [
+ require('@instructure/ui-babel-preset'),
+ {
+ esModules: Boolean(process.env.ES_MODULES),
+ removeConsole: process.env.NODE_ENV === 'production',
+ transformImports: Boolean(process.env.TRANSFORM_IMPORTS)
+ }
+ ]
+ ]
+}
diff --git a/packages/ui-banner/package.json b/packages/ui-banner/package.json
new file mode 100644
index 0000000000..088e4a8c2d
--- /dev/null
+++ b/packages/ui-banner/package.json
@@ -0,0 +1,87 @@
+{
+ "name": "@instructure/ui-banner",
+ "version": "11.7.4",
+ "description": "A UI component for displaying Banners",
+ "author": "Instructure, Inc. Engineering and Product Design",
+ "module": "./es/index.js",
+ "main": "./lib/index.js",
+ "types": "./types/index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/instructure/instructure-ui.git"
+ },
+ "homepage": "https://instructure.github.io/instructure-ui/",
+ "bugs": "https://github.com/instructure/instructure-ui/issues",
+ "scripts": {
+ "lint": "ui-scripts lint",
+ "lint:fix": "ui-scripts lint --fix",
+ "clean": "ui-scripts clean",
+ "build": "ui-scripts build --modules es,cjs",
+ "build:watch": "pnpm run ts:check -- --watch & ui-scripts build --watch",
+ "build:types": "tsc -p tsconfig.build.json",
+ "ts:check": "tsc -p tsconfig.build.json --noEmit --emitDeclarationOnly false"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.29.7",
+ "@instructure/console": "workspace:*",
+ "@instructure/emotion": "workspace:*",
+ "@instructure/shared-types": "workspace:*",
+ "@instructure/ui-buttons": "workspace:*",
+ "@instructure/ui-heading": "workspace:*",
+ "@instructure/ui-icons": "workspace:*",
+ "@instructure/ui-react-utils": "workspace:*",
+ "@instructure/ui-text": "workspace:*",
+ "@instructure/ui-themes": "workspace:*"
+ },
+ "devDependencies": {
+ "@instructure/ui-axe-check": "workspace:*",
+ "@instructure/ui-babel-preset": "workspace:*",
+ "@instructure/ui-color-utils": "workspace:*",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "16.3.2",
+ "vitest": "^4.1.9"
+ },
+ "peerDependencies": {
+ "react": ">=18 <=19"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "sideEffects": false,
+ "exports": {
+ "./lib/*": "./lib/*",
+ "./es/*": "./es/*",
+ "./types/*": "./types/*",
+ "./package.json": "./package.json",
+ "./src/*": "./src/*",
+ ".": {
+ "src": "./src/exports/a.ts",
+ "types": "./types/exports/a.d.ts",
+ "import": "./es/exports/a.js",
+ "require": "./lib/exports/a.js",
+ "default": "./es/exports/a.js"
+ },
+ "./v11_6": {
+ "src": "./src/exports/a.ts",
+ "types": "./types/exports/a.d.ts",
+ "import": "./es/exports/a.js",
+ "require": "./lib/exports/a.js",
+ "default": "./es/exports/a.js"
+ },
+ "./v11_7": {
+ "src": "./src/exports/b.ts",
+ "types": "./types/exports/b.d.ts",
+ "import": "./es/exports/b.js",
+ "require": "./lib/exports/b.js",
+ "default": "./es/exports/b.js"
+ },
+ "./latest": {
+ "src": "./src/exports/b.ts",
+ "types": "./types/exports/b.d.ts",
+ "import": "./es/exports/b.js",
+ "require": "./lib/exports/b.js",
+ "default": "./es/exports/b.js"
+ }
+ }
+}
diff --git a/packages/ui-banner/src/Banner/__tests__/Banner.test.tsx b/packages/ui-banner/src/Banner/__tests__/Banner.test.tsx
new file mode 100644
index 0000000000..e4ac70d957
--- /dev/null
+++ b/packages/ui-banner/src/Banner/__tests__/Banner.test.tsx
@@ -0,0 +1,169 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom'
+import { vi } from 'vitest'
+import { runAxeCheck } from '@instructure/ui-axe-check'
+
+import { Banner } from '@instructure/ui-banner/latest'
+
+describe('', () => {
+ let consoleWarningMock: ReturnType
+ let consoleErrorMock: ReturnType
+
+ beforeEach(() => {
+ consoleWarningMock = vi
+ .spyOn(console, 'warn')
+ .mockImplementation(() => {}) as any
+ consoleErrorMock = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => {}) as any
+ })
+
+ afterEach(() => {
+ consoleWarningMock.mockRestore()
+ consoleErrorMock.mockRestore()
+ })
+
+ it('should render the header', () => {
+ render(Body text)
+
+ expect(screen.getByText('Heads up')).toBeInTheDocument()
+ })
+
+ it('should render children as body content', () => {
+ render(Your assignments were graded.)
+
+ expect(
+ screen.getByText('Your assignments were graded.')
+ ).toBeInTheDocument()
+ })
+
+ it('should render a custom icon when provided', () => {
+ render(}>Body text)
+
+ expect(screen.getByTestId('custom-icon')).toBeInTheDocument()
+ })
+
+ it('should render a call-to-action button when ctaText and onCtaClick are provided', () => {
+ const onCtaClick = vi.fn()
+ render(
+
+ Body text
+
+ )
+
+ const button = screen.getByRole('button', { name: 'Go' })
+ expect(button).toBeInTheDocument()
+
+ button.dispatchEvent(
+ new MouseEvent('click', { bubbles: true, button: 0, detail: 1 })
+ )
+ expect(onCtaClick).toHaveBeenCalledTimes(1)
+ })
+
+ it('should not render a call-to-action button when onCtaClick is missing', () => {
+ render(Body text)
+
+ expect(screen.queryByRole('button', { name: 'Go' })).not.toBeInTheDocument()
+ })
+
+ it('should render a dismiss button and call onDismiss when clicked', async () => {
+ const onDismiss = vi.fn()
+ render(
+
+ Body text
+
+ )
+
+ const closeButton = screen.getByRole('button', { name: 'Close banner' })
+ closeButton.dispatchEvent(
+ new MouseEvent('click', { bubbles: true, button: 0, detail: 1 })
+ )
+
+ expect(onDismiss).toHaveBeenCalledTimes(1)
+ })
+
+ it('should not render a dismiss button when onDismiss is missing', () => {
+ render(Body text)
+
+ expect(screen.queryByRole('button')).not.toBeInTheDocument()
+ })
+
+ it('should not render a dismiss button when isDismissible is false', () => {
+ render(
+ {}} closeButtonLabel="Close banner">
+ Body text
+
+ )
+
+ expect(screen.queryByRole('button')).not.toBeInTheDocument()
+ })
+
+ it('should warn when isDismissible is true without a closeButtonLabel', () => {
+ render(
+ {}}>
+ Body text
+
+ )
+
+ expect(consoleWarningMock.mock.calls.flat()).toContainEqual(
+ expect.stringContaining('`closeButtonLabel` should be provided')
+ )
+ })
+
+ it('should not warn when isDismissible is true with a closeButtonLabel', () => {
+ render(
+ {}} closeButtonLabel="Close">
+ Body text
+
+ )
+
+ expect(consoleWarningMock.mock.calls.flat()).not.toContainEqual(
+ expect.stringContaining('`closeButtonLabel` should be provided')
+ )
+ })
+
+ it('should meet a11y standards', async () => {
+ const { container } = render(
+ {}}
+ closeButtonLabel="Close banner"
+ >
+ Body text
+
+ )
+
+ const axeCheck = await runAxeCheck(container)
+
+ expect(axeCheck).toBe(true)
+ })
+})
diff --git a/packages/ui-banner/src/Banner/v1/README.md b/packages/ui-banner/src/Banner/v1/README.md
new file mode 100644
index 0000000000..d6198f51f4
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/README.md
@@ -0,0 +1,126 @@
+---
+describes: Banner
+---
+
+The Banner component surfaces a short, prominent message -- an announcement
+or a promotion -- with an optional header, an icon, a dismiss control, and
+a slot for a call-to-action.
+
+All of Banner's text comes from props (`header`, `children`, `ctaText`, and
+whatever you pass as `icon`) so it can be translated -- Banner never renders
+hardcoded copy itself.
+
+```javascript
+---
+type: example
+---
+Your assignments were graded.
+```
+
+Add a `header` for a bold lead-in line above the body text.
+
+```javascript
+---
+type: example
+---
+
+ Try the redesigned gradebook filters.
+
+```
+
+Banner ships in two colors: `violet` (default) and `sea`.
+
+```javascript
+---
+type: example
+---
+
+
+ Violet banner.
+
+
+ Sea banner.
+
+
+```
+
+`density` controls padding, gaps, and font sizes. `relaxed` (default) is
+roomier; `compact` is tighter.
+
+```javascript
+---
+type: example
+---
+
+
+
+ More padding, larger text.
+
+
+
+
+ Less padding, smaller text.
+
+
+
+```
+
+Pass `isDismissible` along with `onDismiss` to show a close button in the
+top-right corner. `closeButtonLabel` gives the button an accessible name --
+provide it whenever `isDismissible` is `true`, or a development warning is
+logged. A close button only renders when both `isDismissible` and
+`onDismiss` are set; a dismiss control with no handler is a dead end for
+users, so Banner won't render one.
+
+```javascript
+---
+type: example
+---
+ console.log('dismissed')}
+ closeButtonLabel="Close banner"
+>
+ Dismiss me with the close button.
+
+```
+
+Pass `ctaText` and `onCtaClick` for a follow-up action. Banner always
+renders this as its own fixed-style button -- consumers control the label
+and click behavior, not the button's size or color.
+
+```javascript
+---
+type: example
+---
+ console.log('cta clicked')}
+>
+ See how a saved study plan can keep you on track.
+
+```
+
+Pass `icon` to override the default icon shown in the icon swatch.
+
+```javascript
+---
+type: example
+---
+}>
+ Custom icon example.
+
+```
+
+### Accessibility
+
+- Banner does not assign itself a specific ARIA role; wrap it in a
+ region with an appropriate role (e.g. `role="status"` or
+ `role="alertdialog"`) at the call site when the message needs to be
+ announced to assistive technology.
+- Always provide `closeButtonLabel` when `isDismissible` is `true` -- it is
+ the dismiss button's only accessible name.
+- The `violet` and `sea` pastel backgrounds are chosen from the design
+ token set already tuned for sufficient contrast against Banner's text
+ and icon colors.
diff --git a/packages/ui-banner/src/Banner/v1/index.tsx b/packages/ui-banner/src/Banner/v1/index.tsx
new file mode 100644
index 0000000000..f431021b7a
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/index.tsx
@@ -0,0 +1,157 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { forwardRef } from 'react'
+
+import { useStyleNew } from '@instructure/emotion'
+import { passthroughProps } from '@instructure/ui-react-utils'
+import { warn } from '@instructure/console'
+import { Button, CloseButton } from '@instructure/ui-buttons/latest'
+import { Heading } from '@instructure/ui-heading/latest'
+import { Text } from '@instructure/ui-text/latest'
+import { DiamondInstUIIcon } from '@instructure/ui-icons'
+
+import generateStyle from './styles.js'
+
+import type { BannerProps } from './props'
+
+/**
+---
+category: components
+---
+
+A Banner surfaces a short, prominent message -- typically an announcement
+or a promotion -- with an optional header, an icon, a dismiss control, and
+a call-to-action button.
+
+```js
+---
+type: example
+---
+Your assignments were graded.
+```
+**/
+const Banner = forwardRef(
+ (
+ {
+ color = 'violet',
+ density = 'relaxed',
+ header,
+ children,
+ icon,
+ isDismissible = false,
+ onDismiss,
+ closeButtonLabel,
+ ctaText,
+ onCtaClick,
+ themeOverride,
+ ...rest
+ },
+ ref
+ ) => {
+ const styles = useStyleNew({
+ generateStyle,
+ themeOverride,
+ params: {
+ color,
+ density
+ },
+ componentId: 'Banner',
+ displayName: 'Banner'
+ })
+
+ warn(
+ !isDismissible || !!closeButtonLabel,
+ '[Banner] `closeButtonLabel` should be provided whenever `isDismissible` is `true` so the dismiss button has an accessible label.'
+ )
+
+ const showCloseButton = isDismissible && !!onDismiss
+ const showCta = !!ctaText && !!onCtaClick
+
+ const iconSize = density === 'compact' ? 'sm' : 'md'
+ const defaultIcon =
+ const resolvedIcon = icon ?? defaultIcon
+
+ return (
+
+ )
+ }
+)
+
+Banner.displayName = 'Banner'
+
+export default Banner
+export { Banner }
diff --git a/packages/ui-banner/src/Banner/v1/props.ts b/packages/ui-banner/src/Banner/v1/props.ts
new file mode 100644
index 0000000000..99b5271f4c
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/props.ts
@@ -0,0 +1,122 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import type { ReactNode } from 'react'
+import type { ComponentStyle, NewThemeOverrideProp } from '@instructure/emotion'
+import type { NewComponentTypes } from '@instructure/ui-themes'
+
+type BannerOwnProps = {
+ /**
+ * The color treatment used for the banner's background and default icon.
+ */
+ color?: 'violet' | 'sea'
+
+ /**
+ * Controls the padding, gaps, and font sizes used by the banner.
+ * `'relaxed'` is larger, `'compact'` is smaller.
+ */
+ density?: 'relaxed' | 'compact'
+
+ /**
+ * An optional bold header line shown above the body content.
+ */
+ header?: string
+
+ /**
+ * The banner's body content. This is the main descriptive text (or other
+ * content) shown below the optional `header`.
+ */
+ children: ReactNode
+
+ /**
+ * Overrides the default icon shown in the icon swatch. If not provided, a
+ * decorative diamond icon is used.
+ */
+ icon?: ReactNode
+
+ /**
+ * Whether to show a dismiss (close) button in the top-right corner of the
+ * banner. The close button only renders when both `isDismissible` and
+ * `onDismiss` are provided -- a dismiss button with no handler is a
+ * dead end for users and is bad for accessibility, so it's intentionally
+ * omitted otherwise. When `isDismissible` is `true`, `closeButtonLabel`
+ * must also be provided or a warning is logged in development.
+ */
+ isDismissible?: boolean
+
+ /**
+ * Called when the dismiss button is clicked. Required (in practice) for
+ * the dismiss button to render -- see `isDismissible`.
+ */
+ onDismiss?: () => void
+
+ /**
+ * An accessible label for the dismiss button, read by screen readers.
+ * Required when `isDismissible` is `true`.
+ */
+ closeButtonLabel?: string
+
+ /**
+ * The label for an optional call-to-action button rendered below the
+ * body content. Banner always renders this as its own fixed-style
+ * `Button` (same size and color treatment everywhere) -- consumers
+ * control the label and click behavior, not the button's appearance.
+ * The button only renders when both `ctaText` and `onCtaClick` are
+ * provided.
+ */
+ ctaText?: string
+
+ /**
+ * Called when the call-to-action button is clicked. Required (in
+ * practice) for the button to render -- see `ctaText`.
+ */
+ onCtaClick?: () => void
+}
+
+type PropKeys = keyof BannerOwnProps
+
+type AllowedPropKeys = Readonly>
+
+type BannerProps = BannerOwnProps &
+ NewThemeOverrideProp>
+
+type BannerStyle = ComponentStyle<
+ 'banner' | 'closeButton' | 'iconSwatch' | 'icon' | 'content' | 'cta'
+>
+
+const allowedProps: AllowedPropKeys = [
+ 'color',
+ 'density',
+ 'header',
+ 'children',
+ 'icon',
+ 'isDismissible',
+ 'onDismiss',
+ 'closeButtonLabel',
+ 'ctaText',
+ 'onCtaClick'
+]
+
+export type { BannerProps, BannerStyle }
+export { allowedProps }
diff --git a/packages/ui-banner/src/Banner/v1/styles.ts b/packages/ui-banner/src/Banner/v1/styles.ts
new file mode 100644
index 0000000000..783862d6df
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/styles.ts
@@ -0,0 +1,127 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import type { NewComponentTypes, SharedTokens } from '@instructure/ui-themes'
+import type { BannerProps, BannerStyle } from './props'
+
+type StyleParams = {
+ color: BannerProps['color']
+ density: BannerProps['density']
+}
+
+/**
+ * ---
+ * private: true
+ * ---
+ * Generates the style object from the theme and provided additional information
+ * @param componentTheme The theme variable object.
+ * @param params Additional parameters to customize the style.
+ * @param sharedTokens Shared token object that stores common values for the theme.
+ * @return The final style object, which will be used in the component
+ */
+const generateStyle = (
+ componentTheme: ReturnType,
+ params: StyleParams,
+ _sharedTokens: SharedTokens
+): BannerStyle => {
+ const { color = 'violet', density = 'relaxed' } = params
+ const isCompact = density === 'compact'
+
+ const background =
+ color === 'sea'
+ ? componentTheme.seaBackground
+ : componentTheme.violetBackground
+
+ const iconSwatchBackground =
+ color === 'sea'
+ ? componentTheme.seaIconBackground
+ : componentTheme.violetIconBackground
+
+ const paddingVertical = isCompact
+ ? componentTheme.compactPaddingVertical
+ : componentTheme.relaxedPaddingVertical
+ const paddingHorizontal = isCompact
+ ? componentTheme.compactPaddingHorizontal
+ : componentTheme.relaxedPaddingHorizontal
+ const contentGapHorizontal = isCompact
+ ? componentTheme.compactContentGapHorizontal
+ : componentTheme.relaxedContentGapHorizontal
+ const iconBorderRadius = isCompact
+ ? componentTheme.compactIconBorderRadius
+ : componentTheme.relaxedIconBorderRadius
+
+ return {
+ banner: {
+ label: 'banner',
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'flex-start',
+ gap: contentGapHorizontal,
+ background,
+ color: componentTheme.color,
+ borderStyle: componentTheme.borderStyle,
+ borderWidth: componentTheme.borderWidth,
+ borderColor: componentTheme.borderColor,
+ borderRadius: componentTheme.borderRadius,
+ padding: `${paddingVertical} ${paddingHorizontal}`
+ },
+ closeButton: {
+ label: 'banner__closeButton',
+ position: 'absolute',
+ top: componentTheme.closeButtonMarginTop,
+ right: componentTheme.closeButtonMarginRight
+ },
+ iconSwatch: {
+ label: 'banner__iconSwatch',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: isCompact ? '2rem' : '2.5rem',
+ height: isCompact ? '2rem' : '2.5rem',
+ background: iconSwatchBackground,
+ borderRadius: iconBorderRadius
+ },
+ icon: {
+ label: 'banner__icon',
+ color: componentTheme.iconColor,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ lineHeight: 0
+ },
+ content: {
+ label: 'banner__content',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: componentTheme.contentGapVertical,
+ minWidth: 0
+ },
+ cta: {
+ label: 'banner__cta'
+ }
+ }
+}
+
+export default generateStyle
diff --git a/packages/ui-banner/src/exports/a.ts b/packages/ui-banner/src/exports/a.ts
new file mode 100644
index 0000000000..a677efebea
--- /dev/null
+++ b/packages/ui-banner/src/exports/a.ts
@@ -0,0 +1,27 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+export { default as Banner } from '../Banner/v1/index.js'
+
+export type { BannerProps } from '../Banner/v1/props'
diff --git a/packages/ui-banner/src/exports/b.ts b/packages/ui-banner/src/exports/b.ts
new file mode 100644
index 0000000000..a677efebea
--- /dev/null
+++ b/packages/ui-banner/src/exports/b.ts
@@ -0,0 +1,27 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+export { default as Banner } from '../Banner/v1/index.js'
+
+export type { BannerProps } from '../Banner/v1/props'
diff --git a/packages/ui-banner/tsconfig.build.json b/packages/ui-banner/tsconfig.build.json
new file mode 100644
index 0000000000..de71c0f816
--- /dev/null
+++ b/packages/ui-banner/tsconfig.build.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../tsconfig.build.json",
+ "compilerOptions": {
+ "outDir": "./types",
+ "rootDir": "./src",
+ "composite": true
+ },
+ "include": ["src"],
+ "references": [
+ { "path": "../console/tsconfig.build.json" },
+ { "path": "../emotion/tsconfig.build.json" },
+ { "path": "../shared-types/tsconfig.build.json" },
+ { "path": "../ui-axe-check/tsconfig.build.json" },
+ { "path": "../ui-react-utils/tsconfig.build.json" },
+ { "path": "../ui-babel-preset/tsconfig.build.json" },
+ { "path": "../ui-color-utils/tsconfig.build.json" },
+ { "path": "../ui-themes/tsconfig.build.json" },
+ { "path": "../ui-icons/tsconfig.build.json" },
+ { "path": "../ui-buttons/tsconfig.build.json" }
+ ]
+}
diff --git a/packages/ui-banner/tsconfig.json b/packages/ui-banner/tsconfig.json
new file mode 100644
index 0000000000..b29a7b46c4
--- /dev/null
+++ b/packages/ui-banner/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {}
+}
diff --git a/packages/ui-scripts/package.json b/packages/ui-scripts/package.json
index 7f47b9cede..76b38ae061 100644
--- a/packages/ui-scripts/package.json
+++ b/packages/ui-scripts/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@babel/cli": "^7.27.2",
"@instructure/command-utils": "workspace:*",
- "@instructure/instructure-design-tokens": "github:instructure/instructure-design-tokens#v1.5.0",
+ "@instructure/instructure-design-tokens": "github:instructure/instructure-design-tokens#v1.8.0",
"dprint": "^0.55.1",
"http-server": "^14.1.1",
"inquirer": "^14.0.2",
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 21f888c9e1..21c24eca9a 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -29,6 +29,7 @@
"@instructure/ui-alerts": "workspace:*",
"@instructure/ui-avatar": "workspace:*",
"@instructure/ui-badge": "workspace:*",
+ "@instructure/ui-banner": "workspace:*",
"@instructure/ui-billboard": "workspace:*",
"@instructure/ui-breadcrumb": "workspace:*",
"@instructure/ui-buttons": "workspace:*",
diff --git a/packages/ui/src/v11_7.ts b/packages/ui/src/v11_7.ts
index a0b7911323..8eab1d7a80 100644
--- a/packages/ui/src/v11_7.ts
+++ b/packages/ui/src/v11_7.ts
@@ -47,6 +47,8 @@ export { Avatar } from '@instructure/ui-avatar/v11_7'
export type { AvatarProps } from '@instructure/ui-avatar/v11_7'
export { Badge } from '@instructure/ui-badge/v11_7'
export type { BadgeProps } from '@instructure/ui-badge/v11_7'
+export { Banner } from '@instructure/ui-banner/v11_7'
+export type { BannerProps } from '@instructure/ui-banner/v11_7'
export { Billboard } from '@instructure/ui-billboard/v11_7'
export type { BillboardProps } from '@instructure/ui-billboard/v11_7'
export { Breadcrumb, BreadcrumbLink } from '@instructure/ui-breadcrumb/v11_7'
diff --git a/packages/ui/tsconfig.build.json b/packages/ui/tsconfig.build.json
index 85f3206f78..5fe7ee347d 100644
--- a/packages/ui/tsconfig.build.json
+++ b/packages/ui/tsconfig.build.json
@@ -22,6 +22,9 @@
{
"path": "../ui-badge/tsconfig.build.json"
},
+ {
+ "path": "../ui-banner/tsconfig.build.json"
+ },
{
"path": "../ui-billboard/tsconfig.build.json"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 025a3fe741..9d15426fa6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -662,6 +662,9 @@ importers:
'@instructure/ui-badge':
specifier: workspace:*
version: link:../ui-badge
+ '@instructure/ui-banner':
+ specifier: workspace:*
+ version: link:../ui-banner
'@instructure/ui-billboard':
specifier: workspace:*
version: link:../ui-billboard
@@ -1171,6 +1174,61 @@ importers:
specifier: ^4.1.9
version: 4.1.9(@types/node@22.19.15)(@vitest/browser-playwright@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1)(vite@7.3.1(@types/node@24.1.0)(jiti@2.6.1)(terser@5.48.0)(yaml@2.9.0))
+ packages/ui-banner:
+ dependencies:
+ '@babel/runtime':
+ specifier: ^7.29.7
+ version: 7.29.7
+ '@instructure/console':
+ specifier: workspace:*
+ version: link:../console
+ '@instructure/emotion':
+ specifier: workspace:*
+ version: link:../emotion
+ '@instructure/shared-types':
+ specifier: workspace:*
+ version: link:../shared-types
+ '@instructure/ui-buttons':
+ specifier: workspace:*
+ version: link:../ui-buttons
+ '@instructure/ui-heading':
+ specifier: workspace:*
+ version: link:../ui-heading
+ '@instructure/ui-icons':
+ specifier: workspace:*
+ version: link:../ui-icons
+ '@instructure/ui-react-utils':
+ specifier: workspace:*
+ version: link:../ui-react-utils
+ '@instructure/ui-text':
+ specifier: workspace:*
+ version: link:../ui-text
+ '@instructure/ui-themes':
+ specifier: workspace:*
+ version: link:../ui-themes
+ react:
+ specifier: 18.3.1
+ version: 18.3.1
+ devDependencies:
+ '@instructure/ui-axe-check':
+ specifier: workspace:*
+ version: link:../ui-axe-check
+ '@instructure/ui-babel-preset':
+ specifier: workspace:*
+ version: link:../ui-babel-preset
+ '@instructure/ui-color-utils':
+ specifier: workspace:*
+ version: link:../ui-color-utils
+ '@testing-library/jest-dom':
+ specifier: ^6.9.1
+ version: 6.9.1
+ '@testing-library/react':
+ specifier: 16.3.2
+ version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.1)(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ vitest:
+ specifier: ^4.1.9
+ version: 4.1.9(@types/node@24.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1)(vite@7.3.1(@types/node@24.1.0)(jiti@2.6.1)(terser@5.48.0)(yaml@2.9.0))
+
packages/ui-billboard:
dependencies:
'@babel/runtime':
@@ -3903,8 +3961,8 @@ importers:
specifier: workspace:*
version: link:../command-utils
'@instructure/instructure-design-tokens':
- specifier: github:instructure/instructure-design-tokens#v1.5.0
- version: https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/ae8f600e8ad4cbadddbaad857f0b6477c4a1e1d6
+ specifier: github:instructure/instructure-design-tokens#v1.8.0
+ version: https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/b85c96436131df6a23174a54bcbd3b03a5da1b8a
dprint:
specifier: ^0.55.1
version: 0.55.1
@@ -7065,8 +7123,8 @@ packages:
'@types/node':
optional: true
- '@instructure/instructure-design-tokens@https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/ae8f600e8ad4cbadddbaad857f0b6477c4a1e1d6':
- resolution: {gitHosted: true, tarball: https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/ae8f600e8ad4cbadddbaad857f0b6477c4a1e1d6}
+ '@instructure/instructure-design-tokens@https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/b85c96436131df6a23174a54bcbd3b03a5da1b8a':
+ resolution: {gitHosted: true, tarball: https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/b85c96436131df6a23174a54bcbd3b03a5da1b8a}
version: 1.0.0
'@isaacs/cliui@8.0.2':
@@ -14191,7 +14249,7 @@ snapshots:
'@commitlint/top-level': 21.2.0
'@commitlint/types': 21.2.0
git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0)
- tinyexec: 1.0.4
+ tinyexec: 1.2.4
transitivePeerDependencies:
- conventional-commits-filter
- conventional-commits-parser
@@ -14865,7 +14923,7 @@ snapshots:
optionalDependencies:
'@types/node': 22.19.15
- '@instructure/instructure-design-tokens@https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/ae8f600e8ad4cbadddbaad857f0b6477c4a1e1d6':
+ '@instructure/instructure-design-tokens@https://codeload.github.com/instructure/instructure-design-tokens/tar.gz/b85c96436131df6a23174a54bcbd3b03a5da1b8a':
dependencies:
glob: 13.0.6
diff --git a/regression-test/cypress/e2e/spec.cy.ts b/regression-test/cypress/e2e/spec.cy.ts
index 62424e9fda..27baa54ded 100644
--- a/regression-test/cypress/e2e/spec.cy.ts
+++ b/regression-test/cypress/e2e/spec.cy.ts
@@ -113,6 +113,7 @@ const PAGES: PageSpec[] = [
{ slug: 'alert', title: 'Alert' },
{ slug: 'avatar', title: 'Avatar', wait: 300 },
{ slug: 'badge', title: 'Badge' },
+ { slug: 'banner', title: 'Banner' },
{ slug: 'billboard', title: 'Billboard' },
{
slug: 'breadcrumb',
diff --git a/regression-test/src/app/banner/page.tsx b/regression-test/src/app/banner/page.tsx
new file mode 100644
index 0000000000..ceaffca3b4
--- /dev/null
+++ b/regression-test/src/app/banner/page.tsx
@@ -0,0 +1,71 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+'use client'
+import React from 'react'
+import { Banner as bn, View as vw } from '@instructure/ui/latest'
+
+// alias to avoid TS/SSR friction like other pages
+const Banner = bn as any
+const View = vw as any
+
+export default function BannerPage() {
+ return (
+
+ {/* Default usage */}
+
+
+ Your course has been published and is now visible to students.
+
+
+
+ {/* Colors */}
+
+
+ This banner uses the sea color treatment.
+
+
+
+ {/* Compact density */}
+
+
+ This banner uses the compact density.
+
+
+
+ {/* Dismissible with CTA */}
+
+ {}}
+ ctaText="Learn more"
+ onCtaClick={() => {}}
+ >
+ Try out the new grading workflow before it becomes the default.
+
+
+
+ )
+}