Skip to content

Grammar

cssdoc's doc-comment syntax is defined by a formal, RFC-style specification written in grammarkdown. It's organized as a lexical grammar — characters, comment framing, tokens, and names — building toward a syntactic grammar: the doc comment, its block tags, and the CssReference used inside inline {@link} and {@inheritDoc} tags.

The grammar lives in @cssdoc/spec (alongside the canonical tag vocabulary) and is the source of truth for the shape of a doc comment. The runtime parser in @cssdoc/core is hand-written to conform to these productions, and a test validates the spec on every run, so the two can't drift. grammarkdown is notation and validation only — it doesn't generate a parser.

grammarkdown
// CssDoc — the cssdoc doc-comment grammar.
//
// Notation: `::` marks a lexical production, `:` a syntactic one. Terminals are in backticks; `> …`
// lines are prose (character classes and layout-sensitive constructs outside context-free notation).
// Productions read bottom-up: primitives first, building to the DocComment goal symbol last.

// ============================================================================================
// Lexical Grammar
// ============================================================================================

SourceCharacter ::
    > any Unicode code point

WhiteSpace ::
    > a space or a horizontal tab

LineTerminator ::
    > a line feed or a carriage return

// --- Comment framing ---------------------------------------------------------------------------
// The comment framing is stripped before the tags are parsed.

CommentOpen ::
    `/**`

CommentClose ::
    `*/`

LinePrefix ::
    WhiteSpace? `*` WhiteSpace?

// --- Tokens ------------------------------------------------------------------------------------

BlockSigil ::
    `@`

InlineOpen ::
    `{@`

InlineClose ::
    `}`

// The head/description separator on a tag line: an em dash or a hyphen, surrounded by whitespace.
Separator :: one of
    `—` `-`

Digit :: one of
    `0` `1` `2` `3` `4` `5` `6` `7` `8` `9`

Letter ::
    > an ASCII letter

// --- Names -------------------------------------------------------------------------------------

IdentifierStart ::
    Letter
    `_`

IdentifierPart ::
    IdentifierStart
    Digit
    `-`

Identifier ::
    IdentifierStart
    Identifier IdentifierPart

// A record name, e.g. `button` or `progress-circle`. A qualified name (`menu.item`) names a member
// nested under a dotted-off parent — see MemberOfTag.
RecordName ::
    Identifier
    RecordName `.` Identifier

// How a modifier is spelled is configurable. Three structural forms are recognized,
// shown with their default separators and a neutral `.base` component class;
// every separator and prefix below is configurable:
//   suffix    — joined into the base class name:            `.base--name`  (the default, below)
//   chained   — a separate prefixed class on the base:      `.base.-name`  (optionally split prop/value)
//   attribute — an attribute selector on the base:          `.base[name="value"]`

// A modifier name, in whichever of the three structural spellings the convention uses (below). This is
// what `@modifier` names and what `{@link}`/`{@inheritDoc}` reference; the modifier set itself is derived
// from the CSS, so this only supplies the reference key.
ModifierName ::
    SuffixModifierName
    ChainedModifierName
    AttributeModifier

// The suffix form (the default): the modifier joined into the base class name with a separator. The tail
// is a `Value`, so it may be a `*` family — `.button--primary`, `.button--icon-arrow`, `.button--size-*`.
SuffixModifierName ::
    Identifier `--` Value

// The chained form: a separate prefixed class on the base — a `-<prop>` boolean modifier, or a
// `-<prop>-<value>` modifier split into prop and value. `Prop` is the segment up to the first interior
// hyphen; `Value` is the remainder (which may itself contain hyphens, or be a `*` family — `-icon-*`).
ChainedModifierName ::
    `-` Prop
    `-` Prop `-` Value

Prop ::
    > an identifier segment of letters and digits, containing no hyphen

// A modifier value: a literal value, or a `*` family standing in for the varying value, optionally with a
// literal prefix — `secondary`, `*`, `icon-*`. Reused as the suffix-form tail (SuffixModifierName).
Value ::
    Identifier
    WildcardValue

WildcardValue ::
    WildcardSegment
    Identifier WildcardSegment

// A `*` wildcard marks a modifier/part *family*, standing in for the varying segment — e.g. `-icon-*`.
// Reached through `Value` (suffix/chained names) and `PartName`; the `class`-attribute operator forms
// (`[class*="-icon-"]`, `[class$=…]`, `[class~=…]`) denote the same families. `name-not-in-css` treats a
// family as satisfied by a literal instance (`.-icon-foo`) or such a `class` selector.
WildcardSegment ::
    `*`

// The attribute form: an attribute selector on the base, e.g. `.base[name="value"]` (CUBE), or a
// `class`-attribute family selector — `.base[class*="-icon-"]` denotes the `-icon-*` WildcardSegment
// family.
AttributeModifier ::
    `[` AttributeName `]`
    `[` AttributeName AttributeOperator AttributeValue `]`

AttributeName ::
    Identifier

// A CSS attribute-selector comparison operator: `=` exact; `~=` whitespace-list member; `|=` hyphen
// prefix; `^=` prefix; `$=` suffix; `*=` substring. cssdoc maps `*=`/`|=` to a prefix family, `$=` to a
// suffix family, `~=`/`=` to a concrete name; `^=` anchors to the base class and isn't a modifier (see
// modifier.ts classAttrFamily).
AttributeOperator :: one of
    `=` `~=` `|=` `^=` `$=` `*=`

AttributeValue ::
    QuotedString
    Identifier

QuotedString ::
    `"` StringCharacters `"`
    `'` StringCharacters `'`

StringCharacters ::
    > one or more SourceCharacters other than the enclosing quote

// Two non-modifier roles a convention may name. An element is joined into the base class with `__`,
// e.g. `.base__part`, and is recorded as a PART. A state class is a class chained to the base whose
// name starts with a state prefix, e.g. `.base.is-open`, and is recorded as a STATE. Neither is a
// modifier.
ElementName ::
    Identifier `__` Identifier

StateClass ::
    > a class chained to the base whose name starts with a state prefix

// A `.`-prefixed sub-element class, e.g. `.part`, or a `*` family — `.icon-*`.
PartName ::
    `.` Identifier
    `.` Identifier WildcardSegment

// A non-class part selector: an attribute selector, an ID, or a shadow-DOM pseudo (:host).
// `:host-context(…)` (host in a specific ancestor context) is also accepted — the bracket-aware
// parser reads the full token until the first unbracketed whitespace.
NonClassPartSelector ::
    AttributeModifier
    `#` Identifier
    `:host`

// A user-supplied alias that overrides the derived part name, e.g. `@part [data-layout] container`.
PartAlias ::
    Identifier

// The base selector for a component: a class, attribute selector, ID, or :host pseudo.
// An explicit @selector value prevents the class from being inferred from the CSS rules.
// Compound selectors (e.g. `[class*="x"][data-layout="y"]`) and `:host-context(…)` are also accepted;
// the bracket-aware parser reads until the first unbracketed whitespace.
CssSelectorArgument ::
    ClassSelector
    NonClassPartSelector

// A base-class selector, e.g. `.base`.
ClassSelector ::
    `.` Identifier

// A `--`-prefixed custom property (`@property`) or custom function (`@function`) name.
CustomPropertyName ::
    `--` Identifier

// The `<…>` CSS syntax descriptor on a `@cssproperty`/`@property` line, e.g. `<number>`.
SyntaxDescriptor ::
    `<` SyntaxBody `>`

// A syntax component name from the CSS Properties and Values API supported-names list.
SyntaxBody :: one of
    `angle` `color` `custom-ident` `image` `integer` `length` `length-percentage` `number`
    `percentage` `resolution` `string` `time` `transform-function` `transform-list` `url`

// ============================================================================================
// Syntactic Grammar
// ============================================================================================

// --- Descriptions & inline tags ----------------------------------------------------------------

// The target of an inline reference: a modifier, a part, or a record.
CssReference :
    ModifierName
    PartName
    RecordName

LinkTag :
    InlineOpen `link` WhiteSpace CssReference InlineClose

InheritDocTag :
    InlineOpen `inheritDoc` WhiteSpace CssReference InlineClose

LabelTag :
    InlineOpen `label` WhiteSpace Identifier InlineClose

InlineTag :
    LinkTag
    InheritDocTag
    LabelTag

TextRun ::
    > a run of prose containing no inline tag

DescriptionAtom :
    TextRun
    InlineTag

// Free-text prose that may interleave inline tags.
Description :
    DescriptionAtom
    Description DescriptionAtom

// --- Record-opening tags (choose the CssRecordKind; `@name` is an alias for `@component`) --------

RecordKeyword :
    `@component`
    `@name`
    `@utility`
    `@rule`
    `@declaration`

RecordTag :
    RecordKeyword WhiteSpace RecordName

// --- Prose tags --------------------------------------------------------------------------------

// `@selector` explicitly declares the component's base CSS selector when it isn't a plain class —
// accepts any simple selector: class (`.button`), attribute (`[data-layout="x"]`), ID (`#foo`),
// compound (`[class*="x"][data-layout="y"]`), or shadow-DOM pseudo (`:host`, `:host-context(…)`).
// `@class` is a deprecated alias for `@selector` with identical semantics.
SelectorTag :
    `@selector` WhiteSpace CssSelectorArgument

ClassTag :
    `@class` WhiteSpace CssSelectorArgument

SummaryTag :
    `@summary` WhiteSpace Description

RemarksTag :
    `@remarks` WhiteSpace Description

PrivateRemarksTag :
    `@privateRemarks` WhiteSpace Description

SeeTag :
    `@see` WhiteSpace Description

SinceTag :
    `@since` WhiteSpace Description

GroupKeyword :
    `@group`
    `@category`

GroupTag :
    GroupKeyword WhiteSpace Description

DefaultValueTag :
    `@defaultValue` WhiteSpace Description

DeprecatedTag :
    `@deprecated`
    `@deprecated` WhiteSpace Description

ExampleBody :
    > verbatim example text, which may span multiple lines

ExampleTag :
    `@example` ExampleBody

// --- CSS-surface tags (existing + Custom Elements Manifest) ------------------------------------

// `@deprecated` on a modifier line: free-text guidance and/or a `{@link -canonical}` replacement.
InlineDeprecation :
    `@deprecated`
    `@deprecated` WhiteSpace Description

ModifierBody :
    Description
    InlineDeprecation

// A modifier; the list itself is derived from the CSS, this supplies prose (or an inline deprecation).
ModifierTag :
    `@modifier` WhiteSpace ModifierName
    `@modifier` WhiteSpace ModifierName Separator ModifierBody

// A part can now name any simple selector, not only class names. An optional alias overrides the
// derived name (first attribute key, bare ID/host name, or stripped class name).
PartTag :
    `@part` WhiteSpace PartName
    `@part` WhiteSpace PartName Separator Description
    `@part` WhiteSpace NonClassPartSelector
    `@part` WhiteSpace NonClassPartSelector Separator Description
    `@part` WhiteSpace NonClassPartSelector WhiteSpace PartAlias Separator Description

// A shadow-DOM exposed part (`::part(name)`), named by a bare identifier, e.g. `@csspart header`.
ShadowPartName :
    Identifier

CssPartTag :
    `@csspart` WhiteSpace ShadowPartName
    `@csspart` WhiteSpace ShadowPartName Separator Description

CssPropertyKeyword :
    `@cssproperty`
    `@property`

// A registered custom property; the registration is derived from the `@property` at-rule, this adds prose.
CssPropertyTag :
    CssPropertyKeyword WhiteSpace CustomPropertyName
    CssPropertyKeyword WhiteSpace CustomPropertyName SyntaxDescriptor
    CssPropertyKeyword WhiteSpace CustomPropertyName Separator Description
    CssPropertyKeyword WhiteSpace CustomPropertyName SyntaxDescriptor Separator Description

// A design token the component consumes (`var(--name)`); the set is derived from the CSS, this adds prose.
TokensTag :
    `@tokens` WhiteSpace CustomPropertyName
    `@tokens` WhiteSpace CustomPropertyName Separator Description

// A state name: a bare identifier (a custom `:state(name)` state), a `:`-prefixed native
// pseudo-class state (e.g. `:disabled`), or a bracketed attribute-reflected state (e.g.
// `[aria-sort="ascending"]`) for ARIA/data-* states that aren't expressed as a pseudo-class.
StateName :
    Identifier
    `:` Identifier
    AttributeModifier

CssStateTag :
    `@cssstate` WhiteSpace StateName
    `@cssstate` WhiteSpace StateName Separator Description

SlotName :
    Identifier

SlotTag :
    `@slot` WhiteSpace SlotName
    `@slot` WhiteSpace SlotName Separator Description

// --- CSSOM at-rule surfaces (derived from the CSS; prose optional) -----------------------------

// A CSS custom function (`@function --name`), derived from the `@function` at-rule.
CssFunctionTag :
    `@function` WhiteSpace CustomPropertyName
    `@function` WhiteSpace CustomPropertyName Separator Description

AnimationName :
    Identifier

KeyframesKeyword :
    `@keyframes`
    `@animation`

// An animation the component exposes (`@keyframes`/`@animation`), derived from the `@keyframes` at-rule.
KeyframesTag :
    KeyframesKeyword WhiteSpace AnimationName
    KeyframesKeyword WhiteSpace AnimationName Separator Description

LayerName ::
    > a cascade-layer name, optionally dotted, e.g. theme.dark

// A cascade layer (`@layer`), derived from `@layer` names.
LayerTag :
    `@layer` WhiteSpace LayerName
    `@layer` WhiteSpace LayerName Separator Description

// A container-query surface (`@container`), derived from `@container` / `container-name`.
ContainerTag :
    `@container` WhiteSpace Description

// A feature-query dependency (`@supports`), derived from `@supports` conditions.
SupportsTag :
    `@supports` WhiteSpace Description

MediaKeyword :
    `@media`
    `@responsive`

// Responsive behavior (`@media`/`@responsive`), derived from `@media` conditions.
MediaTag :
    MediaKeyword WhiteSpace Description

AccessibilityKeyword :
    `@a11y`
    `@accessibility`

// Accessibility guidance.
AccessibilityTag :
    AccessibilityKeyword WhiteSpace Description

// --- Structure & demo --------------------------------------------------------------------------

// Nested CSS: each rule's selector is a node, and the rules nested inside it are its children. A
// node's selector is a full compound/complex selector, so `:has()` (contains), `:is()` or a selector
// list (one-of), and `:not()` (not) express relationships between the related parts.
// `@scope (…) { … }` at-rules are accepted as scope-boundary nodes (selector is empty; the prelude
// holds the scope argument). Other CSS at-rules (@media, @layer, etc.) inside the body are forwarded
// to the parser but treated as unknown and silently omitted.
Selector ::
    > a CSS compound or complex selector

// `@scope (…) { … }` at-rules inside a @structure body create scope-boundary nodes (StructureNode
// with scope set to the prelude). The parser recognises @scope as CSS content, not a cssdoc tag.
StructureNode :
    Selector `{` StructureNodeList? `}`

StructureNodeList :
    StructureNode
    StructureNodeList StructureNode

StructureTag :
    `@structure` StructureNodeList
    `@structure` Description StructureNodeList

Provider ::
    Identifier

Ref ::
    > a provider-specific reference token

Url ::
    > an absolute URL

DemoSpec :
    Url
    Provider `:` Ref
    `self` `:` RecordName

DemoTag :
    `@demo` WhiteSpace DemoSpec

// --- Usage, compatibility & related ------------------------------------------------------------

// How to include the stylesheet / use the component.
UsageTag :
    `@usage` WhiteSpace Description

// A browser-support or feature-compatibility note.
CompatTag :
    `@compat` WhiteSpace Description

// A related component cross-reference, named by its record name.
RelatedTag :
    `@related` WhiteSpace RecordName
    `@related` WhiteSpace RecordName Separator Description

// Declared family membership: distinct from `@structure` containment (fixed position) and from
// `@related` (loose "see also") — feeds the parent's Subcomponents section even when this record
// isn't nested in the parent's own `@structure`. A trailing `private` keyword (named after
// TypeScript's own scoping keyword) means this record must only ever appear inside that parent.
MemberOfTag :
    `@memberOf` WhiteSpace RecordName
    `@memberOf` WhiteSpace RecordName WhiteSpace `private`

// Parent-side member declaration (repeatable): `@member child` (or `@member child private`).
// Inside `@structure`, `@member child` is treated as local structure content (not a new block tag).
MemberTag :
    `@member` WhiteSpace RecordName
    `@member` WhiteSpace RecordName WhiteSpace `private`

// --- Modifier (flag) tags: release stage / traits; presence means true ------------------------

ModifierFlagTag :
    `@alpha`
    `@beta`
    `@experimental`
    `@internal`
    `@public`

// --- Custom tags: registered by the host tooling; unregistered tags are ignored ---------------

CustomTagContent ::
    > tag-specific content, interpreted per the tag's configured syntax kind

CustomTag :
    BlockSigil Identifier
    BlockSigil Identifier WhiteSpace CustomTagContent

// --- The doc comment ---------------------------------------------------------------------------

BlockTag :
    RecordTag
    SelectorTag
    ClassTag
    SummaryTag
    RemarksTag
    PrivateRemarksTag
    ModifierTag
    PartTag
    CssPartTag
    CssPropertyTag
    TokensTag
    CssStateTag
    CssFunctionTag
    KeyframesTag
    LayerTag
    ContainerTag
    SupportsTag
    MediaTag
    SlotTag
    AccessibilityTag
    StructureTag
    ExampleTag
    DemoTag
    UsageTag
    CompatTag
    RelatedTag
    MemberOfTag
    MemberTag
    DeprecatedTag
    SeeTag
    SinceTag
    GroupTag
    DefaultValueTag
    ModifierFlagTag
    CustomTag

TagList :
    [empty]
    TagList BlockTag

// A doc comment is a sequence of block tags. Leading untagged prose is ignored; each `@tag` opens a
// block that runs until the next *known* cssdoc `@tag` (so multi-line @example / @summary bodies are
// captured). Inside a `@structure` block, CSS at-rules whose names are not cssdoc tags (e.g. `@scope`,
// `@media`, `@layer`) are treated as CSS content, not new block openers.
DocComment :
    CommentOpen TagList CommentClose

Released under the MIT License