diff --git a/.claude/launch.json b/.claude/launch.json
new file mode 100644
index 0000000000..0bf9891086
--- /dev/null
+++ b/.claude/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.0.1",
+ "configurations": [
+ {
+ "name": "instui-docs",
+ "runtimeExecutable": "pnpm",
+ "runtimeArgs": ["run", "dev"],
+ "port": 9090
+ }
+ ]
+}
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..40fd6183ef
--- /dev/null
+++ b/packages/ui-banner/package.json
@@ -0,0 +1,87 @@
+{
+ "name": "@instructure/ui-banner",
+ "version": "11.7.5",
+ "description": "A UI component for displaying a promotional Banner message",
+ "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-a11y-content": "workspace:*",
+ "@instructure/ui-buttons": "workspace:*",
+ "@instructure/ui-heading": "workspace:*",
+ "@instructure/ui-react-utils": "workspace:*",
+ "@instructure/ui-themes": "workspace:*",
+ "@instructure/ui-view": "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/a.ts",
+ "types": "./types/exports/a.d.ts",
+ "import": "./es/exports/a.js",
+ "require": "./lib/exports/a.js",
+ "default": "./es/exports/a.js"
+ },
+ "./latest": {
+ "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"
+ }
+ }
+}
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..302f9ac9c4
--- /dev/null
+++ b/packages/ui-banner/src/Banner/__tests__/Banner.test.tsx
@@ -0,0 +1,150 @@
+/*
+ * 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 } from 'vitest-browser-react'
+import { page, userEvent } from 'vitest/browser'
+import { describe, it, expect, vi } from 'vitest'
+import { runAxeCheck } from '@instructure/ui-axe-check'
+
+import { Banner } from '@instructure/ui-banner/latest'
+
+describe('', () => {
+ it('should render the header and children', async () => {
+ const { container } = await render(
+
+ Banner body copy
+
+ )
+
+ expect(container).toHaveTextContent('Banner header')
+ expect(container).toHaveTextContent('Banner body copy')
+ })
+
+ it('should render a close button by default and fire onDismiss', async () => {
+ const onDismiss = vi.fn()
+ await render(Banner body copy)
+
+ const closeButton = page.getByRole('button')
+ await closeButton.click()
+
+ expect(onDismiss).toHaveBeenCalled()
+ })
+
+ it('should not render a close button when dismissible is false', async () => {
+ const { container } = await render(
+ Banner body copy
+ )
+
+ expect(container.querySelector('button')).toBeNull()
+ })
+
+ it('should resolve the close button accessible name to the default label', async () => {
+ await render( {}}>Banner body copy)
+
+ const closeButton = page.getByRole('button', { name: 'Close banner' })
+ await expect.element(closeButton).toBeInTheDocument()
+ })
+
+ it('should allow overriding the close button accessible label', async () => {
+ await render(
+ {}}>
+ Banner body copy
+
+ )
+
+ const closeButton = page.getByRole('button', { name: 'Dismiss promo' })
+ await expect.element(closeButton).toBeInTheDocument()
+ })
+
+ it('should render a decorative icon as aria-hidden', async () => {
+ const { container } = await render(
+ icon}
+ >
+ Banner body copy
+
+ )
+
+ const iconWrapper = container.querySelector(
+ '[data-testid="icon"]'
+ )?.parentElement
+
+ expect(iconWrapper).toHaveAttribute('aria-hidden', 'true')
+ })
+
+ it('should render primary and secondary actions', async () => {
+ const { container } = await render(
+ }
+ renderSecondaryAction={() => }
+ >
+ Banner body copy
+
+ )
+
+ expect(container).toHaveTextContent('Primary')
+ expect(container).toHaveTextContent('Secondary')
+ })
+
+ it('should not render a secondary action without a primary action', async () => {
+ const { container } = await render(
+ }
+ >
+ Banner body copy
+
+ )
+
+ expect(container).not.toHaveTextContent('Secondary')
+ })
+
+ it('should be keyboard operable', async () => {
+ const onDismiss = vi.fn()
+ await render(Banner body copy)
+
+ const closeButton = page.getByRole('button').element() as HTMLElement
+ closeButton.focus()
+ await userEvent.keyboard('{Enter}')
+
+ expect(onDismiss).toHaveBeenCalled()
+ })
+
+ it('should meet a11y standards', async () => {
+ const { container } = await render(
+ {}}
+ renderPrimaryAction={() => }
+ >
+ Banner body copy
+
+ )
+
+ 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..faa5dad874
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/README.md
@@ -0,0 +1,134 @@
+---
+describes: Banner
+---
+
+A `Banner` is a promotional message used to proactively surface a feature,
+offer, event, or announcement. It communicates something proactive, not a
+system or account status — use [Alert](#Alert) for status, warnings, and
+confirmations that need to interrupt and be responded to.
+
+### Default
+
+By default, `Banner` is dismissible and shows an optional header, body copy,
+and a single primary action.
+
+```js
+---
+type: example
+---
+ {}}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua.
+
+```
+
+### Colors
+
+Use `color` to choose the visual treatment that best fits the surrounding
+page.
+
+```js
+---
+type: example
+---
+
+ {}}
+ margin="0 0 medium 0"
+ >
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+ {}}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+
+```
+
+### Density
+
+Use `density="compact"` when there's less room for the banner, such as inside
+a smaller container.
+
+```js
+---
+type: example
+---
+ {}}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+```
+
+### Dismissible
+
+Reserve `dismissible={false}` for time-sensitive or recurring campaign
+banners, where the user shouldn't lose track of the message. A `Banner` is a
+controlled component — it does not manage its own visibility, so mount and
+unmount it based on your own state in `onDismiss`.
+
+```js
+---
+type: example
+---
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+```
+
+### Actions
+
+Use `renderPrimaryAction` when there's a next step for the user to take, and
+`renderSecondaryAction` for at most one additional action. Don't use actions
+for page navigation — link the header or message text instead.
+
+```js
+---
+type: example
+---
+ {}}
+ renderPrimaryAction={() => }
+ renderSecondaryAction={() => (
+
+ )}
+>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+```
+
+### Icon
+
+`renderIcon` accepts a function that returns a decorative icon or
+illustration. It's treated as purely decorative, so don't rely on it to
+convey information — put anything meaningful in the header or body copy.
+
+```js
+---
+type: example
+---
+ {}}
+ renderIcon={() => }
+>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+```
+
+### Guidelines
+
+```js
+---
+type: embed
+---
+
+
+ The icon is decorative; don't rely on it to convey meaning
+ The dismiss button always needs an accessible label
+
+
+```
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..c36647f6af
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/index.tsx
@@ -0,0 +1,147 @@
+/*
+ * 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 { CloseButton } from '@instructure/ui-buttons/latest'
+import { Heading } from '@instructure/ui-heading/latest'
+import { View } from '@instructure/ui-view/latest'
+
+import { useStyleNew } from '@instructure/emotion'
+import generateStyle from './styles.js'
+
+import type { BannerProps } from './props'
+
+/**
+---
+category: components
+---
+
+A `Banner` is a promotional message used to proactively surface a feature,
+offer, event, or announcement. Unlike `Alert`, a `Banner` doesn't communicate
+system or account status — use `Alert` for that instead.
+
+```js
+---
+type: example
+---
+ {}}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua.
+
+```
+**/
+const Banner = forwardRef(
+ (
+ {
+ color = 'violet',
+ density = 'relaxed',
+ dismissible = true,
+ onDismiss,
+ closeButtonLabel = 'Close banner',
+ renderIcon,
+ header,
+ children,
+ renderPrimaryAction,
+ renderSecondaryAction,
+ elementRef,
+ margin,
+ themeOverride
+ },
+ ref
+ ) => {
+ const styles = useStyleNew({
+ generateStyle,
+ themeOverride,
+ params: {
+ color,
+ density
+ },
+ componentId: 'Banner',
+ displayName: 'Banner'
+ })
+
+ const handleRef = (el: Element | null) => {
+ if (typeof elementRef === 'function') {
+ elementRef(el)
+ }
+ if (typeof ref === 'function') {
+ ref(el as HTMLDivElement)
+ } else if (ref) {
+ const refObject = ref
+ refObject.current = el as HTMLDivElement
+ }
+ }
+
+ const hasPrimaryAction = typeof renderPrimaryAction === 'function'
+ const hasSecondaryAction =
+ hasPrimaryAction && typeof renderSecondaryAction === 'function'
+
+ return (
+
+ {renderIcon && (
+
+ {renderIcon()}
+
+ )}
+
+
+ {header && (
+
+ {header}
+
+ )}
+ {children &&
{children}
}
+
+ {hasPrimaryAction && (
+
+ {renderPrimaryAction!()}
+ {hasSecondaryAction && renderSecondaryAction!()}
+
+ )}
+
+ {dismissible && (
+
+
+
+ )}
+
+ )
+ }
+)
+
+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..e9ee36f364
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/props.ts
@@ -0,0 +1,132 @@
+/*
+ * 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,
+ Spacing
+} from '@instructure/emotion'
+import type { NewComponentTypes } from '@instructure/ui-themes'
+
+type BannerOwnProps = {
+ /**
+ * The visual color treatment of the `Banner`.
+ */
+ color?: 'violet' | 'sea'
+
+ /**
+ * Controls the padding and internal spacing of the `Banner`. Use `compact`
+ * when there's less real estate for the banner such as smaller containers.
+ */
+ density?: 'relaxed' | 'compact'
+
+ /**
+ * Whether the `Banner` can be dismissed by the user. Reserve `false` for
+ * time-sensitive or recurring campaign banners where the user shouldn't
+ * lose track of the message.
+ */
+ dismissible?: boolean
+
+ /**
+ * Callback fired when the user dismisses the `Banner`. Required when
+ * `dismissible` is `true` — the `Banner` does not manage its own visibility.
+ */
+ onDismiss?: () => void
+
+ /**
+ * An accessible label for the dismiss button.
+ */
+ closeButtonLabel?: ReactNode
+
+ /**
+ * A function returning a decorative icon or illustration to render at the
+ * start of the `Banner`. Treated as purely decorative — pass meaningful
+ * content via `children` instead.
+ */
+ renderIcon?: () => ReactNode
+
+ /**
+ * An optional title for the `Banner`.
+ */
+ header?: ReactNode
+
+ /**
+ * The body content of the `Banner`.
+ */
+ children?: ReactNode
+
+ /**
+ * A function returning the primary action element (for example, a `Button`).
+ * Don't use this for page navigation — link the title or message instead.
+ */
+ renderPrimaryAction?: () => ReactNode
+
+ /**
+ * A function returning a secondary action element. Only rendered alongside
+ * a primary action.
+ */
+ renderSecondaryAction?: () => ReactNode
+
+ /**
+ * Provides a reference to the `Banner`'s underlying html element.
+ */
+ elementRef?: (element: Element | null) => void
+
+ /**
+ * Valid values are `0`, `none`, `auto`, and Spacing token values,
+ * see https://instructure.design/layout-spacing. Apply these values via
+ * familiar CSS-like shorthand. For example, `margin="general.spaceMd auto"`.
+ */
+ margin?: Spacing
+}
+
+type PropKeys = keyof BannerOwnProps
+
+type AllowedPropKeys = Readonly>
+
+type BannerProps = BannerOwnProps &
+ NewThemeOverrideProp>
+
+type BannerStyle = ComponentStyle<
+ 'banner' | 'icon' | 'content' | 'contentInner' | 'actions' | 'closeButton'
+>
+
+const allowedProps: AllowedPropKeys = [
+ 'color',
+ 'density',
+ 'dismissible',
+ 'onDismiss',
+ 'closeButtonLabel',
+ 'renderIcon',
+ 'header',
+ 'children',
+ 'renderPrimaryAction',
+ 'renderSecondaryAction',
+ 'elementRef',
+ 'margin'
+]
+
+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..bb5699a2a5
--- /dev/null
+++ b/packages/ui-banner/src/Banner/v1/styles.ts
@@ -0,0 +1,139 @@
+/*
+ * 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, density } = params
+
+ const backgroundByColor = {
+ violet: componentTheme.violetBackground,
+ sea: componentTheme.seaBackground
+ }
+
+ const iconBackgroundByColor = {
+ violet: componentTheme.violetIconBackground,
+ sea: componentTheme.seaIconBackground
+ }
+
+ const paddingByDensity = {
+ relaxed: `${componentTheme.relaxedPaddingVertical} ${componentTheme.relaxedPaddingHorizontal}`,
+ compact: `${componentTheme.compactPaddingVertical} ${componentTheme.compactPaddingHorizontal}`
+ }
+
+ const iconBorderRadiusByDensity = {
+ relaxed: componentTheme.relaxedIconBorderRadius,
+ compact: componentTheme.compactIconBorderRadius
+ }
+
+ // Matches the Figma spec's Illustration frame (32px, both densities).
+ // Not part of the Banner token set, so kept as a literal, like closeButtonMargin*.
+ const iconSize = '2rem'
+
+ const contentGapHorizontalByDensity = {
+ relaxed: componentTheme.relaxedContentGapHorizontal,
+ compact: componentTheme.compactContentGapHorizontal
+ }
+
+ return {
+ banner: {
+ label: 'banner',
+ position: 'relative',
+ display: 'flex',
+ borderRadius: componentTheme.borderRadius,
+ borderStyle: componentTheme.borderStyle,
+ borderWidth: componentTheme.borderWidth,
+ borderColor: componentTheme.borderColor,
+ color: componentTheme.color,
+ background: backgroundByColor[color!],
+ padding: paddingByDensity[density!],
+ gap: contentGapHorizontalByDensity[density!]
+ },
+ icon: {
+ label: 'banner__icon',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: iconSize,
+ height: iconSize,
+ background: iconBackgroundByColor[color!],
+ color: componentTheme.iconColor,
+ borderRadius: iconBorderRadiusByDensity[density!],
+ fontSize: '1rem',
+ '& svg': {
+ width: '1rem',
+ height: '1rem'
+ }
+ },
+ content: {
+ label: 'banner__content',
+ display: 'flex',
+ flexDirection: 'column',
+ flexGrow: 1,
+ gap: componentTheme.contentGapVertical
+ },
+ contentInner: {
+ label: 'banner__contentInner',
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ actions: {
+ label: 'banner__actions',
+ display: 'flex',
+ alignItems: 'center',
+ // Matches the Figma spec's button group itemSpacing (12px). Not part
+ // of the Banner token set, so kept as a literal.
+ gap: '0.75rem'
+ },
+ closeButton: {
+ label: 'banner__closeButton',
+ position: 'absolute',
+ top: componentTheme.closeButtonMarginTop,
+ insetInlineEnd: componentTheme.closeButtonMarginRight
+ }
+ }
+}
+
+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..fc58933519
--- /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 { 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..0e746efd28
--- /dev/null
+++ b/packages/ui-banner/tsconfig.build.json
@@ -0,0 +1,23 @@
+{
+ "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-a11y-content/tsconfig.build.json" },
+ { "path": "../ui-axe-check/tsconfig.build.json" },
+ { "path": "../ui-babel-preset/tsconfig.build.json" },
+ { "path": "../ui-buttons/tsconfig.build.json" },
+ { "path": "../ui-color-utils/tsconfig.build.json" },
+ { "path": "../ui-heading/tsconfig.build.json" },
+ { "path": "../ui-react-utils/tsconfig.build.json" },
+ { "path": "../ui-themes/tsconfig.build.json" },
+ { "path": "../ui-view/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 141579a530..9cf3fe6d41 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 c2fc557ca8..f82017cc05 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 f821ddec79..9b9a9eaeb9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -668,6 +668,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
@@ -1177,6 +1180,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-a11y-content':
+ specifier: workspace:*
+ version: link:../ui-a11y-content
+ '@instructure/ui-buttons':
+ specifier: workspace:*
+ version: link:../ui-buttons
+ '@instructure/ui-heading':
+ specifier: workspace:*
+ version: link:../ui-heading
+ '@instructure/ui-react-utils':
+ specifier: workspace:*
+ version: link:../ui-react-utils
+ '@instructure/ui-themes':
+ specifier: workspace:*
+ version: link:../ui-themes
+ '@instructure/ui-view':
+ specifier: workspace:*
+ version: link:../ui-view
+ 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':
@@ -3906,8 +3964,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':
@@ -14992,7 +15050,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 2becb31ce8..9cf728b2de 100644
--- a/regression-test/cypress/e2e/spec.cy.ts
+++ b/regression-test/cypress/e2e/spec.cy.ts
@@ -140,6 +140,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..1bcea3c855
--- /dev/null
+++ b/regression-test/src/app/banner/page.tsx
@@ -0,0 +1,78 @@
+/*
+ * 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,
+ Button as btn,
+ SearchInstUIIcon as sii
+} from '@instructure/ui/latest'
+
+// alias to avoid TS/SSR friction like other pages
+const Banner = bn as any
+const Button = btn as any
+const SearchInstUIIcon = sii as any
+
+export default function BannerPage() {
+ return (
+
+
+ {}}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+
+
+
+ {}}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+