BBCode
Renders BBCode as themed HTML over a named, extensible tag vocabulary — as a document or an inline label.
Live demo
BBCode renders forum-style markup into HTML that themes entirely from the token register: quotes and rules ride the border and surface ramps, sizes ride the type scale, and [code] renders a real <xtyle-code> so a code block inside a post and an <xtyle-code> beside it agree in any theme. The roster is phpBB's core set — [b], [i], [u], [url], [img], [email], [quote], [code], [list]/[*], [color], [size] — plus declared extensions where the major dialects agree: [s], [sub]/[sup], [mark], [font], [bg], [spoiler], [noparse], [hr], [br], [h1]–[h6], [table]/[tr]/[td]/[th], and the four alignments.
inline switches to a label render for a chip or a tab title. It ships no sanitizer, and unlike Markdown there is no allowHtml either — BBCode has no raw-HTML passthrough to lift, so everything that is not a registered tag is escaped to text and every tag emits markup the renderer wrote itself, from a bounded set. The two places an author's value still reaches an attribute are handled by name: URLs go through the same scheme allowlist Markdown uses, and style values ([color], [size], [font], [bg]) are matched against narrow patterns rather than escaped, because red;background:url(…) survives escaping intact and is still a second declaration. vocabulary is the interesting attribute. One registry backs every instance and a vocabulary is a named subset of it, so a story body can admit [choice] while an author bio beside it cannot — refusal by construction rather than by filtering afterward. registerBbcodeTags adds tags, defineBbcodeVocabulary composes them into a named set, and an instance picks one by name; a tag outside the set renders as its literal text. The same seam is reachable from Markdown through processBbcode, which composes both languages in one body.
When to use
How this component composes with the rest of the set.
Props
6 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
States
editing
The source view is showing: the body is swapped for the textarea and the toggle reads as pressed.
revealed
An open [spoiler], its body shown beneath the summary.
Anatomy
The named parts that make up the component, with their selectors.
body
Where the rendered BBCode lands. Unlike the Markdown body, every rule here is keyed to the renderer's own class names rather than to element types — which is what makes a single tag reskinnable without touching the rest. Carries data-vocabulary naming the vocabulary the body rendered under, so the choice is visible where the markup landed.
quote
A [quote], marked by a leading accent edge on a raised surface, with its attribution above it.
spoiler
A [spoiler], rendered as a one-section Accordion — so it reveals with no JavaScript, and a mod reshapes it through the Accordion's own fill rather than through a shape this tag invented.
list
A [list], ordered or not. Items are split on [*] rather than closed individually, as BBCode writes them.
table
A [table] with a filled header row and ruled cells.
link
A [url] or [email], on the accent, warming to the body color on hover.
editor
The source textarea shown while editing, in the mono face on the field surface.
toggle
The edit/view switch. Chrome the component invents, so it is a real node in the fragment fill: a mod can reword it, make it an icon, or move it, and the element keeps working.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--bg-1
--fg-0
--fg-1
--fg-2
--font-sans
--leading-normal
--radius-sm
--space-1
--space-2
--space-3
--space-4
--space-6
--text-body
--text-sm
--text-xs
--warn-bg
--warn-text
--weight-bold
--weight-semibold
Slots
The BBCode as text content (html/svelte). For Astro, pass it via the source prop instead.
Accessibility
Code
A document
The block render: emphasis, links, lists and color, themed from the register.
<xtyle-bbcode source="[b]Patch notes[/b]
The [i]tooltip[/i] now tracks its trigger. See [url=/changelog]the changelog[/url].
[list]
[*]fixed placement under scroll
[*][color=accent]Escape[/color] dismisses a hover-raised hint
[/list]"></xtyle-bbcode>
<script lang="ts">
import { Bbcode } from "@xtyle/svelte";
const notes = `[b]Patch notes[/b]
The [i]tooltip[/i] now tracks its trigger. See [url=/changelog]the changelog[/url].`;
</script>
<Bbcode source={notes} />
---
import Bbcode from "@xtyle/astro/Bbcode.astro";
const notes = `[b]Patch notes[/b]
The [i]tooltip[/i] now tracks its trigger.`;
---
<Bbcode source={notes} />
An inline label
The label render, flowing with the text around it and inheriting its type.
<!-- a label, not a document: emphasis renders, blocks stay out of the way -->
<xtyle-bbcode inline source="Fix [b]tooltip[/b] in [color=accent]AnchorTracker[/color]"></xtyle-bbcode>
<script lang="ts">
import { Bbcode } from "@xtyle/svelte";
// a forum-authored title dropped straight into a tab strip
export let title = "Fix [b]tooltip[/b] in [color=accent]AnchorTracker[/color]";
</script>
<Bbcode inline source={title} />
---
import Bbcode from "@xtyle/astro/Bbcode.astro";
---
<Bbcode inline source="Fix [b]tooltip[/b] in [color=accent]AnchorTracker[/color]" />
Quotes, spoilers, and code
[quote] attributes, [spoiler] is a native disclosure, and [code] hands off to the Code component.
<xtyle-bbcode source="[quote=ada]The Analytical Engine weaves algebraic patterns.[/quote]
[spoiler=Ending]She was the engine all along.[/spoiler]
[code=ts]const theme = derive({ bg: '#0f1115' });[/code]"></xtyle-bbcode>
<script lang="ts">
import { Bbcode } from "@xtyle/svelte";
</script>
<Bbcode source={`[quote=ada]The Analytical Engine weaves algebraic patterns.[/quote]
[spoiler=Ending]She was the engine all along.[/spoiler]`} />
---
import Bbcode from "@xtyle/astro/Bbcode.astro";
---
<Bbcode source={`[quote=ada]The Analytical Engine weaves algebraic patterns.[/quote]
[spoiler=Ending]She was the engine all along.[/spoiler]`} />
Two vocabularies, one registry
The same custom [choice] tag is reachable in the story body and inert in the bio, because the bio's vocabulary doesn't list it.
<!-- the story body admits the app's own [choice]; the bio beside it cannot -->
<xtyle-bbcode vocabulary="story" source="You reach the fork. [choice]Go north[/choice]"></xtyle-bbcode>
<xtyle-bbcode vocabulary="bio" source="Writes fiction. [choice]nope[/choice]"></xtyle-bbcode>
<script type="module">
import { registerBbcodeTags, defineBbcodeVocabulary } from "@xtyle/core/elements";
registerBbcodeTags([
{ name: "choice", block: true, render: (c) => `<xtyle-button variant="outline" block>${c.content}</xtyle-button>` },
]);
defineBbcodeVocabulary("story", ["b", "i", "url", "quote", "choice"]);
defineBbcodeVocabulary("bio", ["b", "i", "url"]);
</script>
<script lang="ts">
import { Bbcode } from "@xtyle/svelte";
import { registerBbcodeTags, defineBbcodeVocabulary } from "@xtyle/core/elements";
registerBbcodeTags([
{ name: "choice", block: true, render: (c) => `<xtyle-button variant="outline" block>${c.content}</xtyle-button>` },
]);
defineBbcodeVocabulary("story", ["b", "i", "url", "choice"]);
defineBbcodeVocabulary("bio", ["b", "i", "url"]);
</script>
<Bbcode vocabulary="story" source="You reach the fork. [choice]Go north[/choice]" />
<Bbcode vocabulary="bio" source="Writes fiction. [choice]nope[/choice]" />
---
import Bbcode from "@xtyle/astro/Bbcode.astro";
import { defineBbcodeVocabulary } from "@xtyle/core/elements/bbcode-registry.js";
defineBbcodeVocabulary("bio", ["b", "i", "url"]);
---
<Bbcode vocabulary="bio" source="Writes fiction. [b]Three novels.[/b]" />
Both languages at once
The Markdown component's processBbcode composes the two: the quote comes from BBCode, the emphasis inside it from markdown.
<!-- both languages in one body: the quote is BBCode, the emphasis is markdown -->
<xtyle-markdown process-bbcode source="## Release notes
[quote=ada]It **weaves** algebraic patterns.[/quote]
Written in \`markdown\` with [color=accent]BBCode[/color] alongside."></xtyle-markdown>
<script lang="ts">
import { Markdown } from "@xtyle/svelte";
// forum-shaped input in a markdown document, one vocabulary for both
const body = "## Notes\n\n[quote=ada]It **weaves** patterns.[/quote]";
</script>
<Markdown processBbcode source={body} />
---
import Markdown from "@xtyle/astro/Markdown.astro";
---
<!-- a string names a vocabulary; `true` uses the whole registry -->
<Markdown processBbcode="story" source={"## Notes\n\n[quote=ada]It **weaves** patterns.[/quote]"} />
With a source view
editable adds the toggle the fill draws, emitting input as the source is typed.
<xtyle-bbcode editable source="[b]Draft[/b]
Switch to the source and edit it."></xtyle-bbcode>
<script lang="ts">
import { Bbcode } from "@xtyle/svelte";
let draft = "[b]Draft[/b]\n\nSwitch to the source and edit it.";
</script>
<Bbcode editable source={draft} on:input={(e) => (draft = e.detail.source)} />
---
import Bbcode from "@xtyle/astro/Bbcode.astro";
---
<Bbcode editable source={"[b]Draft[/b]\n\nSwitch to the source and edit it."} />