Skip to content

feat: support Font Awesome 4 through 7 icon syntax - #821

Merged
bbrala merged 2 commits into
masterfrom
feat/font-awesome-4-through-7
Jul 30, 2026
Merged

feat: support Font Awesome 4 through 7 icon syntax#821
bbrala merged 2 commits into
masterfrom
feat/font-awesome-4-through-7

Conversation

@bbrala

@bbrala bbrala commented Jul 30, 2026

Copy link
Copy Markdown
Member

Supersedes #776, which asked "Shouldn't we support both versions?" and never got an answer. This supports all of them, and adds v7.

The problem

Font Awesome renames a style in every major version, and v6 split what used to be one prefix into a separate family and style class:

Version Syntax
v4 fa fa-user
v5 fas fa-user
v6, v7 fa-classic fa-solid fa-user, fa-sharp fa-solid fa-user, fa-sharp-duotone fa-solid fa-user

Detection only knew v5's five short prefixes and v4's shorthand, so on current master:

item.icon Rendered today
fa-solid fa-user <i class="fa fa-solid fa-user"> stray fa
fa-sharp fa-solid fa-user <i class="fa fa-sharp fa-solid fa-user"> stray fa
fa-brands fa-github <i class="fa fa-brands fa-github"> stray fa
fasds fa-user context-menu-icon-fasds on the item no icon at all
fa fa-user context-menu-icon-fa on the item no icon at all

The stray fa is not cosmetic. In v6/v7 it is a legacy alias that sets the solid font-weight and the Free font-family, so it competes with fa-regular for weight and fa-brands for family, and which wins depends on stylesheet order.

Why not just extend the regex

v7 keeps adding packs, each with its own short prefix. The docs list sharp-duotone (fasds) and notdog-duo (fands), and there is a REST endpoint whose entire purpose is enumerating them. Any hardcoded list is guaranteed to need another patch at v8.

So instead of matching a version, this asks the only two questions that matter:

  1. Is this Font Awesome at all? An fa-* token says yes.
  2. Does the class list already name a family/style? If not, and only then, supply v4's base fa. That is what makes the icon: "fa-user" shorthand work.

Short prefixes are matched by shape (fa, fas, fass, fasds, fands, …), so a pack added later needs no change here.

Backwards compatibility

Of 147 unit tests, exactly 4 change behaviour and 143 do not — verified by running the new tests against unmodified source, where only the four broken cases above fail and every compat pin passes.

  • v4 shorthand fa-user still gets fa supplied, and so does the modifier form fa-user fa-lg, which relies on it to render at all. Neither names a family, so the old behaviour is preserved exactly. This was the trap: a blanket "pass classes through untouched" would have silently broken every v4 site.
  • v5 fas/far/fal/fad/fab are byte-identical to before.
  • Built-in icon names are untouched. Detection keys off an fa- token rather than a bare fa… prefix precisely so a short custom name like icon: "fav" is not swallowed. Pinned by a test.
  • --fa5 is still the class emitted on the item. Renaming it to --fa6 (as Support FontAwesome 6 classNames #776 did) would break any custom CSS matching it, and the marker was never version-specific: it is just the hook the stylesheet uses to position the child icon. Documented as historical rather than renamed.
  • No SCSS change, so the compiled CSS is unchanged.

Verification

147/147 unit and 38/38 acceptance green locally, including the existing menu-title-icon-alignment specs. dist/ reverted. Lint adds nothing; the 4 reported errors are pre-existing in the vendored src/jquery.ui.position.js.

documentation/docs/font-awesome.md now shows every supported syntax, and states the two rules plus the --fa5 naming.

Not included

Other icon libraries (bi bi-trash, mdi mdi-delete) still fall through to the built-in branch and emit unusable classes. Broadening detection to any multi-class string would cover them, but it would also change what icon: "edit extra-class" does today, so it wants its own decision. The function form of icon already handles them.

Font Awesome renames a style in every major version:

    v4          <i class="fa fa-user">
    v5          <i class="fas fa-user">
    v6 and v7   <i class="fa-solid fa-user">
                <i class="fa-sharp fa-solid fa-user">
                <i class="fa-sharp-duotone fa-solid fa-user">

Detection only knew v5's five short prefixes and v4's shorthand, so on current
master:

    "fa-solid fa-user"          -> <i class="fa fa-solid fa-user">   stray `fa`
    "fa-sharp fa-solid fa-user" -> <i class="fa fa-sharp fa-solid fa-user">
    "fasds fa-user"             -> context-menu-icon-fasds on the item, no icon
    "fa fa-user"                -> context-menu-icon-fa on the item, no icon

The stray `fa` is not harmless: in v6 and v7 it is a legacy alias that sets the
solid font-weight and the Free font-family, so it competes with `fa-regular`
for weight and with `fa-brands` for family, and which one wins depends on
stylesheet order.

Rather than enumerate versions, which is what put us here, ask the two
questions that actually matter. Is this Font Awesome at all, i.e. is there an
`fa-*` token? And does the class list already name a family/style, or does v4's
base `fa` need supplying? Short prefixes are matched by shape
(`fa`, `fas`, `fass`, `fasds`, `fands`, ...) rather than from a list, so the
packs v7 keeps adding need no change here.

Backwards compatibility. Of 147 unit tests, exactly 4 change behaviour and 143
are unchanged, all four being the broken cases above:

* v4 shorthand `fa-user` still gets `fa` supplied, and so does the modifier
  form `fa-user fa-lg`, which relies on it to render at all. Nothing in either
  list names a family, so the old behaviour is exactly preserved.
* v5's `fas`/`far`/`fal`/`fad`/`fab` are byte-identical to before.
* Built-in icon names are untouched. Detection keys off an `fa-` token rather
  than a bare `fa…` prefix specifically so a short custom name such as
  `icon: "fav"` is not swallowed.
* `--fa5` is still the class emitted on the item. Renaming it to `--fa6`, as
  the original patch in #776 did, would break any custom CSS matching it, and
  the marker was never version-specific: it is just the hook the stylesheet
  uses to position the child icon.

Refs #776.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1c4811e13

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/jquery.contextMenu.js Outdated
// a family or style class: the v6/v7 long forms, plus any short prefix
// (`fa`, `fas`, `fab`, `fass`, `fasds`, ...). The short prefixes are matched
// by shape so a pack added in a future release keeps working untouched.
var faFamilyOrStyle = /^(?:fa[a-z]{0,4}|fa-(?:classic|sharp|sharp-duotone|duotone|solid|regular|light|thin|brands))$/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize Font Awesome 7 semibold family classes

When a Font Awesome 7 icon uses a newer long-form combination such as fa-whiteboard fa-semibold fa-... or fa-utility fa-semibold fa-..., none of its family/style tokens matches this hard-coded alternation. namesFontAwesomeFamily() therefore returns false and the rendering branch injects the legacy v4 fa class, which can select the wrong font family and prevent these advertised v7 icons from rendering. Include the remaining v7 family/style names or detect long-form family/style tokens without a closed list.

Useful? React with 👍 / 👎.

Review feedback: the previous commit claimed not to enumerate versions, then
buried an enumeration inside a fuzzy pattern:

    /^(?:fa[a-z]{0,4}|fa-(?:classic|sharp|sharp-duotone|duotone|solid|regular|light|thin|brands))$/

Two simplifications make it unnecessary.

A short prefix never contains a hyphen. `fa`, `fas`, `fasds`, `fands` are all
hyphen-free, while icon names and modifiers (`fa-user`, `fa-lg`) always have
one. So telling a prefix from an icon name needs no list, and the `fa[a-z]{0,4}`
guesswork goes away with it - along with its false positives on words like
"fancy".

Only the styles need listing, not the families. Version 6 and 7 always name a
style next to the family, so `fa-notdog-duo fa-solid fa-user` is recognised
through its `fa-solid`. A family added after this code was written needs no
change, which is what the pattern was reaching for in the first place. Added a
test for exactly that case.

What is left is a padded string of nine class names and two `indexOf` checks.
The only remaining regex is the `split(/\s+/)` that tokenises the class list.
The padded-string form also avoids an icon named "constructor" matching
Object.prototype, which an object lookup would have.

No behaviour change: 143 of 148 tests are unchanged against master, the same
set as before, and the 5 that differ are the previously broken syntaxes plus
the new unlisted-family case.
@bbrala

bbrala commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

Pushed dfad914 after review feedback that this was too regex-heavy. Fair hit, and it was worse than that: the previous commit claimed not to enumerate versions, then buried an enumeration inside a fuzzy pattern.

/^(?:fa[a-z]{0,4}|fa-(?:classic|sharp|sharp-duotone|duotone|solid|regular|light|thin|brands))$/

Two observations made it unnecessary.

A short prefix never contains a hyphen. fa, fas, fasds, fands are all hyphen-free; icon names and modifiers (fa-user, fa-lg) always have one. So telling them apart needs no list of prefixes, and the fa[a-z]{0,4} guesswork goes away, along with its false positives on ordinary words like "fancy".

Only the styles need listing, not the families. v6 and v7 always name a style beside the family, so fa-notdog-duo fa-solid fa-user is recognised through its fa-solid. A family added after this code was written needs no change, which is what the pattern was reaching for anyway. There is now a test for exactly that.

What's left is a padded string of nine class names and two indexOf calls:

var faFamilyAndStyleClasses = ' fa-solid fa-regular fa-light fa-thin fa-brands' +
    ' fa-duotone fa-classic fa-sharp fa-sharp-duotone ';

// ...
if (token.indexOf('fa') === 0 && token.indexOf('-') === -1) { return true; }
if (faFamilyAndStyleClasses.indexOf(' ' + token + ' ') !== -1) { return true; }

The only regex left is the split(/\s+/) that tokenises the class list. The padded-string form matches the ' x ' idiom already used in this file and, incidentally, avoids an icon named constructor matching Object.prototype, which an object lookup would have.

On the alternative of a faVersion option, which was raised: I don't think it earns its keep.

  • It doesn't remove the decision. Given faVersion: 6 and icon: "fa-user", the plugin still has to decide whether to supply fa, because the version says nothing about whether the caller's class list is complete.
  • Every site that works today with no configuration would need to declare something to keep working. Making it optional means keeping detection as the fallback, so we'd maintain both paths.
  • The version would be per-registration while icons are per-item, so a site mid-migration with both syntaxes can't express that.

Behaviour is unchanged by this commit: 143 of 148 tests match master, the same set as before, and the 5 that differ are the previously broken syntaxes plus the new unlisted-family case. 148/148 unit and 38/38 acceptance green locally, CI green.

@bbrala
bbrala merged commit d2a7531 into master Jul 30, 2026
9 checks passed
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.

1 participant