The Ultimate Guide to CSS Font Weights
Typography is one of the most powerful tools available to web designers, and within typography, font weight plays a central role in establishing visual hierarchy, conveying emphasis, and shaping the personality of a design. CSS font weight is the property that controls how thick or thin the strokes of text characters appear, ranging from delicate hairline weights that feel light and refined to heavy black weights that demand attention and communicate strength. Every website, application, and digital product that displays text makes font weight decisions — sometimes deliberately and thoughtfully, sometimes carelessly and inconsistently — and the quality of those decisions directly affects how readable the content is, how clear the visual hierarchy appears, and how well the design communicates the intended tone and message.
What makes CSS font weight more nuanced than it initially appears is the gap between what the specification supports and what individual fonts actually provide. CSS defines a numeric scale from 100 to 900 in increments of 100, offering nine distinct weight levels, but most fonts only include two or three of those levels, and some include only one. The browser handles the mismatch between requested weight and available weight through a fallback algorithm that can produce results ranging from acceptable to visually problematic depending on the font and the requested weight. This article covers every dimension of CSS font weight in practical depth, from the fundamental syntax and numeric scale through variable fonts that offer true continuous weight control, giving developers and designers everything they need to use font weight effectively and correctly.
The font-weight Property Syntax and Accepted Values
The CSS font-weight property accepts several categories of values, each with distinct behavior and use cases. Numeric values from 100 to 900 in increments of 100 give developers explicit control over the requested weight level, with 400 conventionally representing regular weight and 700 representing bold. These numeric values are the most precise and most broadly applicable way to specify font weight because they map directly to the weight axis of the CSS font matching algorithm without ambiguity. Keyword values provide a more descriptive alternative — normal maps to 400 and bold maps to 700, making them convenient shortcuts for the two most commonly used weights. The relative keywords lighter and bolder adjust the current element’s font weight relative to its parent element’s weight, following a specific lookup table rather than simply subtracting or adding a fixed numeric amount.
The bolder and lighter keywords follow rules that are less intuitive than they might appear, which is why understanding them explicitly prevents unexpected results. When the inherited weight is 100, 200, or 300, bolder produces 400. When the inherited weight is 400 or 500, bolder produces 700. When the inherited weight is 600, 700, 800, or 900, bolder produces 900. The lighter keyword follows the inverse pattern. This lookup table behavior means that applying bolder to text inheriting a weight of 600 produces the same result as applying it to text inheriting a weight of 800 — both produce 900 — which can create visual inconsistency if lighter and bolder are used without understanding their defined behavior. Using explicit numeric values rather than lighter and bolder is a practice that produces more predictable and maintainable results in most design systems.
The Nine Numeric Weight Levels and Their Conventional Names
The CSS specification maps numeric font weight values to conventional weight names that reflect the terminology used in the type design industry, and knowing these names helps developers communicate about weight choices with designers and select appropriate weights from font families that use named weight variants. The weight 100 corresponds to Thin or Hairline, representing the lightest available weight with extremely fine character strokes. Weight 200 is Extra Light or Ultra Light, slightly heavier than Thin but still very delicate. Weight 300 is Light, a popular weight for body text in designs that favor an elegant, airy feel. Weight 400 is Regular or Normal, the standard reading weight used for most body text. Weight 500 is Medium, a slightly heavier variant that sits between regular and bold and is particularly useful for interface labels and captions that need to stand out slightly without appearing fully bold.
Weight 600 is Semi Bold or Demi Bold, a weight that provides clear emphasis while remaining less heavy than a full bold treatment. Weight 700 is Bold, the standard emphasis weight that browsers apply to strong and b elements and that remains the most commonly used non-regular weight in web typography. Weight 800 is Extra Bold or Ultra Bold, providing stronger visual weight than Bold for headlines and display text that needs to dominate a layout. Weight 900 is Black or Heavy, the heaviest standard weight offering maximum visual impact for hero text, posters, and display contexts where extreme typographic weight is a deliberate design statement. Some font families extend beyond this range with weights labeled Ultra Black or Extra Black, but these are not part of the standard CSS numeric scale and are accessed through variable fonts or font-specific class names rather than the standard font-weight property.
How Browsers Handle Missing Font Weight Variants
When a developer requests a font weight that the loaded font family does not provide, the browser does not simply render text at the wrong weight and fail silently — it follows a defined font weight matching algorithm specified in the CSS Fonts specification that attempts to find the closest available weight. The matching rules differ depending on whether the requested weight is below or above 400. For requested weights below 400, the browser first looks for lighter weights below the requested value in descending order, then for heavier weights above 400 in ascending order. For requested weights above 500, the browser first looks for heavier weights above the requested value in ascending order, then for lighter weights below 500 in descending order. For the special case of 400, the browser first tries 500, then weights below 400 in descending order, then weights above 500 in ascending order.
The practical consequence of this matching algorithm is that requesting a weight the font does not include does not necessarily produce an error — it produces a substituted weight that the browser chose as the closest available alternative. This silent substitution can lead to designs where multiple different weight specifications all produce visually identical text because they all resolve to the same available weight. A font that only includes regular and bold variants will render weights 100, 200, 300, and 400 as regular weight and weights 500, 600, 700, 800, and 900 as bold weight, potentially creating a situation where a developer believes they are using five different weight levels while actually using only two. Testing font weight rendering in the browser rather than trusting that requested weights will be visually distinct is an important practice when working with fonts that have limited weight coverage.
Synthetic Bold and the Font Matching Fallback Problem
When a browser cannot find any weight variant that satisfactorily matches a requested bold weight, it may apply synthetic bolding — algorithmically thickening the strokes of an available regular weight to simulate a bold appearance. Synthetic bold is universally considered inferior to a genuine bold font variant because the algorithmic thickening produces uneven stroke widths, degraded letter spacing, and a generally crude appearance that lacks the optical refinements that type designers apply when creating a proper bold weight. Professional type designers compensate for the visual heaviness of bold weight by adjusting letter spacing, stroke contrast, and internal proportions in ways that an algorithm cannot replicate, and the difference between genuine and synthetic bold is often visible to non-specialists once they know to look for it.
The font-synthesis CSS property provides control over whether the browser is permitted to apply synthetic bold, synthetic italic, and synthetic small-caps when genuine variants are not available. Setting font-synthesis: none prevents all synthetic font variants, which causes requested bold text to render at regular weight rather than applying synthetic thickening. Setting font-synthesis: weight none specifically prevents synthetic bold while allowing synthetic italic. In design systems where visual quality is a priority, preventing synthetic bold and ensuring that all displayed font weights correspond to genuine font variants is preferable to allowing algorithmic simulation. The practical implementation of this principle is to load only the font weights actually used in the design and to test that each weight specification renders from a genuine font variant rather than synthetic simulation.
Loading Web Fonts with @font-face and Weight Descriptors
The @font-face rule is the CSS mechanism for loading custom web fonts, and its font-weight descriptor plays a critical role in correctly mapping loaded font files to the weight scale. When loading a font family with multiple weight variants, each weight variant requires a separate @font-face block that specifies the source file for that variant and the font-weight descriptor value that maps it to a specific weight on the numeric scale. Without correct font-weight descriptors, the browser cannot correctly match weight requests to the appropriate font files, resulting in incorrect weight rendering or synthetic bolding where genuine weights are available.
A correctly structured @font-face setup for a font family with four weight variants — Light at 300, Regular at 400, Semi Bold at 600, and Bold at 700 — requires four separate @font-face rules, each specifying the same font-family name, the appropriate font-weight value, and the source files for that particular variant. When all four rules share the same font-family name, the browser treats them as a single logical font family with four available weights and correctly selects the appropriate file when a CSS rule requests that font family at any of the four defined weights. The modern practice of using font-display: swap in @font-face rules improves perceived loading performance by rendering text in a fallback font immediately and swapping to the custom font once it loads, preventing invisible text during font loading. Service-based web font providers like Google Fonts and Adobe Fonts generate @font-face declarations automatically, but understanding the underlying structure is essential for developers working with self-hosted fonts or debugging weight rendering issues.
Variable Fonts and Continuous Weight Control
Variable fonts represent the most significant advance in web typography in recent years, and their impact on font weight is particularly profound. A traditional font family provides weight as a set of discrete files, each covering one point on the numeric scale — a regular file, a bold file, a light file. A variable font packages the entire weight range into a single file by encoding the mathematical relationships between weight variants as interpolation data that allows any intermediate weight to be generated on demand. A variable font with a weight axis ranging from 100 to 900 can render text at weight 342 or weight 617 with the same fidelity as weight 400 or weight 700, enabling typographic precision that discrete font files cannot provide.
Using variable fonts in CSS requires only minor adjustments to the font-weight property syntax. For variable fonts with a continuous weight range, font-weight can accept values at any point within the supported range rather than only the standard hundred increments. A font-weight value of 450 is perfectly valid for a variable font that supports that position on its weight axis. The @font-face rule for a variable font specifies a range for the font-weight descriptor rather than a single value — font-weight: 100 900 indicates that the loaded variable font covers the full standard weight range, and the browser will use it to satisfy any weight request within that range. Variable fonts also reduce the number of HTTP requests needed to load a multi-weight font family from one request per weight variant to a single request for the entire family, which provides a meaningful page load performance benefit in addition to the typographic flexibility they enable.
Font Weight in Design Systems and Component Libraries
Design systems and component libraries need a deliberate and systematic approach to font weight that aligns typographic decisions with the broader visual language of the system. Ad hoc font weight decisions made independently by different developers working on different components produce inconsistent typography that undermines visual coherence across the product. A well-designed system defines a weight scale that maps to the font family’s available weights, assigns each weight level a semantic name reflecting its intended use — a name like weight-body for regular text weight, weight-label for medium weight interface labels, and weight-heading for bold headlines — and documents the appropriate weight for each text style defined in the system.
The implementation of font weight in component libraries benefits from CSS custom properties that expose weight values as design tokens. Defining font weight values as CSS variables with semantic names rather than hardcoding numeric values throughout the codebase allows the entire system’s weight scale to be updated in one location and provides a self-documenting layer that makes the intent behind each weight choice clear to developers using the system. Frameworks and design token tools like Style Dictionary allow weight tokens to be defined once in a platform-agnostic format and compiled to CSS variables, JSON, iOS constants, and Android resources simultaneously, ensuring consistency across web, mobile, and native platforms from a single source of truth. This systematic approach to font weight is a practice that separates mature design systems from collections of loosely related components.
Performance Considerations When Loading Multiple Font Weights
Loading web fonts has a performance cost, and loading multiple weight variants of a font family multiplies that cost proportionally if all weights are loaded regardless of whether they are all used. Each font file loaded by the browser requires an HTTP request, occupies bandwidth during download, and may delay text rendering if the font-display strategy causes the browser to wait for font loading before rendering text. In a naive implementation where a Google Fonts embed loads six weight variants of a typeface for a page that only uses three of them, the unused font files consume bandwidth and potentially delay page rendering without contributing any visual benefit.
The correct approach to font weight performance optimization is to load only the weights that are actually used in the page or application. Google Fonts allows precise weight selection by specifying the needed weights in the font URL, loading exactly the requested variants and nothing more. For self-hosted fonts, ensuring that only the @font-face rules for weights actually used in CSS are included prevents unused weight files from being downloaded. The font-display descriptor controls how the browser handles text rendering during font loading — font-display: swap renders text in a fallback font immediately and swaps when the custom font loads, font-display: optional uses the custom font only if it loads very quickly and falls back permanently otherwise for the current page load, and font-display: block briefly hides text while the font loads. Choosing the appropriate font-display strategy based on the page’s performance priorities and visual requirements is a judgment that affects both user experience and performance metrics.
Font Weight Accessibility and Readability Standards
Font weight has direct implications for accessibility and readability, and the WCAG accessibility guidelines address weight indirectly through requirements for text contrast that are affected by weight choices. Lighter font weights reduce the effective visual weight of text strokes, which can reduce contrast against the background and make text harder to read for users with low vision, color vision deficiencies, or those viewing screens in challenging lighting conditions. Text set at weight 300 with gray color on a white background may appear elegant on a calibrated desktop monitor but become illegible on a mobile device in outdoor daylight, and what appears to meet contrast requirements in a design tool may fail in practice because the thin strokes of light-weight fonts render differently across displays with varying pixel densities and color accuracy.
The practical accessibility guideline for font weight is to treat weights below 400 with caution for any text that carries meaningful content, reserving ultra-light weights for decorative display text that is supplementary rather than essential. Body text intended for extended reading performs best at weight 400 or slightly above, where stroke width provides sufficient contrast and legibility without the visual heaviness that can make bold text tiring to read at length. Weight 700 bold used for emphasis should be used purposefully rather than liberally — when too much text is bold, the visual emphasis loses meaning because everything appears equally important. For users who rely on browser settings to increase font weight for readability, respecting font-weight: bold from user stylesheets and not overriding it with lower weight values through specificity is a small but meaningful accessibility consideration.
Practical Font Weight Pairings That Work Effectively
Effective use of font weight in web design comes from deliberate pairing decisions that create clear visual hierarchy without relying exclusively on weight as the only differentiator between text levels. The most common and reliable pairing approach uses a significant weight contrast between headline and body text — pairing a bold or extra bold headline with a regular weight body creates immediate hierarchy that readers perceive intuitively. A weight 700 headline combined with weight 400 body text establishes clear differentiation; a weight 800 headline with weight 300 body text creates even more dramatic contrast appropriate for editorial or display-focused designs where typographic impact is a primary aesthetic goal.
Interface typography benefits from more subtle weight pairings than editorial typography because the goal is clarity and efficiency rather than visual drama. Using weight 500 or 600 for navigation labels and interactive elements against weight 400 for body content creates sufficient differentiation to guide user attention without creating visual noise. Labels, captions, and metadata that should be secondary to main content can be set at weight 400 with reduced size rather than at reduced weight, avoiding the legibility problems that very light weights can introduce while still achieving visual subordination. The principle that underlies all effective font weight pairing is intentionality — every weight decision should reflect a conscious choice about the relative importance of the content it styles, with the weight scale used consistently enough that users develop an intuitive understanding of the visual language the design establishes.
Troubleshooting Common Font Weight Problems in Production
Font weight problems in production web applications fall into several recurring categories that developers encounter regularly and that have specific diagnostic approaches. The most common problem is font weight requests that produce unexpected visual results because the requested weight is not available in the loaded font and the browser has silently substituted a different weight. Diagnosing this requires opening browser developer tools, inspecting the computed styles of the affected text element, and looking at both the computed font-weight value and the rendered font variant information to confirm whether the browser is using the genuine font at the requested weight or a substitute. The Firefox font panel in developer tools is particularly useful for this diagnosis because it shows which exact font file is being used for selected text.
A second common problem is FOUT, the flash of unstyled text where text renders in a fallback font before the web font loads and then shifts to the correct font. When the fallback font and web font have different weights at the same numeric value — when the system fallback bold appears heavier or lighter than the web font bold — the text shift can cause layout reflow that is visually jarring. Mitigating this requires choosing fallback fonts with weight characteristics similar to the web font and using the size-adjust, ascent-override, descent-override, and line-gap-override descriptors in @font-face to align the metrics of the fallback font with the web font, minimizing the visible shift. A third common problem is inconsistent font weight rendering across operating systems, where the same CSS weight specification appears slightly different on Windows, macOS, and Linux due to differences in font rendering engines and antialiasing approaches. Testing weight rendering across platforms, particularly for text at small sizes where rendering differences are most visible, is an important quality assurance step before shipping typography decisions to production.
Conclusion
CSS font weight is a deceptively deep topic that rewards careful study and thoughtful application. At the surface level, it appears to be a simple property with a straightforward numeric scale — choose a number between 100 and 900, and the text gets thicker or thinner accordingly. The deeper reality is that font weight intersects with font loading, browser font matching algorithms, variable font technology, performance optimization, accessibility requirements, design system architecture, and the fundamental principles of typographic hierarchy in ways that make it one of the most consequential typographic decisions in web development.
The professionals who use font weight most effectively are those who have moved beyond treating it as a binary choice between normal and bold and instead think about it as a full-scale tool for communicating content hierarchy, directing reader attention, and expressing design personality. They know which weights their chosen fonts actually provide, they load only the weights they use, they test weight rendering in real browsers across real devices, and they make weight decisions systematically within a coherent design language rather than on an ad hoc per-element basis. These habits are not complicated to develop, but they do require the kind of deliberate attention to detail that separates production-quality front-end work from code that merely functions.
The emergence of variable fonts has added an exciting dimension to font weight work by making continuous weight control practical for the first time. The ability to specify a font weight of 423 or animate between weights smoothly opens creative possibilities that discrete font files could never support, and the performance benefit of loading a single variable font file instead of multiple discrete weight files makes variable fonts a compelling choice whenever a font family offers them. As browser support for variable fonts is now universal across all modern browsers, the barrier to adopting them is minimal, and developers who have not yet incorporated variable fonts into their typography workflows are leaving both performance gains and creative flexibility on the table.
For developers and designers at any experience level, the practical path to font weight proficiency runs through real projects with real fonts and real browsers. Reading about font weight fallback algorithms is valuable; actually observing how a specific font renders at weights it does not natively support, inspecting the computed styles to see what weight the browser chose, and comparing the result to genuine weight variants from a more complete font family turns abstract knowledge into practical judgment. That practical judgment — knowing when a weight specification will work as intended, when it will fall back to something acceptable, and when it will produce a result that needs a different approach — is what makes the difference between font weight decisions that serve a design well and those that create subtle but persistent typographic problems that users sense even when they cannot name them.