Skip to content

Commit de5e92b

Browse files
committed
feat(ui-scripts,ui-icons,ui-codemods): add brand/custom icons
1 parent 3091422 commit de5e92b

144 files changed

Lines changed: 6256 additions & 2465 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/contributor-docs/adding-icons.md

Lines changed: 30 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,92 +4,52 @@ category: Contributor Guides
44
order: 9
55
---
66

7-
## Adding and Modifying Icons
7+
## Lucide Icons
88

9-
- Use dashes in the name of the .svg files (e.g `calendar-month`).
10-
- Use the same name for the "Line" and "Solid" variants, and save them in the respective folder, e.g. `instructure-ui/packages/ui-icons/svg/Line/calendar-month.svg` and `instructure-ui/packages/ui-icons/svg/Solid/calendar-month.svg`.
11-
- Double-check that the SVG size is 1920x1920.
9+
The bulk of the icon set comes from [Lucide](https://lucide.dev). These are not manually maintained
10+
— they are automatically picked up from the `lucide-react` npm package at build time.
1211

13-
```js
14-
---
15-
type: code
16-
---
17-
<svg
18-
width="1920"
19-
height="1920"
20-
viewBox="0 0 1920 1920"
21-
fill="none"
22-
xmlns="http://www.w3.org/2000/svg"
23-
>
24-
{...}
25-
</svg>
26-
```
27-
28-
- The files cannot contain [clipping paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath)! Sadly, when the Designers export icons from Figma, most of the time they have a clipping path around the whole canvas. When an SVG includes clipping paths, the `Icon Font` variant may not render correctly. Specifically, the use of `<g clip-path="...">` and `<clipPath id="...">` elements can cause rendering issues. If the source code has them, manually refactor the code, e.g:
29-
30-
```js
31-
---
32-
type: code
33-
---
12+
**To get a new or updated Lucide icon**, bump the `lucide-react` version in
13+
`packages/ui-icons/package.json`. The index is regenerated automatically as part of
14+
`pnpm run bootstrap` (via `build-icons`), so no manual step is needed.
15+
16+
Every icon exported by `lucide-react` becomes `<LucideIconName>InstUIIcon` in `@instructure/ui-icons`,
17+
**except** for icons that are shadowed by a custom icon of the same name (see below).
18+
19+
If a Lucide icon is missing or looks wrong, check whether it exists in the installed version of
20+
`lucide-react` first — if not, the only path is to add it as a custom icon.
3421

35-
// Before:
36-
<svg
37-
width="1920"
38-
height="1920"
39-
viewBox="0 0 1920 1920"
40-
fill="none"
41-
xmlns="http://www.w3.org/2000/svg"
42-
>
43-
<g clip-path="url(#clip0_1007_24)">
44-
<path d="..." fill="#2D3B45" />
45-
</g>
46-
<defs>
47-
<clipPath id="clip0_1007_24">
48-
<rect width="1920" height="1920" fill="white" />
49-
</clipPath>
50-
</defs>
51-
</svg>
22+
## Adding Custom Icons
5223

53-
// After:
54-
<svg
55-
width="1920"
56-
height="1920"
57-
viewBox="0 0 1920 1920"
58-
fill="none"
59-
xmlns="http://www.w3.org/2000/svg"
60-
>
61-
<path d="..." fill="#2D3B45" />
62-
</svg>
63-
```
24+
Custom icons live in `packages/ui-icons/svg/Custom/` and are consumed directly by the build script (`scripts/generateCustomIndex.ts`).
6425

65-
- If the icon has to be bidirectional (being mirrored in RTL mode, typically arrow icons), add the icon name to the bidirectional list in `packages/ui-icons/icons.config.js`. Deprecated icons are handled here as well.
26+
- Use kebab-case filenames ending in `.svg`. The filename becomes the React export name: `ai-info.svg``AiInfoInstUIIcon`, `canvas-logo.svg``CanvasLogoInstUIIcon`.
6627

67-
- Run `pnpm run bootstrap`.
28+
- For solid/filled icons, the filename must end in `-solid.svg` (e.g. `bell-solid.svg`).
6829

69-
- Finally, run `pnpm run dev` and verify that the icons are displayed correctly under [Icons](/#icons). Check all 3 versions (React, SVG and icon font).
30+
- If a custom icon has the same name as a Lucide icon (e.g. `message-square-check.svg`), the custom version takes precedence and the Lucide one is hidden from the package.
7031

71-
(Note: The fonts are sometimes not rendered correctly, but we decided not to fix them, because they are not really used anywhere, and we might stop supporting icon fonts in the future in general.)
32+
- After dropping the file into `svg/Custom/`, the index is regenerated automatically as part of
33+
`pnpm run bootstrap` (via `build-icons`).
7234

73-
### Guidelines for Drawing Icons
35+
- Run `pnpm run dev` and verify the icon looks correct in the Icons gallery.
7436

75-
- Draw your icons on the 1920 x 1920 art-boards.
37+
### Drawing Guidelines
7638

77-
- Before you flatten shapes or vectorize strokes as described below, make a hidden copy of the original paths off
78-
to the side so that you can more easily come back and make changes later.
39+
- Uncheck "Clip content" on the frame before exporting. Otherwise Figma wraps every layer in `<g clip-path="url(#…)">` and adds a `<defs><clipPath>…</clipPath></defs>` block, which can cause rendering issues
7940

80-
- Flatten your shapes.
41+
- Use `currentColor` for all path fills and strokes. The build script reads the SVG as-is — no color replacement happens. If you exported with a hardcoded hex value, replace it manually before regenerating the index
8142

82-
- Export strokes to vector.
43+
- Stroke icons: set `fill="none"` on every path, not just on the root `<svg>`. Select all shape layers and set Fill to None in the Design panel
8344

84-
- Don’t use borders on vectors, especially not inside/outside borders which aren’t supported in SVG. Do not use clipping paths.
45+
- Remove `width` and `height` from the `<svg>` root — keep only `viewBox` and `xmlns`. Export at 1× in Figma
8546

86-
- Make sure none of the paths go outside of the art-board. If so, the glyph in the icon font will be misaligned.
87-
Draw inside the lines.
47+
- Flatten all transforms before exporting (Object → Flatten Selection)
8848

89-
- Fill the space edge-to-edge as much as possible. The build process will add margins as needed.
49+
- Standard icons use `viewBox="0 0 24 24"`. Brand/logo icons can use any square viewBox (e.g. `0 0 1920 1920`)
9050

91-
- Don't use inline styles
51+
- Mixed stroke + fill icons are supported. Paths with `fill="currentColor"` render filled; paths with `fill="none"` and a `stroke` render as outlines
9252

93-
- Don't use `class` or `for` attributes
53+
- Do not use per-element `stroke-width`. The wrapper applies a uniform stroke width derived from the icon size
9454

95-
- Always have `<svg>` as the root tag
55+
- Do not use `<mask>` or `<use>` elements. These are not supported — flatten or redesign the layer

docs/guides/upgrade-guide.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,24 @@ TODO add details
1212

1313
## New icons
1414

15-
InstUI has switched to a new icon set, [Lucide](https://lucide.dev/icons/). We are still keeping some Instructure-specific icons, like product logos. We have a codemod that will help you migrate your code to the new icon set (see below).
15+
InstUI has switched to a new icon set based on [Lucide](https://lucide.dev/icons/). We are still keeping some Instructure-specific icons, like product logos, also new custom icons are added. We have a codemod that will help you migrate your code to the new icon set.
1616

1717
### Lucide Icons Package
1818

19-
InstUI v12 introduces a new icon package **`@instructure/ui-icons-lucide`** based on the [Lucide](https://lucide.dev/icons/) icon library, providing 1,700+ icons with improved theming and RTL support. The new Lucide icons are wrapped with `wrapLucideIcon` to integrate with InstUI's theming system while maintaining access to all native Lucide props.
19+
InstUI introduces new icons based on the [Lucide](https://lucide.dev/icons/) icon library, providing 1,900+ stroke-based icons with improved theming and RTL support. The icons are wrapped with `wrapLucideIcon` to integrate with InstUI's theming system while maintaining access to all native icon props.
2020

2121
**Key differences from `SVGIcon`/`InlineSVG`:**
2222

23-
| Property | Old API (SVGIcon) | New API (Lucide) |
23+
| Property | Old API (SVGIcon) | New API |
2424
| :-------------- | :---------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- |
2525
| **size** | `'x-small'` \| `'small'` \| `'medium'` \| `'large'` \| `'x-large'` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'2xl'` \| `number` (pixels) |
2626
| **color** | Limited tokens: `'primary'` \| `'secondary'` \| `'success'` \| `'error'` \| `'warning'` \| etc. | 60+ theme tokens (`'baseColor'`, `'successColor'`, `'actionPrimaryBaseColor'`, etc.) or any CSS color |
27-
| **strokeWidth** | ❌ Not available | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'2xl'` \| `number` \| `string` |
2827
| **children** | `React.ReactNode` | ❌ Removed |
2928
| **focusable** | `boolean` | ❌ Removed |
3029
| **description** | `string` (combined with title) | ❌ Removed (use `title` only) |
3130
| **src** | `string` | ❌ Removed |
3231

33-
The new icons automatically sync with theme changes, support all InstUI color tokens, and provide better TypeScript integration. All standard HTML and SVG attributes can be passed directly to Lucide icons and will be spread onto the nested SVG element. Existing `@instructure/ui-icons` package remains available for legacy Instructure-specific icons.
32+
The new icons automatically sync with theme changes, support all InstUI color tokens, and provide better TypeScript integration. All standard HTML and SVG attributes can be passed directly to Lucide icons and will be spread onto the nested SVG element.
3433

3534
## Focus rings
3635

docs/patterns/UsingIcons.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,116 @@ relevantForAI: true
77

88
## Using Icons
99

10+
Icons from `@instructure/ui-icons` are available as `<Name>InstUIIcon` components — browse them
11+
in the [Icons gallery](#icons). Legacy class-component icons (`IconHeartLine`, `IconSearchLine`,
12+
etc.) are still available for backwards compatibility but are deprecated — see
13+
[Legacy Icons](#legacy-icons) below. Components with the new theming system only accept new icons.
14+
15+
### Accessibility
16+
17+
Without a `title` prop the icon is decorative: `aria-hidden="true"` and `role="presentation"` are
18+
set automatically. When a `title` is provided, the icon becomes meaningful: `aria-label` is set to
19+
the title value and `role="img"` is added.
20+
21+
```js
22+
---
23+
type: example
24+
---
25+
<View as="div" margin="small" padding="medium" background="primary">
26+
<Text as="div" size="large">I <HeartInstUIIcon size="md" color="errorColor" title="Love" /> New York</Text>
27+
</View>
28+
```
29+
30+
### Size
31+
32+
Use the `size` prop with a semantic size token. Stroke width scales automatically with the size.
33+
34+
```js
35+
---
36+
type: example
37+
---
38+
<View as="div" margin="small" padding="medium" background="primary">
39+
<Flex wrap="wrap" alignItems="center" gap="small">
40+
<Flex.Item><Text>xs</Text> <SearchInstUIIcon size="xs" /></Flex.Item>
41+
<Flex.Item><Text>sm</Text> <SearchInstUIIcon size="sm" /></Flex.Item>
42+
<Flex.Item><Text>md</Text> <SearchInstUIIcon size="md" /></Flex.Item>
43+
<Flex.Item><Text>lg</Text> <SearchInstUIIcon size="lg" /></Flex.Item>
44+
<Flex.Item><Text>xl</Text> <SearchInstUIIcon size="xl" /></Flex.Item>
45+
<Flex.Item><Text>2xl</Text> <SearchInstUIIcon size="2xl" /></Flex.Item>
46+
</Flex>
47+
</View>
48+
```
49+
50+
Illustration sizes (`illu-sm`, `illu-md`, `illu-lg`) are intended for larger decorative contexts.
51+
52+
### Color
53+
54+
Use the `color` prop with a semantic color token — a theme-aware named value (e.g. `errorColor`,
55+
`successColor`) that automatically adapts to the active theme. `inherit` passes `currentColor` through from the parent element.
56+
`ai` renders the icon with an AI gradient.
57+
58+
```js
59+
---
60+
type: example
61+
---
62+
<View as="div" margin="small" padding="medium" background="primary">
63+
<Flex wrap="wrap" alignItems="center" gap="small">
64+
<Flex.Item><HeartInstUIIcon size="lg" color="baseColor" /></Flex.Item>
65+
<Flex.Item><HeartInstUIIcon size="lg" color="mutedColor" /></Flex.Item>
66+
<Flex.Item><HeartInstUIIcon size="lg" color="successColor" /></Flex.Item>
67+
<Flex.Item><HeartInstUIIcon size="lg" color="errorColor" /></Flex.Item>
68+
<Flex.Item><HeartInstUIIcon size="lg" color="warningColor" /></Flex.Item>
69+
<Flex.Item><HeartInstUIIcon size="lg" color="infoColor" /></Flex.Item>
70+
<Flex.Item><AiInfoInstUIIcon size="lg" color="ai" /></Flex.Item>
71+
</Flex>
72+
</View>
73+
```
74+
75+
### Rotate
76+
77+
```js
78+
---
79+
type: example
80+
---
81+
<View as="div" margin="small" padding="medium" background="primary">
82+
<Flex wrap="wrap" alignItems="center" gap="small">
83+
<Flex.Item><ChevronUpInstUIIcon size="lg" rotate="0" /></Flex.Item>
84+
<Flex.Item><ChevronUpInstUIIcon size="lg" rotate="90" /></Flex.Item>
85+
<Flex.Item><ChevronUpInstUIIcon size="lg" rotate="180" /></Flex.Item>
86+
<Flex.Item><ChevronUpInstUIIcon size="lg" rotate="270" /></Flex.Item>
87+
</Flex>
88+
</View>
89+
```
90+
91+
### Stroke vs Filled
92+
93+
Lucide icons are stroke-based. Several custom icons come in a filled variant, identified by the
94+
`Solid` suffix in their export name (e.g. `BellInstUIIcon` vs `BellSolidInstUIIcon`).
95+
96+
```js
97+
---
98+
type: example
99+
---
100+
<View as="div" margin="small">
101+
<Flex gap="small" alignItems="center">
102+
<Flex.Item><Text>Stroke</Text> <HeartInstUIIcon size="lg" color="errorColor" /></Flex.Item>
103+
<Flex.Item><Text>Filled</Text> <HeartSolidInstUIIcon size="lg" color="errorColor" /></Flex.Item>
104+
</Flex>
105+
</View>
106+
```
107+
108+
---
109+
110+
## Legacy Icons
111+
112+
> **Deprecated.** The legacy icon set (`IconHeartLine`, `IconSearchLine`, etc.) is kept for
113+
> backwards compatibility. Use the `InstUIIcon` components above for new code.
114+
> To migrate existing usage run:
115+
>
116+
> ```
117+
> npx @instructure/ui-codemods migrateToNewIcons <path>
118+
> ```
119+
10120
### Accessibility
11121
12122
By default, the icon's `role` is set to `presentation`. However, when the `title` prop is set, the role attribute is set to `img`. Include the `description` prop to further describe the icon.

packages/__docs__/buildScripts/build-docs.mts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ import path from 'path'
2727
import { getClientProps } from './utils/getClientProps.mjs'
2828
import { processFile } from './processFile.mjs'
2929
import fs from 'fs'
30-
import {
31-
canvas,
32-
canvasHighContrast,
33-
rebrandDark,
34-
rebrandLight
35-
} from '@instructure/ui-themes'
3630
import type {
3731
LibraryOptions,
3832
MainDocsData,
@@ -42,6 +36,12 @@ import { getFrontMatter } from './utils/getFrontMatter.mjs'
4236
import { createRequire } from 'module'
4337
import { fileURLToPath, pathToFileURL } from 'url'
4438
import { generateAIAccessibleMarkdowns } from './ai-accessible-documentation/generate-ai-accessible-markdowns.mjs'
39+
import {
40+
canvas,
41+
canvasHighContrast,
42+
rebrandDark,
43+
rebrandLight
44+
} from '@instructure/ui-themes'
4545
import { generateAIAccessibleLlmsFile } from './ai-accessible-documentation/generate-ai-accessible-llms-file.mjs'
4646

4747
const __filename = fileURLToPath(import.meta.url)
@@ -175,13 +175,6 @@ function buildDocs() {
175175
}
176176
)
177177

178-
// eslint-disable-next-line no-console
179-
console.log('Copying icons data...')
180-
fs.copyFileSync(
181-
projectRoot + '/packages/ui-icons/src/__build__/icons-data.json',
182-
buildDir + 'icons-data.json'
183-
)
184-
185178
// eslint-disable-next-line no-console
186179
console.log('Finished building documentation data')
187180
})

packages/__docs__/globals.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ const Components = require('./components')
4343
import { rebrandDark, rebrandLight } from '@instructure/ui-themes'
4444
import { debounce } from '@instructure/debounce'
4545

46-
import '@instructure/ui-icons/src/__build__/icon-font/Solid/InstructureIcons-Solid.css'
47-
import '@instructure/ui-icons/src/__build__/icon-font/Line/InstructureIcons-Line.css'
46+
import '@instructure/ui-icons/InstructureIcons-Solid.css'
47+
import '@instructure/ui-icons/InstructureIcons-Line.css'
4848

4949
import { DateTime } from '@instructure/ui-i18n'
5050
// @ts-ignore webpack import

0 commit comments

Comments
 (0)