Icon
One inline SVG, from path data or child shapes, with a required label that makes the accessibility choice explicit: null hides it, a string names it. 1em by default, five fixed steps, fill or stroke, currentColor.
cmp_icon_01 Preview
Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.
Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
Using the PageSugar MCP server, fetch component cmp_icon_01 version 1.0.0 with variant "default", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.Not connected yet? Set up the MCP server
Code
- Palette
- Inherited colour
- Version
- 1.0.0
- Digest
Full digest
sha256-0e26d41df6f16cf6ee221800fc4909ea3f42fd70b0b567fc015a5973894c7aea
<script lang="ts" module>
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
</script>
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
/**
* Accessible name, and it is required. null marks the icon decorative (hidden from
* assistive technology); a string makes it an image with that name.
*/
label: string | null;
/** Path data for a single-path icon: the d attribute. */
path?: string;
/** SVG child elements for a multi-shape icon. Used when path is absent. */
children?: Snippet;
/** The icon's drawing grid. */
viewBox?: string;
/** A fixed square step. Omitted, the icon is 1em and follows the text around it. */
size?: IconSize;
/** Draw with a round-capped stroke and no fill, for outline icon sets. */
strokeMode?: boolean;
/** Mirror in right-to-left text, for directional icons such as arrows and chevrons. */
mirror?: boolean;
/** Extra classes on the svg: colour, margins. */
class?: string;
}
let {
label,
path,
children,
viewBox = '0 0 24 24',
size,
strokeMode = false,
mirror = false,
class: className
}: Props = $props();
/*
* Fixed steps in pixels, written as width and height attributes rather than size classes so a
* size-* class passed in through class always wins.
*/
const PIXELS: Record<IconSize, number> = { xs: 12, sm: 16, md: 20, lg: 24, xl: 32 };
/*
* Stroke weight on screen at each step. 16 px icons sit beside 13 and 14 px labels at weight
* 500, so they draw heavier relative to their size than the 20 and 24 px steps do. At 12 px a
* heavier line closes the counters, so xs steps back to 1.25.
*/
const STROKE: Record<IconSize, number> = { xs: 1.25, sm: 1.75, md: 1.5, lg: 1.5, xl: 2 };
const step = $derived(size && size in PIXELS ? size : undefined);
const name = $derived(typeof label === 'string' && label.trim() ? label.trim() : null);
/*
* Stroke width is in viewBox units. A square viewport fits the grid by its longer side, so
* convert the on-screen weight through that side. A malformed viewBox falls back to 24.
*/
const grid = $derived.by(() => {
const [, , w, h] = viewBox
.trim()
.split(/[\s,]+/)
.map(Number);
const side = Math.max(w, h);
return Number.isFinite(side) && side > 0 ? side : 24;
});
const strokeWidth = $derived(
+(step ? (STROKE[step] * grid) / PIXELS[step] : grid / 12).toPrecision(4)
);
const dimension = $derived(step ? PIXELS[step] : '1em');
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
{viewBox}
width={dimension}
height={dimension}
fill={strokeMode ? 'none' : 'currentColor'}
stroke={strokeMode ? 'currentColor' : undefined}
stroke-width={strokeMode ? strokeWidth : undefined}
stroke-linecap={strokeMode ? 'round' : undefined}
stroke-linejoin={strokeMode ? 'round' : undefined}
role={name ? 'img' : undefined}
aria-label={name ?? undefined}
aria-hidden={name ? undefined : 'true'}
focusable="false"
class={[
'inline-block shrink-0',
// 1em icons drop by an eighth of the text so they centre on the capitals, not the baseline.
step ? 'align-middle' : 'align-[-0.125em]',
// :dir() reads the resolved direction, so an ltr island inside an rtl page is not flipped.
mirror && '[&:dir(rtl)]:-scale-x-100',
className
]}
>
{#if path}
<path d={path} />
{:else if children}
{@render children()}
{/if}
</svg>
Usage#
On this pagePass label (a name, or null for decorative) and either path data or child shapes. It renders one inline svg in currentColor at 1em or a fixed step. It does not ship an icon set, fetch SVG files, accept raw SVG strings, add tooltips or make the icon focusable.
- Suggested location
src/lib/components/icon-01- Required props
label
Limitations
- No icons are included. Paste path data from your own drawings or a set whose licence allows it, or install an icon package and pass its shapes as children.
- Path data is a single d attribute. Icons that need several elements, a fill-rule or mixed fill and stroke go in children instead.
- A filled icon with holes must draw its counters in the opposite direction, or pass fill-rule="evenodd" on a child path, because the svg uses the default nonzero rule.
- The fixed steps set an
on-screenstroke weight and convert it through theviewBox's longer side, so sets drawn on 16 or 20 unit grids keep the same weight. Their own optical sizes are not chosen for you. - Mirroring flips the drawing horizontally under dir="rtl"; icons whose meaning does not depend on direction (a check, a clock) should not pass mirror.
Example
<script lang="ts">
import Icon from '$lib/components/icon-01/Icon.svelte';
const check = 'M5 12.5l4.5 4.5L19 7.5';
</script>
<p>
<Icon label={null} path={check} strokeMode /> Guests can comment without a seat
</p>
<button type="button" class="inline-flex items-center gap-2">
<Icon label={null} size="sm" strokeMode>
<path d="M10 11.5a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z" />
<path d="M3.5 20a6.5 6.5 0 0 1 13 0M19 8v6M16 11h6" />
</Icon>
Invite a guest
</button>
<Icon label="Overdue" size="sm" path="M12 3 22 20H2Z" class="text-amber-700" />Icon#
label is required, and it is the whole point: every icon on the page has made its
accessibility decision in the markup. null hides the icon from assistive technology; a string
names it.
<script lang="ts">
import Icon from '$lib/components/icon-01/Icon.svelte';
const clock = 'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM12 7.5V12l3 2';
const warning = 'M12 3 22 20H2Z';
</script>
<!-- Decorative: the text beside it says the same thing. -->
<p class="text-sm text-zinc-600">
<Icon label={null} path={clock} strokeMode /> Due Thursday 9 October
</p>
<!-- Meaningful: nothing else says "Overdue". -->
<Icon label="Overdue" size="sm" path={warning} class="text-amber-700" />Which one to choose#
| The icon… | label | Renders |
|---|---|---|
| sits beside text that says the same thing | null |
aria-hidden="true", focusable="false" |
| is the only child of a button or link | null |
the same; name the button instead |
| carries meaning nothing else on the page does | "Overdue" |
role="img", aria-label="Overdue" |
An icon-only button names the button, not the icon:
<button type="button" aria-label="Close" class="grid size-11 place-items-center rounded-lg">
<Icon label={null} size="md" path="M6 6l12 12M18 6 6 18" strokeMode />
</button>Sizes#
| size | Pixels | Stroke weight | Use |
|---|---|---|---|
| (omitted) | 1em | 1/12 of grid | inside text: paragraphs, headings |
xs |
12 | 1.25 px | captions, badges |
sm |
16 | 1.75 px | buttons, dense rows, beside 13–14 px |
md |
20 | 1.5 px | navigation, lists |
lg |
24 | 1.5 px | illustrations, empty states |
xl |
32 | 2 px | large illustrations |
Stroke weights are on-screen pixels, converted through the longer side of the viewBox, so a
set drawn on a 20 or 16 unit grid gets the same weight when it passes its own viewBox. A 1em icon drops by
0.125em so it centres on the capitals of the text around it rather than sitting on the baseline.
Beside text that wraps#
Centre the icon on the first line, not on the paragraph. Put it in a box exactly as tall as one
line of the text beside it (h-6 for text-base/6):
<p class="flex gap-3 text-base/6">
<span class="flex h-6 items-center text-lg">
<Icon label={null} path="M5 12.5l4.5 4.5L19 7.5" strokeMode />
</span>
<span>Guests can comment on milestones and follow the timeline without taking a paid seat.</span>
</p>Several shapes#
Pass children when an icon needs more than one element, a fill-rule, or a filled part inside
an outline icon:
<Icon label={null} strokeMode>
<path d="M6 5h12a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2ZM4 10h16" />
<path d="M8 13h3v3H8Z" fill="currentColor" stroke="none" />
</Icon>Children are drawing only. A <title>, link or tabindex inside them would add a tooltip or a
focus stop the icon otherwise never has.
Right to left#
Arrows and chevrons pass mirror and flip when their resolved direction is right to left (a
dir="ltr" island inside an Arabic page stays unflipped). Icons whose meaning has no
direction (a check, a clock, a calendar) leave it off.
Alignment#
A 1em icon sits at vertical-align: -0.125em, a fixed step at middle. Override with an
important utility, class="align-baseline!": two alignment utilities on the same element are
resolved by Tailwind's order, not by which one you wrote last.
Props and content inputs#
On this page| Name | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | null | Yes | None | Accessible name, always passed. null marks the icon decorative (aria-hidden). A string makes it role="img" with that aria-label. A blank string is treated as null. |
path | string | No | None | Path data (the d attribute) for a single-path icon. When set, children are ignored. |
children | Snippet | No | None | SVG child elements (paths, circles, groups) for a multi-shape icon. Used when path is absent. |
viewBox | string | No | '0 0 24 24' | The icon's drawing grid, four numbers. Its longer side converts each step's stroke weight into grid units. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | No | None | Fixed square step: 12, 16, 20, 24 or 32 px. Omitted, the icon is 1em and scales with the surrounding text. |
strokeMode | boolean | No | false | Draw with a round-capped, round-joined stroke in currentColor and no fill, for outline icon sets. |
mirror | boolean | No | false | Flip the icon horizontally in right-to-left text. For arrows, chevrons and other directional icons. |
class | string | No | None | Extra classes on the svg: a text colour, margins, or a size-* class that replaces the step. |
Customization#
On this pageThe icon has no colour of its own: it is drawn in currentColor, so set the text colour on it or any ancestor. Size through size or a size-* class; stroke weights live in the STROKE map.
- Colour: pass class="
text-amber-700" or set color on a parent. On a dark band or a cream page the icon follows the text with no change. - Size: omit size inside text so the icon is 1em and grows with a heading. Use sm (16 px) beside 13 to 14 px labels in buttons and dense rows, md (20 px) in navigation and lists, lg (24 px) only as an illustration.
- Any size: pass class="
size-10" (or anysize-*utility). The steps are width and height attributes, so a class always wins. Stroke weight then scales with the drawing. - Stroke weight: the STROKE map at the top of the script holds each step's weight in pixels (1.25 at 12 px, 1.75 at 16, 1.5 at 20 and 24, 2 at 32). Change it there; 1em icons draw 2 units on a 24-unit grid.
- Beside wrapping text: put the icon in a box one line tall (display: flex; align-items: center; height equal to the text's line height) so it stays level with the first line.
usage.mdhas the pattern. - Icon sets: paste a single d attribute into path, or pass the set's child elements as children. A set drawn on a 20 or 16 unit grid passes its own
viewBox. - Alignment: a 1em icon sits at vertical-align -0.125em and a fixed step at middle. To change it, pass an important utility such as class="align-baseline!", because two alignment utilities on one element resolve by Tailwind's order, not by which came last.
Accessibility#
On this page- label is a required prop typed string | null, so leaving out the accessibility decision is a TypeScript error rather than a silent default.
- Decorative (label=
{null}):aria-hidden="true" and focusable="false", no role. Use it whenever adjacent text already says the same thing. - Meaningful (label="Overdue"): role="img" and
aria-labelon the svg. The component renders no <title>, so there is no hover tooltip and no duplicated name. - An icon alone inside a button or link is decorative; give the button its name (
aria-labelor visually hidden text), not the icon.usage.mdshows both. - The icon is never focusable and carries no tooltip. A meaningful icon whose name should be visible on hover needs its own button or a visible label next to it.
- Drawn in
currentColor, so forced-colours mode repaints it with the system text colour, and contrast follows the text around it. A meaningful icon given its own colour needs 3:1 against the background. - Directional icons can pass mirror so they point the right way in right-to-left text.
- Children are rendered as given. Pass drawing elements only (paths, circles, groups): a <title>, a link or a tabindex inside them would bring back a tooltip or a focus stop that the svg itself never has.
Known limitations
- A blank label is treated as decorative rather than reported; check that dynamic labels are never empty when the icon carries meaning.
- Colour alone is not a status: a red or amber icon still needs its label, or text beside it.
Release details#
On this page- Integration
- Presentational
- Works without client-side JavaScript
- Server-side rendering supported
- Dependencies
- No additional runtime packages beyond Svelte and Tailwind CSS
- License
MIT. Default license approval is pending; see the license status before adopting the source.
- Version history
- 1.0.0 (Published) Current release · 1 October 2026
Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.