Home
Content

Components

Rich components in plain markdown. No JSX, no imports, just directives.

Sourcey renders directives in standard markdown. The ::: syntax is processed at build time; no JavaScript ships for static content. Every component on this page is rendered using the syntax it documents.

To see all of these components used in a real project, check the Cheese Store demo and its source on GitHub.

Callouts

Four types for different contexts. Add text after the type to override the default title.

Note

Use note for supplementary information that supports the surrounding content.

Warning

Use warning for things that will break, cost money, or lose data if ignored.

Tip

Use tip for shortcuts, best practices, or non-obvious techniques.

Info

Use info for context that helps the reader understand why something works the way it does.

Custom Title

Add text after the type to set a custom title. This callout uses :::note Custom Title.

Syntax:

:::note
Content here.
:::

:::warning Custom Title
Content with a custom title.
:::

Available types: note, warning, tip, info.

Steps

Numbered processes with a visual connector line. Write a standard ordered list inside :::steps.

1

Write the directive

Open with :::steps on its own line.

2

Add numbered items

Use a standard markdown ordered list. The first line is the step title; subsequent indented lines are the content.

3

Close it

End with ::: on its own line.

Syntax:

:::steps
1. Step title
   Step content goes here. Indent by 3 spaces.
2. Another step
   More content.
:::

Tabs

Tabbed content panels. Each tab is a ::tab child with a title attribute.

Content for the first tab. This tab is active by default.

Content for the second tab. Click to reveal.

Tabs work for any content: text, code blocks, lists, images.

Syntax:

:::tabs
::tab{title="Tab Name"}
Content here.
::
::tab{title="Another Tab"}
More content.
::
:::

Code Group

Tabbed code blocks with syntax highlighting. Wrap fenced code blocks in :::code-group. Each block needs a title attribute.

const res = await fetch('https://api.example.com/items');
const data = await res.json();
import requests
res = requests.get('https://api.example.com/items')
data = res.json()
curl -s 'https://api.example.com/items' | jq

Syntax:

:::code-group
```javascript title="Node.js"
const res = await fetch('https://api.example.com/items');
```
```python title="Python"
import requests
res = requests.get('https://api.example.com/items')
```
:::

Cards

Grid of linked or static cards with icons. Wrap ::card children in :::card-group. The cols attribute controls the grid (2, 3, or 4 columns on desktop; always 1 on mobile).

With a link

Cards with an href attribute are clickable and navigate to that page.

With an icon

Cards without href are static. Use them to highlight features or concepts.

Minimal

Only title is required. Icon and href are optional.

Two-column variant:

Left card

Two-column layouts work well for binary choices or before/after comparisons.

Right card

The grid is responsive. On mobile, cards always stack vertically.

Syntax:

:::card-group{cols="3"}
::card{title="Card Title" icon="book" href="/path"}
Card content.
::
::card{title="Another Card" icon="lock"}
More content.
::
:::

Attributes:

  • card-group: cols (number of columns, default 2)
  • card: title (required), icon (optional), href (optional)

Accordion

Expandable sections using the HTML <details> element. Consecutive accordions are automatically grouped.

Is this rendered with details/summary?

Yes. Each accordion is a native <details> element with a styled <summary>. No JavaScript required for the expand/collapse behavior.

Do consecutive accordions group automatically?

They do. Place multiple :::accordion blocks one after another and sourcey wraps them in a visual group with shared borders.

Can I have a single accordion?

Absolutely. A single :::accordion renders standalone without group styling.

**Syntax:**
:::accordion{title="Question or section title"}
Answer or content here.
:::

Place multiple accordions consecutively for automatic grouping.

Video

Embed YouTube, Vimeo, or raw video files. YouTube URLs are rewritten to youtube-nocookie.com for privacy.

Syntax:

::video[https://www.youtube.com/watch?v=VIDEO_ID]{title="Optional title"}

::video[https://vimeo.com/123456789]{title="Vimeo video"}

::video[https://example.com/video.mp4]{title="Self-hosted video"}

YouTube and Vimeo URLs render as iframes. Raw .mp4 and .webm URLs render as native <video> elements.

Iframe

Embed any external page. Only https:// and http:// URLs are allowed.

Syntax:

::iframe[https://example.com]{title="Example embed" height="400"}

Attributes:

  • title (optional) — accessible label for the iframe
  • height (optional, default 400) — height in pixels

JSX syntax

Sourcey also accepts JSX-style component syntax. These are preprocessed to directives internally, so the output is identical.

<Steps>
  <Step title="First">Content</Step>
  <Step title="Second">Content</Step>
</Steps>

<Tabs>
  <Tab title="First">Content</Tab>
  <Tab title="Second">Content</Tab>
</Tabs>

<CodeGroup>
  (fenced code blocks with title attributes)
</CodeGroup>

<CardGroup cols={2}>
  <Card title="Title" icon="lock" href="/path">Content</Card>
</CardGroup>

<AccordionGroup>
  <Accordion title="Question">Answer</Accordion>
</AccordionGroup>

<Note>Informational callout</Note>
<Warning title="Careful">Warning with custom title</Warning>
<Tip>Helpful suggestion</Tip>
<Info>Contextual information</Info>

<Video src="https://www.youtube.com/watch?v=ID" title="Video title" />

<Iframe src="https://example.com" title="Embed" height="500" />
Info

Directive syntax (:::) is the recommended format. JSX syntax exists for compatibility and convenience; both produce the same HTML output.

Warning

Nesting directives inside other directives is not supported. Each directive block must be a top-level element in your markdown.