# icons

> Drop `.svg` files into `model/icons/` and render them with `Icon(Icons.Search)`. The name is checked at compile time, so a typo is an error rather than a blank square. An icon inherits the surrounding text size and color, so one icon set follows your theme through light and dark; pass `size:` when a glyph needs its own size. `Icons` is also a type, so an icon can be a parameter, a return value or a stored field.

<!-- id: ui-icons · area: ui · stability: stable · html: https://osysharp.com/reference/ui/icons/ -->

## Summary        {#summary}
Icons are **files in your app**, not data. Put an `.svg` in `model/icons/` and it becomes part of your app's
vocabulary:

```text
model/
  icons/
    search.svg
    close.svg
    menu.svg
```

```osy title="an icon beside a field" test app=file-manager
[Composable]
component SearchBar() {
  string query = "";
  render {
    Row(gap: 2) {
      Icon(Icons.Search);
      Input(value: query, placeholder: "Search");
    }
  }
}
```

This compiles against the [drop-ship-order](#see-also) sample, whose `icons/` folder really does contain
`search.svg` — which is the whole point: the name is checked against the files the app ships, so this example is
wrong the moment that file is renamed.

Adding an icon is dropping a file in. There is nothing to register and nothing to import.

## Signature      {#signature}
```osy syntax
Icon(Icons.Search) · Icon(Icons.Search, size: 18) — an icon, named by a compile-checked identifier
```

## Description    {#description}

### The name is checked   {#names}
`Icon(Icons.Search)` names the icon by a **bare identifier**, and it is checked against the icons your app actually
declares. A typo is a compile error with a suggestion:

```text
unknown icon 'serch' (declared icons: close, menu, search). Did you mean 'search'?
```

Because the name is an identifier, an icon's **file name must be one too** — `chevron_right.svg`, not
`chevron-right.svg`. A kebab-case file is rejected with the rename to make.

The name is never an expression. A local variable called `search` does **not** change what `Icon(Icons.Search)` means —
the icon vocabulary always wins. This is deliberate: an icon chosen at runtime could not be checked, so there is no
way to write one by accident.

**Choosing an icon from data** — a category's icon, say — is a *content* concern, not chrome. Use `Image(src)`, or
branch explicitly:

```osy syntax
if (item.Kind == Kind.Folder) { Icon(Icons.Folder); } else { Icon(Icons.File); }
```

### How big is an icon, and what color? — it inherits   {#styling}
An icon is an **em square that inherits the current text color**. Put one beside a label and it matches at any font
size, in light mode and dark, with nothing to configure:

```osy title="an em square that already matches the label beside it" syntax
Row(gap: 2) { Icon(Icons.Search); Text("Search"); }
```

**Color** defaults to the surrounding text color — any color you draw into the `.svg` is replaced by the current
text color when your app is built, so an icon copied from any icon set immediately follows your theme. Override one
glyph's color with `color:` — a theme token or a color string:

```osy title="overriding one glyph's color on the call, never a tint wrapper" syntax
Icon(Icons.Chev, color: Colors.TextMuted)        // a muted separator in primary-colored text
Icon(Icons.Close, size: 24, color: Colors.Accent)
```

Like `size:`, color is a property of the glyph written on the call — never a tint wrapper around it. An **outline**
icon (drawn as `fill="none"` plus a stroke) stays an outline.

**Size** defaults to the surrounding text size, and you override it per call with `size:` — a length:

```osy title="overriding one glyph's size on the call" syntax
Icon(Icons.Search, size: 18)       // 18px
Icon(Icons.Check, size: 14)        // 14px — a denser glyph
Icon(Icons.Menu, size: "1.5em")    // relative to the surrounding text
```

Size is a property of the glyph, so it is written on the call — never a wrapper component around it. If your app
uses a handful of standard sizes, name them with an enum and pass the member (its value is the length):

```osy title="naming your standard sizes" test app=file-manager
enum IconSize { Sm = 14, Md = 18, Lg = 24 }

[Composable]
component SizedSearch() {
  render { Icon(Icons.Search, size: IconSize.Md); }
}
```

`size:` is the only argument `Icon` takes besides the name; a bare second argument or any other named argument is a
compile error, so `Icon(Icons.Search, 18)` is corrected to `Icon(Icons.Search, size: 18)`.

### What an icon may contain   {#contents}
An icon is shapes: `path` `circle` `ellipse` `line` `polyline` `polygon` `rect` `g`, and a `viewBox` on the root
`<svg>`. Titles, descriptions and `id`/`class` attributes are dropped — they aren't needed.

Anything that could **run, load, or reference** something is a compile error, naming the file:

```text
'evil.svg' contains a <script> element. An icon may only contain shape elements
(circle, ellipse, g, line, path, polygon, polyline, rect); scripting, styling,
embedding and animation are not allowed.
```

That covers `<script>`, `<style>`, `<image>`, `<a>`, `<use>`, animation elements, any `on…` handler, and any
attribute that points somewhere (`fill="url(#x)"`, `xlink:href`, a `javascript:` link). An SVG is a place scripts
can hide, and your icons are placed directly into your app's pages — so the rule is an allow-list, and it is not
negotiable.

An icon with no drawable content left, or with no `viewBox`, is also an error rather than an invisible square.

### Icons from a UI kit   {#kits}
A kit's icons land in your app tree alongside your own, and are picked up the same way. Two files claiming the same
name is an error naming both, so an icon always resolves to exactly one file.

### Passing an icon around — `Icons` is a type   {#as-a-value}
`Icons` is not only a spelling for a call site: it is a **type**, so an icon is a value you can pass, return, store
and compare like any other.

```osy title="a component that takes an icon" test app=drop-ship-order
[Composable]
component NavItem(string label, Icons icon) {
  render {
    Row(gap: 2) {
      Icon(icon);
      Text(label);
    }
  }
}
```

The caller names the glyph the same way it always did:

```osy syntax title="calling it — the call site does not change"
NavItem("Search", Icons.Search);
```

The same type works in every other position — a return type, a local, a parameter to an ordinary function, and a
field on an entity:

```osy syntax title="the same type in every other position — return, local, parameter, field"
entity NavEntry {
  string Label;
  Icons Glyph;                       // stored, keyed by the file's own name
}

Icons GlyphFor(bool searching) {
  return searching ? Icons.Search : Icons.Close;
}
```

Because a stored icon is keyed by the file's **name** and not by a position in a list, adding a new `.svg` to your
app never changes what an already-stored row means.

⭐ **A KIT CONTROL HOLDS THE SAME TYPE.** `Icons` is built from YOUR files, so it looks like a type only your own
app could name — but the compiler puts it in scope for the kits your app takes as well, and the vocabulary it hands
them is yours, merged with the built-ins. That is why `NavItem.Icon` in the bundled app shell is an `Icons`: your
`icons/logo.svg` is a nav row's glyph, a menu row's glyph and the shell's brand mark, written exactly as you write
`Icons.Search` anywhere else. There is one icon vocabulary in an Osy# app and a kit does not get its own.

### How icons are delivered — inline, on the first byte   {#delivery}
Your icons are combined into one small file, and it is **already in the page** when it arrives — a server-rendered
page paints its icons on the very first byte, before any script runs. There is no icon-flash, no request, and
nothing to configure. Change an icon and the file changes with it; leave them alone and browsers keep the copy they
already have.

Custom glob, if `model/icons/` doesn't suit you:

```osy syntax
app Admin {
  model "model/**/*.osy";
  icons "assets/icons/*.svg";
}
```

## See also   {#see-also}
- [theme tokens](https://osysharp.com/reference/ui/theming/) — the tokens an icon's color resolves against.
- [layout primitives](https://osysharp.com/reference/ui/layout/) — `Row`/`Stack` and the `gap` that spaces an icon from its label.
- The **drop-ship-order** sample (`osy docs sample drop-ship-order`) — its `icons/` folder is the vocabulary the
  compiled examples above resolve against, and its storefront header uses one.
