Iconography

General

Jhana UI uses a unified, customizable and variable icon syntax, that all components understand. There are four ways to define an SVGIcon. Allowing for more and more features. Depending on how complex your requirements are. No matter which option, you will never specify the SVG tag. Instead, all of thsese will require you to define the SVGViewBox.

Icon defaults to currentColor: the SVG element's computed CSS text color, normally inherited from its parent. Omit color to follow text in buttons, selected cards and ordinary content, including changes on hover or focus. Set color, hover or active only when the icon needs its own colors. See the button examples.

@leon8ix/jhana-ui

export type SVGViewBox = { w: number; h: number; x?: number; y?: number };

Choosing Material Symbols

Reuse Jhana's existing icons first. For a new Material symbol, the author's preset is Material Symbols Rounded, weight 500, fill 0, grade 0 and optical size 24. Keep all five settings. Google's default weight 400 is thinner. Material's unfilled designs are still filled SVG paths, so preserve the downloaded path instead of adding a stroke or changing its thickness. Existing custom icons keep their own geometry.

Agents can retrieve the exact SVG directly from Google's official repository using this URL pattern. Replace both symbol placeholders with the Google symbol's snake_case name. Preserve its viewBox, and set the display size on Icon separately. The complete agent contract ships in docs/icons.md, including source retrieval and migration instructions.

Material Symbols Rounded / 500 / 0 / 0 / 24

https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web/{symbol}/materialsymbolsrounded/{symbol}_wght500_24px.svg

Reactivity

Icon normally follows its parent's text color, including interaction states. For independent hover, active and focus-visible colors, put icon-root on the owning button or input label. Active means the pressed state, not an active CSS class. Native disabled roots do not trigger these interaction colors.

/routes/icon-button/+page.svelte

<button type="button" class="btn dark icon-root">
    <Icon {icon} width="1.5em" height="1.5em" hover="var(--cLighter)" active="var(--cAcc)" />
    <span>My Button</span>
</button>

Filled Icons

You specify the SVG path ('d' Attribute). Which will be filled with the correct color, depending on the context. Since SVG paths can contain multiple subpaths, any mono-color filled icon can be defined like this.

/lib/icons/filled-icon.ts

const iconFilled: SVGIcon = {
    box: { w: 960, h: 960, y: -960 },
    fill: 'M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80Z',
};

Stroked Icons

You specify the SVG path ('d' Attribute). Which will be stroked with the correct color, depending on the context. You can optionally set the stroke-width. Since SVG paths can contain multiple subpaths, any mono-color single-stroke-width icon can be defined like this.

/lib/icons/stroked-icon.ts

const iconStroked: SVGIcon = {
    box: { w: 960, h: 960, y: -960 },
    stroke: 'M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80Z',
    strength: 3, // Optional, default: 3.4
};

Custom SVG Icons

In case you need to use different opacities for different icon elements, or you want to use SVG shapes like circle, or some other advanced stuff, you are free to use this syntax. You have access to many CSS variables, so you can use any custom logic, while still being variable and unaware of the context. You must not include the SVG tag itself here, only its contents.

Use var(--cIcon) for fills and strokes that should follow Icon's inherited color and explicit interaction colors. This also applies to snippet icons. Hardcoded colors inside custom SVG content remain unchanged.

/lib/icons/svg-icon.ts

/*
- var(--color)    Static color passed to Icon
- var(--hover)    Static hover color passed to Icon
- var(--active)   Static active color passed to Icon
- var(--cIcon)    Reactive color, changes on hover or active,
                  if those colors are set, this is what the icons 
                  internally use by default
- var(--isHover)  Numbool, 0 or 1 depending on hover (state), 
                  no matter if hov color is set
- var(--isActive) Numbool, 0 or 1 depending on pressed state
                  no matter if active color is set
*/
const iconColorSwatch: SVGIcon = {
    box: { w: 32, h: 32 },
    svg: `
        <style> .svg-cs-c-o { opacity: calc((1 - var(--isHover)) * 0.2 + 0.6) } </style>
        <circle cx="16" cy="16" r="12.5" fill="var(--cIcon)" />
        <circle cx="16" cy="16" r="6" fill="var(--cDark)" class="svg-cs-c-o" />
    `
};

Custom Snippet Icons

If you require the features from the 'SVG Icon' syntax, but want to integrate Svelte reactivity or you simply prefer having syntax highlighting, type checking and autocomplete, you might want to use Svelte snippets. In addition to having access to the same CSS variables, you can also use the snippet's props and do JS calculations or Svelte conditionals with them. The downside here, is that you will have to define the icon inside a Svelte component.

/lib/icons/SnippetIcon.svelte

<script lang="ts">
    import type { SVGIconProps } from '@leon8ix/jhana-ui';
</script>

{#snippet icon({ color, hover, active, trans }: SVGIconProps)}
    <!-- You can use those vars however you want -->
    <g fill="none" stroke="var(--cIcon)" stroke-width="4">
        <line class="line-lft" y1="-1" y2="-9" x1="-9" x2="+0" transform-origin="0 -9" />
        <line class="line-rgt" y2="-1" y1="-9" x2="+9" x1="+0" transform-origin="0 -9" />
        <line class="line-bot" y1="+8" y2="+8" x1="-10" x2="+10" />
    </g>
    <!-- You can style this in that Svelte component's style tag -->
{/snippet}

<Icon icon={{ snippet: icon, box: { x: -20, y: -20, w: 40, h: 40 } }} width="1.4em" />