Pseudo-element icons and CSS styling hooks

Render icons through ::before content codes, wire up the font-family rules once, and use v7 CSS custom properties to colour and size icons without touching the markup.

Icons without element markup

<!-- The class you write -->
<a class="btn btn-primary" href="/cart">
  <i class="fa-solid fa-cart-shopping" aria-hidden="true"></i>
  Cart
</a>

<!-- What Font Awesome expands it into: the glyph is injected as ::before
     content, and the font-family is applied to the pseudo-element. -->
<style>
  .fa-solid::before {
    content: "\f07a";                 /* the cart-shopping code point */
    font-family: "Font Awesome 7 Free";
    font-weight: 900;
  }
</style>
/* The complete rule set for using a code point on your own selector.
   This is what .fa-solid does, written out so you can attach it to anything. */
.btn--cart::before {
  display: var(--fa-display, inline-block);
  font-style: normal;
  font-variant: normal;
  line-height: 1;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  font-family: "Font Awesome 7 Free";
  font-weight: 900;                   /* the Solid style */
  content: "\f07a";
  margin-inline-end: 0.5em;           /* logical property: flips in RTL */
}

/* The three free families and their weights */
.fa-regular::before { font-family: "Font Awesome 7 Free"; font-weight: 400; }
.fa-solid::before   { font-family: "Font Awesome 7 Free"; font-weight: 900; }
.fa-brands::before  { font-family: "Font Awesome 7 Brands"; font-weight: 400; }

/* Custom Properties (paid) use the same pattern with a different family name. */
.fa-sharp::before       { font-family: "Font Awesome 7 Sharp"; font-weight: 900; }
.fa-sharp-duotone::before { font-family: "Font Awesome 7 Sharp Duotone"; font-weight: 900; }
FamilyWeightStyleAvailable in
Font Awesome 7 Free900SolidFree
Font Awesome 7 Free400RegularFree, a limited set
Font Awesome 7 Brands400BrandsFree
Font Awesome 7 Pro900 / 400 / 300Solid, Regular, LightPro
Font Awesome 7 Sharp900 / 400 / 300Sharp Solid, Regular, LightPro
Font Awesome 7 Duotone900DuotonePro
Font Awesome 7 Sharp Duotone900Sharp DuotonePro+
⚠️
Overriding content on an existing .fa-* class requires the same specificity or higher, and the icon glyph is a private-use code point. Copy the code from an authoritative source — guessing an offset by one gives you a completely different icon, and a wrong range shows a missing-glyph box.

The v7 CSS custom properties

/* Font Awesome 7 exposes the icon's styling through custom properties,
   which means a single class can be re-themed without a new utility class. */
.icon-badge {
  --fa-primary-color: #6d28d9;
  --fa-secondary-color: #c4b5fd;

  /* Opacity per layer of a duotone icon */
  --fa-primary-opacity: 1;
  --fa-secondary-opacity: 0.4;

  /* Size and layout */
  --fa-size: 2rem;
  --fa-width: 1.25em;             /* fixed width for a vertical stack */
  --fa-li-width: 2.5em;           /* list item width */
  --fa-display: inline-block;

  /* Rotation and transform */
  --fa-rotate-angle: 90deg;
  --fa-flip-x: 1;
  --fa-flip-y: 1;
  --fa-scale: 1.5;

  /* Animation timing */
  --fa-animation-duration: 2s;
  --fa-beat-scale: 1.25;
  --fa-fade-opacity: 0.4;

  /* The masking colour for fa-layers */
  --fa-inverse: #fff;
}

/* Colour inherits from the text colour when no property is set. */
.themed-icon { color: #b91c1c; }
PropertyEffectInherits from
--fa-primary-colorFirst layer of a duotone or layered iconNothing; explicit only
--fa-secondary-colorSecond layerNothing
--fa-primary-opacityFirst layer opacity--fa-primary-opacity
--fa-secondary-opacitySecond layer opacitySame
--fa-sizeFont size of the iconThe element's font-size
--fa-rotate-angleRotation in degrees90deg
colorSingle-colour iconsThe cascade
--fa-animation-durationClock, spin and beat timing2s
--fa-inverseThe colour of fa-inverse layers#fff
<style>
  /* Replacing a whole set of utility classes with one themed rule */
  .sidebar .icon {
    --fa-primary-color: #0ea5e9;
    --fa-secondary-color: #94a3b8;
    --fa-secondary-opacity: 0.5;
    --fa-size: 1.125rem;
  }

  /* A dark-mode override without changing any markup */
  @media (prefers-color-scheme: dark) {
    .sidebar .icon {
      --fa-primary-color: #7dd3fc;
      --fa-secondary-color: #475569;
    }
  }
</style>

<nav class="sidebar">
  <a href="/dashboard"><i class="fa-solid fa-gauge icon" aria-hidden="true"></i> Dashboard</a>
  <a href="/reports"><i class="fa-solid fa-chart-line icon" aria-hidden="true"></i> Reports</a>
</nav>

Declaring icons in one place

/* A pattern that keeps icon choices out of the markup: one map of code
   points, and semantic classes in the HTML. Changing the icon set becomes
   a change to this file only. */
[data-icon]::before {
  font-family: "Font Awesome 7 Free";
  font-weight: 900;
  display: inline-block;
  font-style: normal;
  line-height: 1;
  margin-inline-end: 0.4em;
}

[data-icon="save"]::before     { content: "\f0c7"; }
[data-icon="delete"]::before   { content: "\f2ed"; }
[data-icon="download"]::before { content: "\f019"; }
[data-icon="search"]::before   { content: "\f002"; }

/* Icon-only buttons need the margin removed and the name supplied by
   the button's own accessible name. */
[data-icon-only]::before { margin-inline-end: 0; }
<button type="button" data-icon="save">Save changes</button>
<button type="button" data-icon="delete">Delete</button>
<button type="button" data-icon="search" data-icon-only aria-label="Search"></button>

<!-- The advantage: no <i> element, no class names in the content, and the
     pseudo-element is decorative by definition — screen readers do not
     announce generated content as a separate item, so no aria-hidden is needed. -->
<a href="/export.csv" data-icon="download">Export</a>
ApproachMarkup verbosityThemingRisk
<i class="fa-solid fa-x">Verbose, repeated everywhereClass-basedNone
::before on a semantic classOne attributeCustom property or classGenerated content is invisible to the DOM APIs
SVG+JS replacementClass onlyCSS on the SVGMutationObserver cost
The React/Vue/Angular componentA component with a nameProps and CSSBundle size without tree-shaking
  • Content injected by a pseudo-element is not part of the accessible tree as a separate item, which is why this approach needs no aria-hidden. It is also why it cannot be tested by querying the DOM — read the computed ::before content instead.
  • If the icon is the only content of a control, the control still needs an accessible name: aria-label on the button, or visible text.
  • A pseudo-element inherits the element's font-size, so icon sizing follows the text unless --fa-size overrides it.

FAQ

Why is my ::before icon showing a box?
Either the font file is not loaded, or the code point is not in the family you selected. Check that the family name matches the version exactly — Font Awesome 7 Free not Font Awesome 6 Free — and that you have installed the matching version. A missing glyph renders as a box rather than as nothing.
Can I animate a pseudo-element icon?
Yes. The ::before pseudo-element supports transforms, transitions and keyframe animations exactly like a real element, so transition: transform plus a hover rotate works. Keyframe names must not collide with the ones Font Awesome ships if you also use fa-spin.

Using, sizing and animating icons Kits, CDN and self-hosting

Last refreshed 2026-09-18.