---
title: 'Write guides for Video.js'
description: 'A guide on writing documentation, and a test bed for all our MDX components'
---

import FrameworkCase from '@/components/docs/FrameworkCase.astro';
import StyleCase from '@/components/docs/StyleCase.astro';
import MinimalFrame from '@/components/frames/Minimal.astro';
import ServerCode from '@/components/Code/ServerCode.astro';
import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs.tsx';
import DocsLink from '@/components/docs/DocsLink.astro';
import Aside from '@/components/Aside.astro';
import Demo from '@/components/docs/demos/Demo.astro';
import BasicUsageDemoReact from '@/components/docs/demos/play-button/react/css/BasicUsage';
import basicUsageReactTsx from '@/components/docs/demos/play-button/react/css/BasicUsage.tsx?raw';
import basicUsageReactCss from '@/components/docs/demos/play-button/react/css/BasicUsage.css?raw';
import BasicUsageDemoHtml from '@/components/docs/demos/play-button/html/css/BasicUsage.astro';
import basicUsageHtml from '@/components/docs/demos/play-button/html/css/BasicUsage.html?raw';
import basicUsageHtmlCss from '@/components/docs/demos/play-button/html/css/BasicUsage.css?raw';
import basicUsageHtmlTs from '@/components/docs/demos/play-button/html/css/BasicUsage.ts?raw';

What is a guide? In this context, it's a document in the docs that's not an API reference. For API references, see <DocsLink slug="reference/write-references">Write reference pages</DocsLink>.

<Aside type="tip">
Oh hey by the way. A lot of this knowledge is encoded in the `docs` Claude skill. You should still read it, but, now your LLM can help you along the way, too.
</Aside>

## 1. Understand our documentation structure

First, read and understand [Diátaxis](https://diataxis.fr/). We organize documentation into three modes:

1. **Concept pages** (`src/content/docs/concepts/`): Explain how and why things work — design decisions, trade-offs, and context. General understanding, spanning multiple APIs, can be applied to multiple outcomes. As we write guides, what are things we need people to understand in multiple places and don’t want to duplicate the content? Diátaxis calls this mode "explanation."
2. **How-to guides** (`src/content/docs/how-to/`): Spans multiple concepts in order to achieve a specific outcome with step-by-step instructions. Address a real user goal, assume competence, and link to concept pages instead of explaining inline.
3. **Reference pages** (`src/content/docs/reference/`): Component API documentation. These are scaffolded using `write-api-reference` and the api-docs-builder. Again, see <DocsLink slug="reference/write-references">Write reference pages</DocsLink> for details.

<Aside type="note">
Diátaxis has a fourth mode — learning-oriented **tutorials**. We don't have a tutorials section yet — something we've intentionally chosen to omit, for now. Learning-oriented content lives in the Getting started how-to guides instead.
</Aside>

To pick a mode, use the Diátaxis [compass](https://diataxis.fr/compass/). Ask two questions: does the content inform **action** (practical steps) or **cognition** (understanding)? And does it serve the reader's **study** (acquiring skills) or their **work** (applying them)?

| | Informs action | Informs cognition |
|---|---|---|
| **Serves work** | How-to guide | Reference page |
| **Serves study** | How-to guide (Getting started) | Concept page |

**When in doubt, write a concept page.** Most new documentation should be concept pages. Use this table to decide between the two:

| Concept page | How-to guide |
|---|---|
| **Preferred** — default choice | Use sparingly |
| Reference while working | Learning from scratch |
| One concept per page | Multi-step narrative |
| Scannable, minimal prose | Explains "why" at each step |
| No prerequisites | Has prerequisites |
| Jump in anywhere | Sequential |

Whichever mode you choose, **don't mix modes on one page**. Step-by-step instructions creeping into a concept page belong in a how-to guide; background explanation swelling a how-to step belongs in a concept page — link to it instead. Each mode serves a different reader need, and blending them dilutes both.

## 2. Create a guide in the correct content folder

Our guides are [MDX](https://mdxjs.com) files placed in the matching directory:

- Concept pages go in `src/content/docs/concepts/[slug].mdx`
- How-to guides go in `src/content/docs/how-to/[slug].mdx`

Every guide needs a frontmatter block with `title` and `description`:

```yaml
---
title: 'Your guide title'
description: 'One-sentence summary for search and metadata'
---
```

Optional frontmatter fields:

- **`frameworkTitle`** — override the title for specific frameworks (e.g., `frameworkTitle: { react: 'Hooks in React' }`)
- **`ogTitle`** — shorter title for the default `/og/...` and `/og/twitter/...` social preview images. Use this when your page title is too long for the rendered OG card (over ~80 characters).

## 3. Add that guide to the sidebar

Next, open `src/docs.config.ts` and add your guide to the appropriate section of the sidebar. For example, to add a how-to guide on "Writing guides", you would add:

```ts
{
  slug: 'how-to/write-guides'
}
```

The sidebar label defaults to the guide's `title` frontmatter; override it with `sidebarLabel`. If you want the guide to only apply to specific frameworks, you can specify that as well:

```ts
{
  slug: 'how-to/write-guides',
  sidebarLabel: 'Writing guides',
  frameworks: ['react'] // Only for React
}
```

<Aside type="note">
There is no `styles` restriction on sidebar items. Style is a client-side preference — every guide renders for all styles. To show style-specific content, use `<StyleCase>` within the guide, as shown below.
</Aside>

## 4. Follow our writing style

Keep documentation clear, human, and useful. Here are the key rules:

- **Sentence case for headings** — capitalize only the first word and proper nouns (e.g., "Choose your JS framework", not "Choose Your JS Framework")
- **Active voice, second person** — speak directly to the reader with "you"
- **Collaborative pronouns** — use "we," "us," and "our" when talking about the project or team
- **Avoid gerunds in headings** — prefer "Write good headings" over "Writing good headings"
- **Oxford comma** — always use the serial comma in lists of three or more
- **Gender-neutral language** — use "they" or "their" instead of "his" or "her"
- **Cut filler words** — remove "In order to," "basically," "simply," "might," "could," "perhaps"
- **Be precise** — make claims as strong as possible without becoming false; avoid vague qualifiers like "somewhat" or "fairly"
- **Read it out loud** — if it sounds awkward, rewrite it

## 5. Understand MDX and our components

### `<FrameworkCase>` and `<StyleCase>`
First, understand that the guide you write will be rendered for every framework / style combination (e.g., HTML + CSS, React + CSS) unless you restrict it to specific frameworks in the sidebar config as shown above.

Use the `<FrameworkCase>` and `<StyleCase>` components to conditionally show content to just one framework or style:

```mdx
<FrameworkCase frameworks={["react"]}>
  React-only content
</FrameworkCase>
```


<FrameworkCase frameworks={["react"]}>
  React-only content
</FrameworkCase>

<Aside type="note">
If you see nothing, that means you probably don't have React selected as your framework. Cool! It's working!
</Aside>

Use the `<StyleCase>` component to show content only for specific styling approaches. For example,

```mdx
<StyleCase styles={["css"]}>
  CSS-only content
</StyleCase>
```


<StyleCase styles={["css"]}>
  CSS-only content
</StyleCase>

<Aside type="note">
If you see nothing, that means you probably don't have CSS selected as your style. Cool! It's working!
</Aside>

## Use Github-Flavored Markdown

### Headings

Don't use H1 (or in markdown, `# H1`). We already have an H1 at the top of the page. Do, however, check out H2 (`## H2`) through H6 (`###### H6`).


## H2 with `code`

### H3 with `code`

#### H4 with `code`

##### H5 with `code`

###### H6 with `code`

### Paragraph

Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.

Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.

### Images

```mdx
![alt text](../path/to/local/or/remote/image)
```

![All four Video.js themes](../../../assets/blog/2019-09-13-announcing-the-new-videojs-com/collage.png)

### Blockquotes

The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.

```markdown
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.
```

> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.

### Footnotes

```markdown
> Don't communicate by sharing memory, share memory by communicating.<br/>
> — <cite>Rob Pike[^1]</cite>
```

> Don't communicate by sharing memory, share memory by communicating.<br/>
> — <cite>Rob Pike[^1]</cite>

[^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015.

### Tables

```markdown
| Italics   | Bold     | Code   |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
```


| Italics   | Bold     | Code   |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |

When a table is really wide, it overflows nicely on mobile

| A | Table | With | A | Lot | Of | Columns |
| --- | --- | --- | --- | --- | --- | --- |
| contentcontentcontent | contentcontentcontent | contentcontentcontent | contentcontentcontent | contentcontentcontent | contentcontentcontent | contentcontentcontent |

### Code blocks

we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash

````markdown
```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Example HTML5 Document</title>
  </head>
  <body>
    <p>Test</p>
  </body>
</html>
```
````


```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Example HTML5 Document</title>
  </head>
  <body>
    <p>Test</p>
  </body>
</html>
```

#### Code frames and titles

Every standalone code block is automatically wrapped in a `<CodeFrame>` — the tabbed header and copy button you see on the examples above. You don't write it yourself; the `satteriCodeFrame` MDX plugin injects it. Code blocks inside an authored `<TabsPanel>` are left alone, since the tab group is already their frame.

The frame's tab shows the language by default. To show a filename instead, add `title="..."` to the fence meta:

````markdown
```ts title="App.ts"
console.log('Hello, Video.js!');
```
````

Which renders as:

```ts title="App.ts"
console.log('Hello, Video.js!');
```

### Code annotations

You can annotate code blocks with special comments to highlight, diff, focus, or word-highlight specific lines. The annotation comments are stripped from the rendered output (and from copy/paste).

#### Diff

Show added and removed lines with `// [!code ++]` and `// [!code --]`:

```ts
function greet(name: string) {
  console.log('Hello') // [!code --]
  console.log(`Hello, ${name}!`) // [!code ++]
  console.log(`It's great to meet you.`) // [!code ++]
}
```

#### Focus

Dim everything except the focused lines with `// [!code focus]`. Hovering the code block reveals all lines:

```ts
function greet(name: string) {
  const message = `hello, ${name}!`
  console.log(message) // [!code focus]
}
```

#### Word highlight

Highlight a specific word or phrase with `// [!code word:term]`. For example, `// [!code word:name]` looks like this:

```ts
// [!code word:name]
function greet(name: string) {
  console.log(`hello, ${name}!`)
}
```

### List types

#### Ordered list

```markdown
1. First item
2. Second item
3. Third item
```

1. First item
2. Second item
3. Third item

#### Unordered list

```markdown
- List item
- Another item
- And another item
```

- List item
- Another item
- And another item

#### Nested list

```markdown
- Fruit
  - Apple
  - Orange
  - Banana
- Dairy
  - Milk
  - Cheese
```

Which looks like:

- Fruit
  - Apple
  - Orange
  - Banana
- Dairy
  - Milk
  - Cheese

### HTML elements, like abbr, sub, sup, kbd, mark

You can also write raw HTML within your markdown. This is useful for elements like...

```markdown
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.

H<sub>2</sub>O

X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>

Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.

Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
```

Which would output:

<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.

H<sub>2</sub>O

X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>

Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.

Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.


## Other custom components
### Asides / Callouts / Admonitions

Use the `<Aside>` component to highlight important information. It supports four types: `note`, `tip`, `caution`, and `danger`.

```mdx
{/* or type="tip" or "caution" or "danger" */}
<Aside type="note" title="Custom Title">
Your content. You can use **markdown** in here, too.
</Aside>
```

Which will give you...

<Aside type="note" title="Custom Title">
Your content. You can use **markdown** in here, too.
</Aside>

<Aside type="tip">
Your content. You can use **markdown** in here, too.
</Aside>

<Aside type="caution">
Your content. You can use **markdown** in here, too.
</Aside>

<Aside type="danger">
Of course it works with longer content as well. Even things that have multiple elements!

The elements have a gross Tailwind class that manages their margins which makes them tighter in this context. Neat!
</Aside>

### Tabs

Use `<TabsRoot>`, `<TabsList>`, `<Tab>`, and `<TabsPanel>` to show multiple items side-by-side.

<Aside type="note">
- Set `initial` on your first `<Tab>` and first `<TabsPanel>` to make them active by default
- All `<Tab*>` components require `client:idle` — do NOT use `client:visible` because hidden panels won't trigger the Intersection Observer and will never hydrate
- Use descriptive `label` prop on `<TabsList>` for accessibility
</Aside>


````mdx
<TabsRoot client:idle>
  <TabsList client:idle label="Code examples">
    <Tab client:idle value="typescript" initial>TypeScript</Tab>
    <Tab client:idle value="javascript">JavaScript</Tab>
  </TabsList>
  <TabsPanel client:idle value="typescript" initial>
    ```ts
    console.log('Hello, TypeScript!');
    ```
  </TabsPanel>
  <TabsPanel client:idle value="javascript">
    ```js
    console.log('Hello, JavaScript!');
    ```
  </TabsPanel>
</TabsRoot>
````


<TabsRoot client:idle>
  <TabsList client:idle label="Code examples">
    <Tab client:idle value="typescript" initial>TypeScript</Tab>
    <Tab client:idle value="javascript">JavaScript</Tab>
  </TabsList>
  <TabsPanel client:idle value="typescript" initial>
    ```ts
    console.log('Hello, TypeScript!');
    ```
  </TabsPanel>
  <TabsPanel client:idle value="javascript">
    ```js
    console.log('Hello, JavaScript!');
    ```
  </TabsPanel>
</TabsRoot>


### `<ServerCode />`, for showing code from a `{variable}` or `?raw` imported file

While MDX supports inline variables like `{someVariable}`, this doesn't work inside of markdown code blocks.
If you tried
````mdx
```tsx
{someVariable}
```
````
You would just get
```tsx
{someVariable}
```

To work around this, use `<ServerCode />` instead of triple backticks.

<Aside type="caution">
Unlike regular markdown code blocks, which are automatically wrapped in a `<CodeFrame>` as described above, `<ServerCode>` renders a bare `<pre>` with **no** frame. You should use `TabsRoot` with a single `TabsPanel` to make it look pretty.
</Aside>

```mdx
import componentCode from '@/examples/react/Component.tsx?raw';
import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs';
import ServerCode from '@/components/Code/ServerCode.astro';

<TabsRoot client:idle>
  <TabsList client:idle label="Component implementation">
    <Tab client:idle value="component" initial>Component</Tab>
  </TabsList>
  <TabsPanel client:idle value="component" initial>
    <ServerCode code={componentCode} lang="tsx" />
  </TabsPanel>
</TabsRoot>
```

Which will happily render

<TabsRoot client:idle>
  <TabsList client:idle label="Component implementation">
    <Tab client:idle value="component" initial>Component</Tab>
  </TabsList>
  <TabsPanel client:idle value="component" initial>
    <ServerCode code={`import React from 'react';

function Component() {
  return <div>Hello, world!</div>;
}`} lang="tsx" />
  </TabsPanel>
</TabsRoot>

### `<Demo>`, for a live preview with tabbed source code

Use `<Demo>` to show a live, interactive example alongside the code that produces it. This is the standard pattern on reference pages (e.g., <DocsLink slug="reference/play-button">PlayButton</DocsLink>).

Pass the source files via the `files` prop — each entry has a `title`, `code` (a `?raw` import), and `lang` — and place the rendered demo in the default slot. Any `css` file in `files` is automatically scoped to the demo, so the demo's styles won't leak into the page.

Demos are framework-specific, so wrap each variant in `<FrameworkCase>` (and `<StyleCase>`, if applicable):

```mdx
import Demo from '@/components/docs/demos/Demo.astro';
import BasicUsageDemoReact from '@/components/docs/demos/play-button/react/css/BasicUsage';
import basicUsageReactTsx from '@/components/docs/demos/play-button/react/css/BasicUsage.tsx?raw';
import basicUsageReactCss from '@/components/docs/demos/play-button/react/css/BasicUsage.css?raw';

<FrameworkCase frameworks={["react"]}>
  <StyleCase styles={["css"]}>
    <Demo files={[
      { title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
      { title: "App.css", code: basicUsageReactCss, lang: "css" },
    ]}>
      <BasicUsageDemoReact client:idle />
    </Demo>
  </StyleCase>
</FrameworkCase>
```

Which renders as:

<FrameworkCase frameworks={["react"]}>
  <StyleCase styles={["css"]}>
    <Demo files={[
      { title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
      { title: "App.css", code: basicUsageReactCss, lang: "css" },
    ]}>
      <BasicUsageDemoReact client:idle />
    </Demo>
  </StyleCase>
</FrameworkCase>

<FrameworkCase frameworks={["html"]}>
  <StyleCase styles={["css"]}>
    <Demo files={[
      { title: "index.html", code: basicUsageHtml, lang: "html" },
      { title: "index.css", code: basicUsageHtmlCss, lang: "css" },
      { title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
    ]}>
      <BasicUsageDemoHtml />
    </Demo>
  </StyleCase>
</FrameworkCase>

Agent-assisted changes should follow the demo rules in `site/AGENTS.md`; all contributors should follow neighboring demos for the current file structure.

## Literally any other component

This is just MDX, so you can import any old component and use it. To make that imported component look pretty, you might consider wrapping it in `<MinimalFrame>` or placing it inside of a `<TabsRoot>` alongside code, as shown below.


### `<MinimalFrame>` to limit max width and provide a nice border

Use the `<MinimalFrame>` frame component to wrap standalone demos with a styled border and background:

```mdx
import { MyDemo } from '@/examples/react/MyDemo';
import MinimalFrame from '@/components/frames/Minimal.astro';
<MinimalFrame>
  <MyDemo client:idle />
</MinimalFrame>
```

#### Using `iframe`s in `<MinimalFrame>`

Imo `<MinimalFrame>` is a great tool for laying out `iframe`s. If you go down this path, note that `<MinimalFrame>` has `position: relative;`, which means that you can (and should) absolutely position your `iframe` within it. For example...

```mdx
<MinimalFrame class="aspect-video">
  <iframe class="absolute top-0 left-0 w-full h-full" src="https://jsfiddle.net/ugnkw65y/1/embedded/result,html/light" frameBorder="0" allowFullScreen />
</MinimalFrame>
```

<MinimalFrame class="aspect-video">
  <iframe class="absolute top-0 left-0 w-full h-full" src="https://jsfiddle.net/ugnkw65y/1/embedded/result,html/light" frameBorder="0" allowFullScreen />
</MinimalFrame>

### `<TabsRoot>` supports arbitrary content, too

You can place any old content underneath `<TabsPanel>`, and it'll show alongside whatever tabbed content you're showing. This is a pattern we've used so far to display code alongside a rendered example (e.g., <DocsLink slug="reference/play-button">PlayButton</DocsLink>).

```mdx
import { MyDemo } from '@/examples/react/MyDemo';
import componentCode from '@/examples/react/MyDemo.tsx?raw';
import cssCode from '@/examples/react/MyDemo.module.css?raw';
import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs';
import ServerCode from '@/components/Code/ServerCode.astro';

<TabsRoot client:idle>
  <TabsList client:idle label="MyDemo implementation">
    <Tab client:idle value="component" initial>Component</Tab>
    <Tab client:idle value="css">CSS Module</Tab>
  </TabsList>
  <TabsPanel client:idle value="component" initial>
    <ServerCode code={componentCode} lang="tsx" />
  </TabsPanel>
  <TabsPanel client:idle value="css">
    <ServerCode code={cssCode} lang="css" />
  </TabsPanel>
  <MyDemo client:idle />
</TabsRoot>
```
