Skip to content

Adding custom/brand icons#2410

Open
joyenjoyer wants to merge 7 commits intov12from
INSTUI-4892-tidy-up-the-icons-gallery
Open

Adding custom/brand icons#2410
joyenjoyer wants to merge 7 commits intov12from
INSTUI-4892-tidy-up-the-icons-gallery

Conversation

@joyenjoyer
Copy link
Contributor

@joyenjoyer joyenjoyer commented Feb 18, 2026

Summary

  • Adds the custom icon set — 85 Instructure-specific SVG icons (product/brand logos, solid variants, stroke based icons) in svg/Custom/, each exported as <Name>InstUIIcon alongside the existing Lucide icons
  • Adds wrapCustomIcon — a runtime wrapper that owns the <svg>, adds a similar API than wrapLucideIcon
  • Adds generateCustomIndex build script — parses SVGs at build time into [tagName, attrs][] tuples (Lucide's IconNode format, it is needed for color handling)
  • Adds migrateToNewIcons codemod in ui-codemods for migrating legacy icons
  • Adds visual regression page

Test plan

  • pnpm run test:vitest ui-codemods -> codemod fixtures pass
  • pnpm run test:vitest ui-icons -> IconPropsProvider tests pass
  • Run visual regression page and check icons for visual glitches, artifacts
  • Try a few new icons on docs page

How to check the regression page manually

cd regression-test
npm install        # first time only
npm run dev        # starts localhost:3000
Open http://localhost:3000/custom-icons or npm run cypress-chrome

@joyenjoyer joyenjoyer self-assigned this Feb 18, 2026
@joyenjoyer joyenjoyer changed the base branch from master to v12 February 18, 2026 10:19
@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch 2 times, most recently from 84fae89 to feb4960 Compare February 18, 2026 10:36
@github-actions
Copy link

github-actions bot commented Feb 18, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2410/

Built to branch gh-pages at 2026-03-11 14:48 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copy link
Contributor

@balzss balzss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skimmed through the PR and mostly left questions, not change requests since i'm not familiar with the first iteration at all

i'm not really sure about the name "stroke" for the new icon variants, since it's a small technical detail about them, not really important for neither us and especially not for the end user. i think a more semantic differentiation could be better but that's just my 2 cents

as someone who is not familiar with how the icons are generated, from where and why, the structure feels very complicated to me and i'm not really looking forward to maintain this in the future. i think it would really benefit us in the long run to come up with something that's easier to wrap our heads around

also with the old icons we also provided the raw svg values along with the react components (in the docs page). are those not needed anymore? I'm not against it, just asking if that is intentional

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. We have a codemod that will help you migrate your code to the new icon set (see below).

### Lucide Icons Package
### Stroke-Based Icons Package
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this name coming from? did design come up with it? it's a bit confusing for me what it means (and how this is different from other icons. others are store based too, no?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an issue anymore, I removed the 'filled', 'stroke' names.

const __dirname = path.dirname(__filename)
const require = createRequire(import.meta.url)

// Use CommonJS require to load ui-themes to avoid ES module resolution issues
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a new issue? or was it not working before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was caused by the local Node 22.6 issue. I fixed it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is in a separate dir and not under Icons?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, old icons will be shown on a separate page than new icons.

* Stroke width is automatically derived from size for consistent visual weight.
* Numeric and custom CSS values are not supported.
*/
export function wrapStrokeIcon(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't wrapLucideIcon actually be an appropriate name here? since this is actually for wrapping just the lucide library icons

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, I restored it during refactoring.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a build file? seems like it has the same svg data as svg/Custom/.... or what's the difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is generated by the generateCustomIndex script. It contains all the custom icons that developers can import.

// Re-export all generated SVG icons and Lucide icons
// Main entry point - exports all icons for backwards compatibility
export * from './lucide'
export * from './custom'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this (in theory) cause a naming conflict if a custom icon has a same name as a lucide icon? is that even a problem?

Copy link
Contributor Author

@joyenjoyer joyenjoyer Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming conflicts are handled in generateLucideIndex (lines 69-88). Custom icons shadow Lucide icons of the same name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the canvas logo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this icon from the old icon build system. Yeah it looks like the canvas logo and canvas.svg looks pretty similar. I reach out to designers whether they want to keep this under this name.

const MyIcon = () => {
return (
<${iconName} size={'2xl'} color='successColor'/>
<${icon.name} size={'2xl'} color='successColor' />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: double quotes and string primitive would be nicer here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


function getUsageInfo(iconName: string) {
return `import { ${iconName} } from '@instructure/ui-icons'
// Get all stroke icons
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section is a bit overcomplicated. you are getting the keys of each icon type to convert an object list to a string array and then you are converting it back to an object while accessing each item in the original list again. this is resource heavy and with lots of icons could cause perf issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored IconsGallery entirely, this is no longer an issue.

...strokeIconNames.map((name) => ({
name,
component: (StrokeIcons as any)[name],
source: 'stroke' as const,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why as const?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like it either but it is needed for the type IconInfo otherwise ts throws an error.

Copy link
Contributor

@balzss balzss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it seems like the custom icons with ai color have a border around them:

<div>
  <IconButton><SparklesInstUIIcon size="2xl" color="ai"/></IconButton>
  <IconButton><Sparkles2InstUIIcon size="2xl" color="ai" /></IconButton>
</div>
Image

is this intentional?


also the new icons docs page have a border around which doesn't seem necessary and the focus ring is cut off from the input field:

Image

the custom filled ai button is black in the avatar example, this also seems like a bug:

Image

@matyasf
Copy link
Collaborator

matyasf commented Feb 20, 2026

Also color="ai" seems to mess up lines, e.g.
image

@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch 3 times, most recently from 290e31a to f72f6cb Compare March 2, 2026 09:18
@joyenjoyer joyenjoyer requested review from balzss and matyasf March 2, 2026 09:31
@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch from b41e5e2 to f3dfb7f Compare March 2, 2026 14:16
Comment on lines +124 to +137
// write legacy-icons-data.json for the docs gallery
const legacyIconsData = generateLegacyIconsData()
const legacyOutputDir = path.join(process.cwd(), 'lib/legacy/')
fs.mkdirSync(legacyOutputDir, { recursive: true })
const legacyOutputPath = path.join(
legacyOutputDir,
'legacy-icons-data.json'
)
fs.writeFileSync(
`${config.destination}icons-data.json`,
JSON.stringify(iconsData)
legacyOutputPath,
JSON.stringify(legacyIconsData, null, 2),
'utf8'
)
info(`Generated ${legacyOutputPath} (${legacyIconsData.length} icons)`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I dont understand this part. This json is only generated for the docs page? Why not use the icons itself in the docs app, that way we are also checking that they work OK. Why not just take them from the buil;d __build__ folder?

Copy link
Contributor Author

@joyenjoyer joyenjoyer Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this already but legacy icons are shown and exposed as svg too. This json is needed for the docs file. It can be removed when we remove all legacy icons in a future release.

@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch from f3dfb7f to 5a671d2 Compare March 2, 2026 15:26
Comment on lines +10 to +13
Icons from `@instructure/ui-icons` are available as `<Name>InstUIIcon` components — browse them
in the [Icons gallery](#icons). Legacy class-component icons (`IconHeartLine`, `IconSearchLine`,
etc.) are still available for backwards compatibility but are deprecated — see
[Legacy Icons](#legacy-icons) below.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also write about compatibility:

  • Components with the new theme ONLY accept new icons
  • what happens if you use a component with the old theme and a new icon?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a sentence about the first part. The second part will be answered once the docs multi-version support is available and I’ve tested it thoroughly with all the components. The new icons worked with some old components on my local branch.

---

## Adding and Modifying Icons
## Adding Custom Icons
Copy link
Collaborator

@matyasf matyasf Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also mention in this doc how to update and add Lucide icons (IMO this is a much better place, than the package README files, those are read very rarely)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@balzss balzss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the code itself and left comments but i wasn't able to test it because the updates from last time weren't deployed to gh pages and i get errors when i try to run the branch locally, so i will finish the review when those are working again

name,
component: component as React.ComponentType<any>,
source: 'lucide' as const,
importPath: '@instructure/ui-icons'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if source and importPath are static data, why add it every time when the page is rendered instead of including these with the orignal data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is safe to be there, it is only called during module load not during render.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does only called during module load mean? i've just tested and this is runs on every render. also as I said earlier, I think static data should not be added runtime if it can just be included with the original data

canvasHighContrast,
rebrandDark,
rebrandLight
} from '@instructure/ui-themes'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why were these removed?

Copy link
Contributor Author

@joyenjoyer joyenjoyer Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put them back, I mistakenly removed them because of my local Node 22.6 issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see. i would add them back to the same place to not update git history unnecessarily

"require": "./lib/index.js",
"default": "./es/index.js"
},
"./InstructureIcons-Solid.css": "./src/__build__/icon-font/Solid/InstructureIcons-Solid.css",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just don't really understand why did it work with the previous icon system and why did it break with the current that made this line required?

@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch 3 times, most recently from f2a5a27 to de5e92b Compare March 6, 2026 11:25
@joyenjoyer
Copy link
Contributor Author

joyenjoyer commented Mar 6, 2026

also with the old icons we also provided the raw svg values along with the react components (in the docs page). are those not needed anymore? I'm not against it, just asking if that is intentional

@balzss We don't expose svgs or icon fonts of new icons in the new system.

@joyenjoyer joyenjoyer requested review from balzss and matyasf March 6, 2026 12:14
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env -S node --experimental-strip-types
Copy link
Contributor Author

@joyenjoyer joyenjoyer Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@balzss comment on this: We discussed with Matyi that it would be good to convert all the JS files in ui-scripts to TS soon, so as a good first step I added the --experimental-strip-types Node feature, and the new generator scripts are written in TS.

Copy link
Contributor

@balzss balzss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first half of my review. i'll continue on monday

"fantasticon": "^3.0.0",
"svgo": "^3.3.2"
"svgo": "^3.3.2",
"svgson": "^5.3.1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this new lib needed? what process our old pipeline is not capable of?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning to add this lib:

  • I did not want to write a custom svg parser
  • svgo has an svg parser but it has no public API, I could import it from svgo/lib/parser.js but it would be an antipattern, the lib's internals might change later
  • I could use the optimize function call from svgo but it does a lot of other things than just parsing svg (invokePlugins(), stringifySvg(), etc.) and it has no option to make attributes camelCase; it is a very ugly solution to call the optimize function with a plugin that filters attributes, extracts IconNodes and do camelCase conversion
  • I chose svgson because the Lucide library uses it, I trust it, it is a known support library. I followed what they are doing: first they parse svgs and convert them into IconNodes (we need to convert custom svg into IconNodes to be able to change colors). It can also convert attribute names to camelCase, which React needs
  • as soon as we throw out the old icon system, svgo can be thrown out too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joyenjoyer

you don't have to write a custom svg parser, we already have and use one for the legacy icon pipeline at packages/ui-scripts/lib/icons/svg2jsx.js. I would prefer using that to adding a new lib

canvasHighContrast,
rebrandDark,
rebrandLight
} from '@instructure/ui-themes'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see. i would add them back to the same place to not update git history unnecessarily

@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch from 03d3694 to 6810da0 Compare March 10, 2026 15:54
@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch from 6810da0 to ef85acd Compare March 10, 2026 16:15
@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch from 31e63df to 75cdd1d Compare March 11, 2026 14:43
Copy link
Collaborator

@matyasf matyasf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now, just 2 small remarks

<path d="..." fill="#2D3B45" />
</svg>
```
Custom icons live in `packages/ui-icons/svg/Custom/` and are consumed directly by the build script (`scripts/generateCustomIndex.ts`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called now ui-scripts/lib/generate-custom-index.ts

Comment on lines +45 to +49
const ICON_IMPORT_PATHS = [
'@instructure/ui-icons',
'@instructure/ui-icons/es/svg'
]
const NEW_ICON_IMPORT_PATH = '@instructure/ui-icons'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a later PR (can you please make a ticket?) this codemod should also handle if the icon is imported from the ui package

@balzss balzss self-requested a review March 11, 2026 16:33
@balzss
Copy link
Contributor

balzss commented Mar 11, 2026

@joyenjoyer my commits are logically separated so i think those should remain as is, but your first 3 commit should be squashed and a more descriptive commit message added. can you cherry pick those to have a nicer git history?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants