Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guides/accessing-the-dom.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Accessing the DOM
category: Guides
order: 3
order: 4
relevantForAI: true
---

Expand Down
108 changes: 108 additions & 0 deletions docs/guides/component-versioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: Component versioning
category: Guides
order: 2
---

## Why components are versioned

When InstUI needs to make a breaking change to a component (renamed props, changed behaviour, etc.), the old version is **kept alongside the new one** instead of being replaced. Upgrading the library no longer forces you to immediately rewrite every component usage — you can migrate to new versions on your own schedule.

New component versions appear with InstUI **minor** version bumps (e.g. `11.7` → `11.8`). Patch releases never include breaking changes.

## Import paths

Every InstUI component package supports three import styles:

### Default — `@instructure/ui-<name>`

Always points to the **oldest** still-supported component version. Upgrading the library without changing your imports will keep your code working without surprises.

```js
---
type: code
---
import { Alert } from '@instructure/ui-alerts'
```

### Pinned — `@instructure/ui-<name>/v11_X`

Locks the import to a specific InstUI minor version of the component. When you are ready to adopt a breaking change, update the path to the next pinned version.

```js
---
type: code
---
import { Alert } from '@instructure/ui-alerts/v11_7'
```

### Latest — `@instructure/ui-<name>/latest`

Always points to the newest component version. This may bring breaking changes when you upgrade InstUI itself.

```js
---
type: code
---
import { Alert } from '@instructure/ui-alerts/latest'
```

### Per-package or umbrella package

InstUI also ships an umbrella package, `@instructure/ui`, which re-exports every component from the individual `@instructure/ui-*` packages. Two equivalent import styles work — both resolve to the same component:

```js
---
type: code
---
// per-package import
import { Alert } from '@instructure/ui-alerts/v11_7'

// umbrella package import
import { Alert } from '@instructure/ui/v11_7'
```

The same three path styles (default / `/v11_X` / `/latest`) work on the umbrella package as well. Pick per-package imports when you want better tree-shaking and only pull in what you use, or the umbrella package when you'd rather depend on a single `@instructure/ui` entry in your `package.json`.

## Versions and theming engines

InstUI is in the middle of a transition between two theming systems. Which engine a component uses depends on which version you import:

- **`v11_6` and earlier** — legacy theming. Components are configured through the Canvas theme variables and the `themeOverride` prop, which accepts a function or object that maps to the component's own theme map. See the [Legacy theme overrides](legacy-theme-overrides) guide.

- **`v11_7` and newer** — new theming system. Components consume pre-resolved design tokens, and theming is done through the new token override structure. See the [New theme overrides](new-theme-overrides) guide.

Mixing imports from both groups in the same app is fully supported — the two engines run side-by-side without conflict.

### Supported themes per version

You import themes the same way as before — from `@instructure/ui-themes` — and pass them to `InstUISettingsProvider`:

```js
---
type: code
---
import { canvas } from '@instructure/ui-themes'

<InstUISettingsProvider theme={canvas}>
<App />
</InstUISettingsProvider>
```

The `canvas` (and `canvasHighContrast`) export works for **both `v11_6` and `v11_7+` components in the same app** — internally it carries the data each engine needs. You don't need to switch theme objects when you bump a component import to `/v11_7`.

- **`v11_6` and earlier** — supports the original two themes:

- `canvas` — default theme used by Canvas products
- `canvasHighContrast` — same as `canvas`, with colors WCAG-tuned for high-contrast accessibility

- **`v11_7` and newer** — supports the same two themes (rendered through the new engine, labelled `(legacy)` in the docs UI Theme selector) plus two brand-new ones:

- `canvas` — same import as above, now driven by the new engine (labelled as `(legacy)`)
- `canvasHighContrast` — same import as above, now driven by the new engine (labelled as `(legacy)`)
- `light` — new light theme
- `dark` — new dark theme

This means that when you move a component import from `/v11_6` to `/v11_7`, you can continue using the `canvas` theme to maintain a familiar look and feel, and opt in to `light` or `dark` only when you're ready.

> The `@instructure/ui-themes` package also exports `legacyCanvas` and `legacyCanvasHighContrast`. These are the raw new-engine forms that `canvas` / `canvasHighContrast` wrap internally — most consumers don't need to import them directly.
2 changes: 1 addition & 1 deletion docs/guides/forms.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Forms
category: Guides
order: 4
order: 5
relevantForAI: true
---

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/module-federation.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Module federation
category: Guides
order: 2
order: 3
relevantForAI: true
---

Expand Down
47 changes: 45 additions & 2 deletions docs/theming/new-theme-overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,9 +527,52 @@ type: example
</InstUISettingsProvider>
```

### 13. Provider-level overrides cannot target a child component selectively
### 13. Independent overrides for child parts of compound components

Because `Button` uses `BaseButton`'s theme internally, a `components.Button` entry in the provider's `themeOverride` does **not** override `BaseButton`'s tokens for `Button` instances only. Both `BaseButton` and `Button` share the same `BaseButton` theme variables, so a `components.BaseButton` override affects both, regardless of whether a separate `components.Button` entry is also present.
Most compound components expose each part as a separate component with its own `componentId`. This means you can independently override each part via `components` — both overrides take effect:

```js
---
type: example
---
<InstUISettingsProvider theme={canvas}>
<InstUISettingsProvider
themeOverride={{
components: {
TableColHeader: {
background: 'rebeccapurple',
color: 'gold'
},
TableRowHeader: {
background: 'deeppink',
color: 'white'
}
}
}}
>
<Table caption="Independent overrides: ColHeader purple, RowHeader deeppink">
<Table.Head>
<Table.Row>
<Table.ColHeader id="row-headers">Row headers column - purple</Table.ColHeader>
<Table.ColHeader id="cells">Cells column - purple</Table.ColHeader>
</Table.Row>
</Table.Head>
<Table.Body>
<Table.Row>
<Table.RowHeader>TableRowHeader — deeppink</Table.RowHeader>
<Table.Cell>TableCell — unchanged</Table.Cell>
</Table.Row>
<Table.Row>
<Table.RowHeader>TableRowHeader — deeppink</Table.RowHeader>
<Table.Cell>TableCell — unchanged</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</InstUISettingsProvider>
</InstUISettingsProvider>
```

**Exception — `Button` and `BaseButton`:** `Button` uses `BaseButton`'s `componentId` internally, so `components.Button` has no effect. A `components.BaseButton` override affects all `BaseButton` instances including those rendered inside `Button` — there is no way to target only one:

```js
---
Expand Down
7 changes: 5 additions & 2 deletions packages/__docs__/buildScripts/DataTypes.mts
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,14 @@ type ResolvedColors = {
semantic: Record<string, string>
}

// At runtime, build-docs.mts also attaches `resolvedComponents` (and on
// canvas / canvas-high-contrast: `key`, `description`) to the new-system
// entries. Not declared per-branch; surfaced as optional on `MainDocsData.themes`.
type ThemeResource =
| (BaseTheme & { resolvedComponents: Record<string, any> }) // legacy-canvas, legacy-canvas-high-contrast
| (NewBaseTheme & { resolvedColors: ResolvedColors }) // canvas, canvas-high-contrast
| (LightTheme & { resolvedColors: ResolvedColors })
| (DarkTheme & { resolvedColors: ResolvedColors })
| (LightTheme & { resolvedColors: ResolvedColors }) // light
| (DarkTheme & { resolvedColors: ResolvedColors }) // dark
| SharedTokens

type MainDocsData = {
Expand Down
35 changes: 31 additions & 4 deletions packages/__docs__/buildScripts/build-docs.mts
Original file line number Diff line number Diff line change
Expand Up @@ -420,17 +420,44 @@ function parseThemes() {
parsed['legacy-canvas-high-contrast'] = {
resource: { ...canvasHighContrast, resolvedComponents: resolveComponents(legacyCanvasHighContrast) }
}
// `key` is read by Document.tsx's `componentDidUpdate` to detect theme
// changes and refetch the Default Theme Variables. `legacyCanvas` /
// `legacyCanvasHighContrast` from `newThemeTokens` do not include a `key`
// field (unlike `light` / `dark`, which come through wrappers that set it).
// Without it, switching e.g. canvas (legacy) → canvas-high-contrast (legacy)
// on v11_7 leaves `themeVariables.key` `undefined` on both sides, so
// `undefined !== undefined` is false and the refetch never fires.
parsed['canvas'] = {
resource: { ...legacyCanvas, resolvedColors: resolveNewThemeColors(legacyCanvas), description: canvas.description }
resource: {
...legacyCanvas,
key: 'canvas',
resolvedColors: resolveNewThemeColors(legacyCanvas),
resolvedComponents: resolveComponents(legacyCanvas),
description: canvas.description
}
}
parsed['canvas-high-contrast'] = {
resource: { ...legacyCanvasHighContrast, resolvedColors: resolveNewThemeColors(legacyCanvasHighContrast), description: canvasHighContrast.description }
resource: {
...legacyCanvasHighContrast,
key: 'canvas-high-contrast',
resolvedColors: resolveNewThemeColors(legacyCanvasHighContrast),
resolvedComponents: resolveComponents(legacyCanvasHighContrast),
description: canvasHighContrast.description
}
}
parsed[light.key] = {
resource: { ...light, resolvedColors: resolveNewThemeColors(light.newTheme as typeof legacyCanvas) }
resource: {
...light,
resolvedColors: resolveNewThemeColors(light.newTheme as typeof legacyCanvas),
resolvedComponents: resolveComponents(light.newTheme as typeof legacyCanvas)
}
}
parsed[dark.key] = {
resource: { ...dark, resolvedColors: resolveNewThemeColors(dark.newTheme as typeof legacyCanvas) }
resource: {
...dark,
resolvedColors: resolveNewThemeColors(dark.newTheme as typeof legacyCanvas),
resolvedComponents: resolveComponents(dark.newTheme as typeof legacyCanvas)
}
}
const canvasSemantics = legacyCanvas.semantics(legacyCanvas.primitives)
parsed['shared-tokens'] = { resource: legacyCanvas.sharedTokens(canvasSemantics) }
Expand Down
Loading
Loading