From 8b2367cd8d623b44df1c89552992b440127bcc12 Mon Sep 17 00:00:00 2001 From: dallasbpeters Date: Tue, 29 Sep 2026 16:04:02 -0500 Subject: [PATCH 1/4] add svg-icon support --- CHANGELOG.md | 7 +++++ src/components/icon.css | 39 ++++++++++++++++++++++++++++ src/stories/Components/Icon/Icon.mdx | 27 +++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ccaac3..a147eda0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Component Changes + +- Add `.icon--svg` for inline SVG icon sets (for example, Hugeicons) + - Size modifiers work as they do for icon fonts. Weight modifiers set the stroke width, and `.icon--low-emphasis` lowers the opacity. Stroke widths can be customized with `--_op-icon-stroke-width-*`. + ## [2.4.0] - 2026-06-25 ### Utility Changes diff --git a/src/components/icon.css b/src/components/icon.css index 7482f314..fd3e0af1 100644 --- a/src/components/icon.css +++ b/src/components/icon.css @@ -112,6 +112,45 @@ font-weight: unset; } +.icon--svg { + /* Public API (customizable component options) */ + --_op-icon-stroke-width-light: 1; + --_op-icon-stroke-width-normal: 1.5; + --_op-icon-stroke-width-semi-bold: 2; + --_op-icon-stroke-width-bold: 2.5; + --_op-icon-svg-opacity-low-emphasis: 0.6; + + svg { + display: block; + block-size: 100%; + inline-size: 100%; + } + + &.icon--weight-light { + --__op-icon-stroke-width: var(--_op-icon-stroke-width-light); + } + + &.icon--weight-normal { + --__op-icon-stroke-width: var(--_op-icon-stroke-width-normal); + } + + &.icon--weight-semi-bold { + --__op-icon-stroke-width: var(--_op-icon-stroke-width-semi-bold); + } + + &.icon--weight-bold { + --__op-icon-stroke-width: var(--_op-icon-stroke-width-bold); + } + + &:is(.icon--weight-light, .icon--weight-normal, .icon--weight-semi-bold, .icon--weight-bold) svg [stroke-width] { + stroke-width: var(--__op-icon-stroke-width); + } + + &.icon--low-emphasis { + opacity: var(--_op-icon-svg-opacity-low-emphasis); + } +} + /* Custom SVG Icons app specific */ .custom-icons { svg { diff --git a/src/stories/Components/Icon/Icon.mdx b/src/stories/Components/Icon/Icon.mdx index d12818cd..49a3dece 100644 --- a/src/stories/Components/Icon/Icon.mdx +++ b/src/stories/Components/Icon/Icon.mdx @@ -244,6 +244,26 @@ Emphasis acts similarly to weight, but changes the thickness of the icon strokes +## SVG Icons + +`.icon--svg` styles an icon drawn as inline SVG instead of a font glyph. Use it for SVG sets such as [Hugeicons](https://hugeicons.com), or for Lucide, Tabler, and Phosphor when you want only the icons your app uses. Optics does not ship the SVG files. Each app keeps its own, and the [rolemodel_rails icon generator](https://github.com/RoleModel/rolemodel_rails/tree/main/lib/generators/rolemodel/optics/icons) (`--hugeicons`) renders them. + +```html + +``` + +- **Color:** the SVG uses `currentColor`, so it follows the text color. +- **Size:** the size modifiers work as they do for fonts. The SVG fills the `.icon` box. +- **Weight:** the stroke keeps the width the set drew until a weight modifier is set. Then `.icon--weight-*` sets the stroke width of every stroked element. +- **Emphasis:** `.icon--low-emphasis` lowers the opacity. Normal and high look the same. +- **Fill:** SVG sets draw filled icons as a separate style (for example, Hugeicons `solid-rounded`), so the fill modifiers do not apply. + +If the app uses only SVG icons, import `optics+no_icons.css` so that no icon font loads. + ## Icon API Styles are built on CSS variables scoped to the icon. @@ -276,6 +296,13 @@ Here are the variables that can be customized: --_op-icon-optical-size-medium --_op-icon-optical-size-large --_op-icon-optical-size-x-large + +/* SVG icons (.icon--svg) */ +--_op-icon-stroke-width-light +--_op-icon-stroke-width-normal +--_op-icon-stroke-width-semi-bold +--_op-icon-stroke-width-bold +--_op-icon-svg-opacity-low-emphasis ``` {/* prettier-ignore-end */} From af43a5465cfe4383b85d7037ab930a4c6993687a Mon Sep 17 00:00:00 2001 From: dallasbpeters Date: Wed, 30 Sep 2026 09:08:21 -0500 Subject: [PATCH 2/4] Make .svg-icon a block used with .icon, and add a story Review asked whether .icon--svg was a block or a modifier: it was named as a modifier but not nested to enforce it. It is now the .svg-icon block, used with .icon the same way .material-symbols-outlined is. .icon keeps the public API: the stroke-width variables sit with the other icon options, and the weight modifiers set --__op-icon-stroke-width, which .svg-icon reads. Adds an Svg story showing sizes, weights and low emphasis, and shows it in the SVG Icons docs section. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- src/components/icon.css | 39 ++++++++------------- src/stories/Components/Icon/Icon.mdx | 8 +++-- src/stories/Components/Icon/Icon.stories.js | 37 +++++++++++++++++++ 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a147eda0..0a9b244c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Component Changes -- Add `.icon--svg` for inline SVG icon sets (for example, Hugeicons) +- Add `.svg-icon` for inline SVG icon sets (for example, Hugeicons), used with `.icon` - Size modifiers work as they do for icon fonts. Weight modifiers set the stroke width, and `.icon--low-emphasis` lowers the opacity. Stroke widths can be customized with `--_op-icon-stroke-width-*`. ## [2.4.0] - 2026-06-25 diff --git a/src/components/icon.css b/src/components/icon.css index fd3e0af1..f12a640e 100644 --- a/src/components/icon.css +++ b/src/components/icon.css @@ -26,6 +26,13 @@ --_op-icon-optical-size-large: 40; --_op-icon-optical-size-x-large: 48; + /* Stroke width and emphasis for SVG icon sets (.svg-icon) */ + --_op-icon-stroke-width-light: 1; + --_op-icon-stroke-width-normal: 1.5; + --_op-icon-stroke-width-semi-bold: 2; + --_op-icon-stroke-width-bold: 2.5; + --_op-icon-svg-opacity-low-emphasis: 0.6; + /* Private API (component option defaults) */ --__op-icon-weight: var(--_op-icon-weight-normal); --__op-icon-fill: var(--_op-icon-fill-outlined); @@ -53,18 +60,22 @@ /* Weight Modifiers */ &.icon--weight-light { --__op-icon-weight: var(--_op-icon-weight-light); + --__op-icon-stroke-width: var(--_op-icon-stroke-width-light); } &.icon--weight-normal { --__op-icon-weight: var(--_op-icon-weight-normal); + --__op-icon-stroke-width: var(--_op-icon-stroke-width-normal); } &.icon--weight-semi-bold { --__op-icon-weight: var(--_op-icon-weight-semi-bold); + --__op-icon-stroke-width: var(--_op-icon-stroke-width-semi-bold); } &.icon--weight-bold { --__op-icon-weight: var(--_op-icon-weight-bold); + --__op-icon-stroke-width: var(--_op-icon-stroke-width-bold); } /* Emphasis */ @@ -112,36 +123,16 @@ font-weight: unset; } -.icon--svg { - /* Public API (customizable component options) */ - --_op-icon-stroke-width-light: 1; - --_op-icon-stroke-width-normal: 1.5; - --_op-icon-stroke-width-semi-bold: 2; - --_op-icon-stroke-width-bold: 2.5; - --_op-icon-svg-opacity-low-emphasis: 0.6; - +/* Inline SVG icon sets (Hugeicons, Lucide, Tabler...). Used with .icon, e.g. `icon svg-icon icon--large` */ +.svg-icon { svg { display: block; block-size: 100%; inline-size: 100%; } - &.icon--weight-light { - --__op-icon-stroke-width: var(--_op-icon-stroke-width-light); - } - - &.icon--weight-normal { - --__op-icon-stroke-width: var(--_op-icon-stroke-width-normal); - } - - &.icon--weight-semi-bold { - --__op-icon-stroke-width: var(--_op-icon-stroke-width-semi-bold); - } - - &.icon--weight-bold { - --__op-icon-stroke-width: var(--_op-icon-stroke-width-bold); - } - + /* A rule on the root cannot beat a stroke-width attribute on a child, so target the attribute. + With no weight modifier the set keeps the widths it was drawn with. */ &:is(.icon--weight-light, .icon--weight-normal, .icon--weight-semi-bold, .icon--weight-bold) svg [stroke-width] { stroke-width: var(--__op-icon-stroke-width); } diff --git a/src/stories/Components/Icon/Icon.mdx b/src/stories/Components/Icon/Icon.mdx index 49a3dece..11a00d06 100644 --- a/src/stories/Components/Icon/Icon.mdx +++ b/src/stories/Components/Icon/Icon.mdx @@ -246,16 +246,18 @@ Emphasis acts similarly to weight, but changes the thickness of the icon strokes ## SVG Icons -`.icon--svg` styles an icon drawn as inline SVG instead of a font glyph. Use it for SVG sets such as [Hugeicons](https://hugeicons.com), or for Lucide, Tabler, and Phosphor when you want only the icons your app uses. Optics does not ship the SVG files. Each app keeps its own, and the [rolemodel_rails icon generator](https://github.com/RoleModel/rolemodel_rails/tree/main/lib/generators/rolemodel/optics/icons) (`--hugeicons`) renders them. +`.svg-icon` is the block for an icon drawn as inline SVG instead of a font glyph. Use it together with `.icon`, the same way `.material-symbols-outlined` is used with `.icon` for the font. Use it for SVG sets such as [Hugeicons](https://hugeicons.com), or for Lucide, Tabler, and Phosphor when you want only the icons your app uses. Optics does not ship the SVG files. Each app keeps its own, and the [rolemodel_rails icon generator](https://github.com/RoleModel/rolemodel_rails/tree/main/lib/generators/rolemodel/optics/icons) (`--hugeicons`) renders them. ```html -