Skip to content

Comments

Adding custom/brand icons#2410

Open
joyenjoyer wants to merge 1 commit intov12from
INSTUI-4892-tidy-up-the-icons-gallery
Open

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

Conversation

@joyenjoyer
Copy link
Contributor

No description provided.

@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 from 69c5e5e to 84fae89 Compare February 18, 2026 10:27
@joyenjoyer joyenjoyer force-pushed the INSTUI-4892-tidy-up-the-icons-gallery branch from 84fae89 to feb4960 Compare February 18, 2026 10:36
@github-actions
Copy link

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-02-18 10:40 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

i also don't see the colored ai icon in the new custom icons, was that intentional too? don't we have components depending on that?

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?)

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

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?

* 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

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?

// 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

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?

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


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.

...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?

Comment on lines +42 to +44
migrateToNewIcons,
// Backwards compatibility alias (deprecated)
migrateToNewIcons as migrateToLucideIcons
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is here this exported twice, once under an alias?
What do you mean by "Backwards compatibility alias (deprecated)"?

@@ -1,38 +0,0 @@
## ui-codemods
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why was this deleted?

Comment on lines 42 to 53
interface MappingData {
version: string
lastUpdated: string
mappings: IconMapping[]
unmapped?: Array<{
instUI: {
line: string
solid: string
}
reason: string
}>
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This type has nothing to do with the actual data its applied to?

Comment on lines +136 to +138
console.error(
`Mapped ${mappingData.mappings.length} InstUI icons to Lucide`
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This never happens. Also please dont use console.error to indicate success

const svgFiles = fs
.readdirSync(svgDir)
.filter((f) => f.endsWith('.svg'))
.sort()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does it need to be sorted?

Comment on lines +176 to +189
/**
* Convert kebab-case filename to PascalCase icon name
* Examples:
* ai-info.svg -> AiInfo
* post-sis.svg -> PostSis
* calculator-desmos.svg -> CalculatorDesmos
*/
function fileNameToIconName(fileName: string): string {
return fileName
.replace('.svg', '')
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('')
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not just call them by the converted name like AiInfo?

attributes[camelCaseName] = 'currentColor'
} else if (camelCaseName === 'stroke' && value !== 'none') {
hasStroke = true
attributes[camelCaseName] = 'currentColor'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Fixes like this should be in the .svg files, not patched every time when builing?

Comment on lines +135 to +136
} else if (camelCaseName === 'strokeWidth') {
attributes[camelCaseName] = '1.5'
Copy link
Collaborator

@matyasf matyasf Feb 19, 2026

Choose a reason for hiding this comment

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

I think this code never ececutes, why is it here? also why 1.5?

Comment on lines +143 to +154
if (
tagName === 'path' ||
tagName === 'polygon' ||
tagName === 'circle' ||
tagName === 'rect' ||
tagName === 'ellipse'
) {
if (!('fill' in attributes) && !('stroke' in attributes)) {
attributes.fill = 'currentColor'
hasFill = true
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, we should not process these svgs on every build, but once commit in the right format

}

const iconType = shouldUseFilled ? '(filled)' : '(stroke)'
console.error(` ✓ ${fileName} → ${iconName}InstUIIcon ${iconType}`)
Copy link
Collaborator

Choose a reason for hiding this comment

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

please do not use console.error for success messages

const iconType = shouldUseFilled ? '(filled)' : '(stroke)'
console.error(` ✓ ${fileName} → ${iconName}InstUIIcon ${iconType}`)
} catch (error) {
console.error(` ✗ Error: ${fileName}:`, error)
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 throw an error in this case, the build should fail

export function wrapFilledIcon(
iconNode: IconNode,
iconName: string,
viewBox: string = '0 0 24 24'
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is there a default value for this??


// Extract width and height from viewBox for gradient sizing
const viewBoxParts = viewBox.split(' ')
const gradientSize = parseFloat(viewBoxParts[2]) || 24
Copy link
Collaborator

Choose a reason for hiding this comment

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

why the default value? When is it applied?

@@ -0,0 +1,145 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is tis the same file as custom/wrapFilledIcon? Why do you need this code duplication?

Comment on lines +51 to +54
export function createIconWrapper(
config: IconWrapperConfig
): React.ComponentType<StrokeIconWrapperProps> {
const { displayName, renderIcon, renderGradientIcon } = config
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 so complicated now

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

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