Skip to content

feat: Add ui.icon() for inline SVG icons (FontAwesome + Bootstrap)#2165

Draft
elnelson575 wants to merge 12 commits intomainfrom
feat/icon-function
Draft

feat: Add ui.icon() for inline SVG icons (FontAwesome + Bootstrap)#2165
elnelson575 wants to merge 12 commits intomainfrom
feat/icon-function

Conversation

@elnelson575
Copy link
Copy Markdown
Contributor

@elnelson575 elnelson575 commented Feb 11, 2026

Closes #2159

Summary

  • Adds ui.icon() for rendering FontAwesome and Bootstrap Icons as inline SVGs
  • Both icon sets are bundled directly — no external packages required (eliminates faicons dependency)
  • Bootstrap Icons are bundled to match parity with R's bslib, which includes Bootstrap Icons out of the box
  • API: icon(name, *, lib="fa", size=None, fill=None, class_=None, id=None, variant=None, **kwargs)
    • lib: "fa" (default) or "bs"
    • variant: selects FontAwesome style ("solid", "regular", "brands", etc.)
    • size: semantic sizes ("xs", "sm", "lg", "xl", "2xl", etc.) or any CSS unit ("2em", "24px")
    • SVG presentation attributes (stroke, fill_opacity, etc.) pass through via **kwargs
  • Full accessibility support: decorative (default, aria-hidden) and semantic (aria-label) modes
  • Drop-in replacement for faicons.icon_svg() — same layout defaults (margin_left, margin_right, position)
  • Icons are lazy-loaded from JSON on first use and cached for the process lifetime

Test plan

  • tests/pytest/test_icon.py covers BS and FA icons, all params, a11y modes, unknown icon errors, semantic sizes, stroke/fill attrs
  • Example apps: shiny/api-examples/icon/app-core.py and app-express.py
  • Verify ui.icon("star") works in both core and express apps
  • Verify ui.icon("github", variant="brands") renders correctly
  • Verify ui.icon("star", lib="bs", variant="solid") emits a warning

🤖 Generated with Claude Code

Right now, py-shiny requires the faicons package in order to use icons in shiny functions. This gets irritating. We should support icons through a native shiny function.
Reference the info in this function: #2159

It should support FontAwesome and Bootstrap Icons at minimum, potentially other icon libraries if they are popular enough.

The function signature should be similar to https://github.com/rstudio/bsicons icon function signatures but more pythonic if appropriate.
@elnelson575 elnelson575 self-assigned this Feb 11, 2026
Comment thread shiny/ui/_icon.py Outdated
elnelson575 and others added 5 commits February 23, 2026 15:15
… docs

- Add fa_icons.json (Font Awesome Free 6.x, 1852 icons) to git so the FA
  icon path works in clean checkouts
- Fix numeric size test assertions to verify proper px units
- Update example apps to demonstrate both FA and BS icons
- Add update process documentation to _icon_data/__init__.py
- Add CHANGELOG entry for ui.icon()
- Delete stale ADR (decision superseded by implementation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BS icons with a11y="semantic" now always set aria-label: uses title if
  provided, otherwise derives it from the icon name (hyphens → spaces) and
  emits a warning suggesting an explicit title
- Fix test assertions for numeric size to verify px units are present
- Add comment noting FA positioning defaults match faicons.icon_svg() for
  drop-in migration compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hrough

- Add explicit `variant` parameter to icon() for FA style selection
  (replaces overloaded `style` kwarg)
- Remove pre-extraction of stroke/fill_opacity/stroke_width/stroke_opacity;
  these now pass through as SVG presentation attributes on the element
- Merge user-supplied `style` kwarg into generated CSS rather than overwriting
- BS icons: same SVG attribute passthrough, simpler extraction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@elnelson575 elnelson575 changed the title feat: Add Icon Funtion feat: Add ui.icon() for inline SVG icons (FontAwesome + Bootstrap) Apr 21, 2026
elnelson575 and others added 4 commits April 21, 2026 16:56
…sistency

- Add `icon` to shiny.express.ui exports (was missing — express example app crashed)
- Fix double semicolon in FA style merge (css() already adds trailing semicolon)
- Standardize aria-hidden to hyphenated form in _icon_bs (was using underscore)
- Add warning when variant is passed with lib="bs" (was silently ignored)
- Fix import to use public .css package boundary instead of private ._css_unit submodule
- Remove dead code branches in FA size logic (size always sets both height and width)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implementation:
- Remove html_escape() from title/aria-label construction in both FA and BS
  icons — htmltools escapes text and attribute values automatically on render;
  pre-escaping caused double-escaped output (e.g. &amp;amp; in rendered HTML)

Tests:
- Merge redundant test_icon_bs_defaults_to_decorative into test_icon_bs_a11y_decorative
- Replace 3 separate semantic-size tests with a single @pytest.mark.parametrize
  covering all 7 sizes (xs, sm, md, xl were previously untested)
- Remove thin example-section tests that only asserted isinstance(icon, Tag)
- Add gap coverage: BS variant warning, FA semantic a11y without title,
  FA title+decorative coexistence, FA/BS class_ parameter, FA style kwarg
  merge (no double semicolon), HTML special-char escaping in title/aria-label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FA: add brand variants (python, twitter), custom fill colors
- BS: add custom fill, CSS class styling (text-warning/success/danger)
- Sizes: add semantic sizes (xs→2xl) alongside CSS units and numeric px
- A11y: show decorative pattern (label the container), semantic with title
  (both FA and BS), and semantic without title (derives label from icon name)
- In-context: buttons with filled/colored icons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…veAspectRatio

Implementation:
- Remove dead _parse_length_unit / _CSS_LENGTH_UNITS / re import (unused since
  size logic was simplified in an earlier commit)
- Fix preserveAspectRatio guard: was always True (height/width always assigned);
  now only set when size is explicitly provided
- FA semantic a11y without title now warns (matching BS behaviour); previously
  silently fell back to icon_data['label'] with no signal to the caller
- Correct docstring: remove broken aria_label kwarg example (aria_label +
  aria-hidden='true' produces invalid ARIA); point callers to a11y='semantic'
- Fix size type annotation on _icon_fa/_icon_bs: Optional[CssUnit] ->
  Optional[IconSize] to match public API signature
- Add BS layout defaults (matching FA): margin-left:auto, margin-right:0.2em,
  position:relative, vertical-align:-0.125em, overflow:visible

Tests:
- Add test_icon_bs_default_margins, test_icon_bs_default_position,
  test_icon_bs_custom_margins
- Add test_icon_fa_semantic_a11y_without_title_warns
- Update test_icon_fa_semantic_a11y_without_title_uses_icon_label to assert
  warning is emitted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Icon function

1 participant