---
title: "Parse and edit CSS"
url: https://recursiveai.org/onehtml/cookbook/parse-and-edit-css/
description: "Parse a stylesheet into rules and declarations with source ranges, walk nested @media rules, and change a declaration or add a rule without reformatting…"
version: "1.0.0"
---

# Parse and edit CSS

Parse a stylesheet into rules and declarations with source ranges, walk nested @media rules, and change a declaration or add a rule without reformatting the rest of the file.

`Stylesheet.Parse` implements CSS Syntax Level 3. Malformed CSS never throws: invalid parts become `Invalid` nodes. Only the hostile-input size limits throw (`CssLimitExceededException`). Every node keeps its source range, so you can read the original text (`PreludeText`, `ValueText`, `Raw`) as well as the parsed component values. This example uses the same `site.css` as [Compute CSS styles](https://recursiveai.org/onehtml/cookbook/computed-style-csharp/):

```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 }
}
```

```csharp
using RecursiveAI.OneHtml.Css;
using RecursiveAI.OneHtml.Editing;

string css = File.ReadAllText("site.css");
Stylesheet sheet = Stylesheet.Parse(css, "https://library.example/css/site.css");

void Print(CssNode rule, string indent)
{
    if (rule.Kind == CssNodeKind.AtRule)
    {
        Console.WriteLine($"{indent}@{rule.Name} {rule.PreludeText.ToString().Trim()}");
        foreach (CssNode child in rule.Rules)
        {
            Print(child, indent + "  ");
        }
        return;
    }
    Console.WriteLine($"{indent}{rule.PreludeText.ToString().Trim()}");
    foreach (CssNode declaration in rule.Declarations)
    {
        Console.WriteLine($"{indent}  {declaration.Name}: {declaration.ValueText.ToString().Trim()}{(declaration.IsImportant ? " !important" : "")}");
    }
}

foreach (CssNode rule in sheet.Rules)
{
    Print(rule, "");
}

// Edit the stylesheet the same lossless way as HTML: only the touched declaration changes
CssNode cardHeading = sheet.Rules.First(r => r.PreludeText.ToString().Trim() == ".card h2");
BatchResult result = StylesheetEditing.Apply(sheet, new[]
{
    SheetEdit.SetDeclaration(sheet, cardHeading, "font-size", "1.75rem"),
    SheetEdit.AppendRule(sheet, ".card h2 a { text-decoration: none }"),
});
Console.WriteLine($"Succeeded: {result.Succeeded}");
Console.Write(result.Sheets.Single().NewSheet.ToSourceText());
```

```text
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
Succeeded: True
body { font-family: Georgia, serif; color: #222 }
.card h2 { font-size: 1.75rem }
nav ul { display: flex; gap: 1rem }
@media (max-width: 600px) {
  nav ul { display: block }
}
.card h2 a { text-decoration: none }
```

Notes:

- `sheet.Rules` lists the top-level rules. A rule’s `Kind` is `QualifiedRule` (a style rule) or `AtRule`. For at-rules, `Name` is the at-keyword and `AtRuleKind` says which one (`Media`, `Supports`, `Layer`, `Import` and others). `Rules` and `Declarations` give the children.
- `ToSourceText()` returns the sheet’s text with its original formatting. `Serialize()` writes the CSSOM serialization instead.
- `StylesheetEditing.Apply` edits the way HTML edits do. It splices only the touched declaration or rule, copies the file’s spacing and indentation for new rules, and parses the result again to verify it. The other operations are `RemoveDeclaration`, `InsertRuleBefore`, `InsertRuleAfter`, `ReplaceRule` and `RemoveRule`.
- To edit a `<style>` element or a linked sheet together with the page, `InlineStylesheets.Of(doc)` returns the page’s `<style>` sheets, `LinkedStylesheet.Create(url, bytes)` wraps a linked one, and `new EditBatch(doc, edits, sheetEdits, linkedSheets).Apply()` runs HTML and CSS edits as one transaction.
- Inline `style=""` attributes are edited with `Edit.SetStyle` and `Edit.RemoveStyle` on the element (see [Edit HTML without reformatting it](https://recursiveai.org/onehtml/cookbook/edit-html-csharp-lossless/)).

Parsing details are in [CSS syntax](https://recursiveai.org/onehtml/docs/css-syntax/), and how stylesheet edits are written and verified is at the end of [Editing](https://recursiveai.org/onehtml/docs/editing/).
