Recursive AI

Compute CSS styles for an element

All onehtml recipes

Get the computed value of any CSS property for an element, find the rule it came from, and recompute styles for a focused element or a narrow viewport, including stylesheets the page links to.

DocumentStyles.Create gathers a page’s style sources and runs the cascade. That includes the HTML user-agent stylesheet, presentational attributes, <style>, style="" and linked sheets, with layers, !important, var(), shorthands and @media/@supports. The library never fetches anything, so a <link rel=stylesheet> gets its bytes from a resolver you supply, keyed by absolute URL.

The page in this example links /css/site.css:

CSS
body { font-family: Georgia, serif; color: #222 }
.card h2 { font-size: 1.5rem }
nav ul { display: flex; gap: 1rem }
@media (max-width: 600px) {
  nav ul { display: block }
}
C#
using System.Text;
using RecursiveAI.OneHtml;
using RecursiveAI.OneHtml.Css;
 
Document doc = Document.Parse(File.ReadAllBytes("events.html"));
 
// The page links /css/site.css. The library never fetches it: you hand it the bytes.
var options = new StyleSourceOptions
{
    BaseUrl = "https://library.example/events/",
    Resolver = url => url == "https://library.example/css/site.css"
        ? new StylesheetResource(File.ReadAllBytes("site.css"))
        : null,
};
DocumentStyles styles = DocumentStyles.Create(doc, StyleContext.Default, options);
 
Node heading = doc.Root.QuerySelector(".card h2");
ComputedStyle style = styles.GetStyle(heading);
Console.WriteLine($"color: {style.GetPropertyValue("color")}, computed: {style.GetComputedValue("color")}");
Console.WriteLine($"font-size: {style.FontSizePx}px, display: {style.GetPropertyValue("display")}");
 
// Where a value came from
DeclarationSource? source = style.GetSource("color");
Console.WriteLine($"color set by \"{source?.SelectorText}\" in {source?.SheetUrl ?? "an inline <style>"}");
DeclarationSource? font = style.GetSource("font-family");
Console.WriteLine($"font-family: {style.GetPropertyValue("font-family")} from \"{font?.SelectorText}\" in {font?.SheetUrl}");
 
// The same element under another context: a focused link, a narrow viewport
Node link = doc.Root.QuerySelector("main a");
DocumentStyles focused = styles.WithContext(StyleContext.Default.WithFocus(link));
Console.WriteLine($"outline normally: {styles.GetStyle(link).GetPropertyValue("outline-style")}, " +
                  $"focused: {focused.GetStyle(link).GetPropertyValue("outline-style")}");
 
DocumentStyles phone = styles.WithContext(StyleContext.Default.WithMedia(new MediaFeatures { Width = 390, Height = 844 }));
Node nav = doc.Root.QuerySelector("nav ul");
Console.WriteLine($"nav at {MediaFeatures.Default.Width}px: {styles.GetStyle(nav).GetPropertyValue("display")}, " +
                  $"at 390px: {phone.GetStyle(nav).GetPropertyValue("display")}");
 
foreach (StyleDiagnostic diagnostic in styles.Diagnostics)
{
    Console.WriteLine(diagnostic);
}
Text
color: #1a4d7a, computed: rgb(26, 77, 122)
font-size: 24px, display: block
color set by ".card h2" in an inline <style>
font-family: Georgia, serif from "body" in https://library.example/css/site.css
outline normally: none, focused: solid
nav at 1280px: flex, at 390px: block

How to read it:

  • GetPropertyValue returns the value as text. GetComputedValue returns the getComputedStyle serialization for the typed properties, such as colors as rgb(). Typed accessors such as Color, FontSizePx, Display, Visibility and Opacity give values you can compute with: 1.5rem became 24px.
  • GetSource names the declaration that won: its selector, sheet URL (null for an inline <style>), origin, layer and importance. For an inherited property it is the ancestor’s declaration, here body in site.css.
  • StyleContext is the environment: the viewport and media features, plus at most one focused, hovered, active or target element. WithContext returns a view that recomputes only what the new context can reach. The default viewport is 1280 px wide.
  • Diagnostics reports what couldn’t be used, such as a linked sheet your resolver returned null for (MissingStylesheet), an import cycle or an unsupported rule. This page has none.

There is no layout. Values that depend on layout, like percentages resolved against a containing block or the used width of an element, stay unresolved. Cascade lists what is implemented and what isn’t, and CSS values lists the typed properties.