# Icon Icons provide additional context to elements on the page. Section: components Docs: https://nebula.kaluza.com/components/icon --- ## When to use - As a visual shorthand to help users quickly identify common actions or concepts, for instance a magnifying glass for search - To provide visual anchors in navigation menus or headers, helping users scan information faster - To reinforce meaning alongside text labels in system states or feedback, such as success, error, or warning messages ## When not to use - **As pure decoration** - icons that do not add functional value or clarify meaning create visual noise and distract the user - **For abstract or complex concepts without an obvious visual metaphor** - use text instead - **In place of critical labels** - unless the icon is a widely-understood standard, do not use an icon without a text label for primary actions - **When a standard icon contradicts its common meaning** - this can be confusing, for instance using a trash can icon to mean anything other than delete - **For every concept** - using an icon for one thing does not mean you need to use an icon for everything. Overuse reduces the visual impact of icons across the page ## Anatomy 1. **Icon** ## Basic usage ```tsx <> ``` ## Variants and features ### Pairing icons with text In most cases, icons should be paired with a text label. Do not assume a standalone icon is universally understood - if a label is absent from an icon that is not a widely-accepted standard, users will have to work out what it means. ```tsx

Example

``` ### Standalone icons If an icon must be used without a visible label, for instance in a condensed toolbar: - Wrap the icon in a tooltip trigger so that a text label is revealed on hover or focus - Use [VisuallyHidden](/components/visually-hidden) or an `aria-label` to ensure screen reader users can identify the action ```tsx ``` ### Sizing and scaling When icons need to be sized relative to text, use `em` units rather than fixed pixels. This ensures icons scale proportionally with their parent text element and maintains the visual relationship between icon and typography across different screen sizes and zoom levels. ```tsx Example ``` ### Custom colours and sizes Icons inherit the colour and size of their parent by default. Both can be set manually when needed. `size` takes a number, treated as pixels, or any CSS length. `color` sets the SVG `color` attribute, which icons drawn with `currentColor` pick up. ```tsx ``` ### Custom icons You can override existing icons or add new icons by wrapping your app in `IconsProvider`. Icons passed to it are merged over the built-in set, so a name that already exists is replaced and a new name is added. Custom icons should be a last resort - over time they can create inconsistency even within the same icon set, where optical size and stroke width may vary. If you need an icon not available in Nebula: 1. Raise the need with the design system team 2. Check the KAE application for suitable custom icons that have already been added 3. If a new icon is still needed, source an SVG from Figma or Material UI All custom icons should be on a square canvas and use `currentColor` as the default fill colour. Spread props on the `svg` element. These examples are not live, because `IconsProvider` wraps an application at its root rather than a single component. ```tsx import { IconsProvider } from '@kaluza-platform/nebula'; const Flame = (props: JSX.IntrinsicElements['svg']) => ( ); {/* Anywhere inside, now renders the custom icon */} ; ``` You can also use an SVG file directly, as long as it is imported as a React component: ```tsx import { IconsProvider } from '@kaluza-platform/nebula'; import Flame from './flame.svg'; ; ``` ## Rules and guidance ### Usage - **Icon** MUST NOT be used as pure decoration with no functional meaning. Rationale: Decorative icons create visual noise and distract the user without adding value. [id: icon.no-decoration; testability: manual] - **Icon** MUST NOT be used as the sole means of communicating meaning. Rationale: Icons must always be accompanied by a text label or programmatic label. Relying on an icon alone excludes users who cannot perceive or interpret it. [id: icon.no-sole-means-of-communication; testability: manual] - **Icon** SHOULD be paired with a visible text label. Rationale: Icons are visual aids, not a replacement for language. Do not assume a standalone icon is universally understood. [id: icon.pair-with-label; testability: manual] - **Icon** SHOULD be consistent in size within the same context. Rationale: Mixing icon sizes in a single navigation, toolbar, or list creates visual noise. [id: icon.consistent-size-in-context; testability: manual] - **Icon** SHOULD use em units for size when the icon needs to scale with surrounding text. Rationale: em units ensure icons scale proportionally with their parent text element, maintaining visual consistency across different screen sizes and zoom levels. [id: icon.em-units-for-scaling; testability: auto] - **Custom icon** MUST use currentColor as the fill colour. Rationale: currentColor ensures the icon inherits colour from its parent, making it compatible with theming and colour overrides. [id: custom-icon.currentcolor-fill; testability: auto] - **Icon** SHOULD NOT be used for abstract concepts that have no obvious visual metaphor. Rationale: Use text instead. An icon without a clear visual metaphor forces users to guess its meaning. [id: icon.no-abstract-concepts; testability: manual] - **Icon** SHOULD NOT be used as a substitute for a primary action label. Exception: the icon is a widely understood standard such as a pencil for edit or a trash can for delete. [id: icon.no-substitute-for-primary-label; testability: manual] - **Icon** SHOULD NOT be used in a way that contradicts the icon's common meaning. Rationale: Using a familiar icon to mean something unexpected. For instance a trash can for anything other than delete. Creates confusion and slows users down. [id: icon.no-contradicting-meaning; testability: manual] - **Icon** SHOULD NOT be used on every concept simply because an icon was used for one concept. Rationale: Overuse reduces the visual impact of icons across the page and dilutes their ability to draw attention. [id: icon.no-overuse; testability: manual] ### Interaction - **Icon** WILL set aria-hidden="true" and focusable="false" on the SVG element automatically. The icon is never announced by screen readers and never receives focus. Rationale: All meaning must be provided through a visible label or a programmatic label on the parent element. [id: icon.will-aria-hidden; testability: auto] - **Icon** WILL render nothing when the name prop does not match a known icon. No error is thrown. [id: icon.will-renders-nothing-unknown; testability: auto] - **Icon** MUST be wrapped in a TooltipTrigger when used without a visible label, so a label is available on hover and focus. [id: icon.tooltip-when-standalone; testability: manual] - **Icon** MUST have a descriptive aria-label, title, or VisuallyHidden label when used without a visible label. Rationale: Without a programmatic label, screen reader users cannot identify the icon's meaning or purpose. [id: icon.accessible-label-when-standalone; testability: auto] ## Accessibility ### In brief Icon is an `svg` that is hidden from screen readers. It is never announced and never takes focus, so any meaning it carries has to be written as text. ### What Nebula provides - Every Icon renders with `aria-hidden="true"`, so a screen reader skips it. - The size defaults to `1em`, so the icon scales with the text around it. ### Accessible design #### Pair every icon that carries meaning with a text label The icon is hidden from screen readers, so the label is the only thing they announce. An icon beside text is decoration; an icon without text is invisible to a screen reader user. WCAG: [1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html) #### If you set a custom colour on an icon that carries meaning, check it reaches 3:1 against its background The `color` prop sets the colour of the whole icon, and an icon that is hard to see loses its meaning for a low-vision user. WCAG: [1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) ### Accessible development #### If an icon stands alone, put its name in text next to it with `VisuallyHidden` Where there is no room for a visible label, `VisuallyHidden` gives the screen reader the text the hidden icon cannot. WCAG: [1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html) #### Don't name an icon with `aria-label` or `title` Icon sets `aria-hidden="true"`, and a hidden element's name is not announced. Name the control around the icon instead. WCAG: [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) #### Spread the props onto the `svg` of every custom icon Icon passes `aria-hidden` to the icon component through its props, so a custom icon that drops them is no longer hidden from screen readers. WCAG: [1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html) ### Accessibility testing status Reviewed on 9 October 2026 against v17.0.1. | Test | Status | Tested with | | --- | --- | --- | | Default state | Tested | axe-core via the Storybook test runner (CI) | | Advanced states | Partially tested | axe-core via the Storybook test runner (CI) | | Keyboard navigation | Manually tested | keyboard (reported by the owner) | | Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) | What each test covers: - Default state: The component as it first appears, before anything is clicked or changed. - Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering. - Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring. - Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state. What these statuses mean: - Tested: Every check in this row passed in an automated run that repeats on every pull request. - Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change. - Partially tested: Some checks in this row were run once and passed; the rest are still to do. - Issue found: A check in this row failed. The ticket is shown beside the status. - Not tested: No check in this row has been run yet. - Not applicable: This row does not apply to the component, for the reason given beside it. The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used. ## Properties ### Icon Also accepts all props from `SVGAttributes`. - `name` (type `IconName | (string & {})`, required). Name of the icon to render, looked up in the built-in icons plus any added through `IconsProvider`. Renders nothing if no icon has that name. - `size` (type `string | number`, optional, default `'1em'`). Width and height of the icon. A number is treated as pixels and a string as any CSS length. - `color` (type `string`, optional). Sets the SVG `color` attribute, which icons drawn with `currentColor` pick up as their colour.