Skip to content

docs(site): add ejected skin section to installation guide#1524

Open
ronald-urbina wants to merge 11 commits into
mainfrom
docs/add-ejected-skin-to-install-guide
Open

docs(site): add ejected skin section to installation guide#1524
ronald-urbina wants to merge 11 commits into
mainfrom
docs/add-ejected-skin-to-install-guide

Conversation

@ronald-urbina

@ronald-urbina ronald-urbina commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Closes 1385

Adds a new "Ejecting a skin" section to the installation guide so users
can access the ejected skin workflow without going back to the homepage.

Changes

  • New EjectedSkinToggle component with a default/minimal radio switcher
    that shows the correct ejected-skin install steps per variant
  • installation.mdx wired up with the toggle and a teaser link near the
    skin picker section

Note

Medium Risk
Touches shared installation codegen used by the site and CLI bundle; incorrect ejected JSON or placeholder resolution would break copy-paste install paths, but behavior is heavily tested and background-video is explicitly guarded.

Overview
Adds packaged vs ejected as a first-class installation choice so generated snippets either use pre-built skins or copy ejected skin source into the project.

The installation guide gets an embed method picker (hidden for background-video), and HTML/React code blocks switch to multi-file output (index.html / index.ts / skin.css or MyPlayer.tsx + skin.css) when ejected is selected. Shared codegen in codegen.ts branches on embedMethod, loading snippets via new ejected.ts from ejected-skins.json; background-video always stays packaged.

The ejected-skins build now emits placeholder HTML ({{SRC}}, {{POSTER}}, {{MEDIA_TAG}}) with separate cdnScript / cdnStylesheet fields instead of baking CDN tags into the template. Docs EjectedSkin.astro resolves placeholders for display.

@videojs/cli docs how-to/installation gains --embed-method, interactive prompting for embed method, and inference of background-video from --media without extra prompts. CLI bundles alias ejected.ts and formats ejected output consistently with the site.

Reviewed by Cursor Bugbot for commit 5dce5fa. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented May 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jun 17, 2026 3:05pm

Request Review

@netlify

netlify Bot commented May 7, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 5dce5fa
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a32b78702dddf00086f0a5c
😎 Deploy Preview https://deploy-preview-1524--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html — no changes
Presets (7)
Entry Size
/video (default) 41.80 kB
/video (default + hls) 181.22 kB
/video (minimal) 41.46 kB
/video (minimal + hls) 181.08 kB
/audio (default) 35.62 kB
/audio (minimal) 32.88 kB
/background 4.22 kB
Media (9)
Entry Size
/media/background-video 1.07 kB
/media/container 1.72 kB
/media/dash-video 242.65 kB
/media/hls-video 141.12 kB
/media/mux-audio 163.61 kB
/media/mux-video 163.61 kB
/media/native-hls-video 8.94 kB
/media/simple-hls-audio-only 16.04 kB
/media/simple-hls-video 17.83 kB
Players (5)
Entry Size
/video/player 7.61 kB
/audio/player 5.39 kB
/background/player 3.92 kB
/live-video/player 7.63 kB
/live-audio/player 5.40 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 5.23 kB
/video/skin.css css 5.19 kB
/video/minimal-skin js 41.46 kB
/video/minimal-skin.tailwind js 41.95 kB
/video/skin js 41.78 kB
/video/skin.tailwind js 42.21 kB
/audio/minimal-skin.css css 3.45 kB
/audio/skin.css css 3.36 kB
/audio/minimal-skin js 32.92 kB
/audio/minimal-skin.tailwind js 33.22 kB
/audio/skin js 35.70 kB
/audio/skin.tailwind js 36.02 kB
/background/skin.css css 133 B
/background/skin js 1.16 kB
/live-video/minimal-skin.css css 5.23 kB
/live-video/skin.css css 5.19 kB
/live-video/minimal-skin js 40.88 kB
/live-video/minimal-skin.tailwind js 41.25 kB
/live-video/skin js 40.86 kB
/live-video/skin.tailwind js 41.13 kB
/live-audio/minimal-skin.css css 3.45 kB
/live-audio/skin.css css 3.36 kB
/live-audio/minimal-skin js 27.17 kB
/live-audio/minimal-skin.tailwind js 26.64 kB
/live-audio/skin js 29.61 kB
/live-audio/skin.tailwind js 29.19 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.37 kB
UI Components (37)
Entry Size
/ui/airplay-button 2.92 kB
/ui/alert-dialog 1.27 kB
/ui/alert-dialog-close 558 B
/ui/alert-dialog-description 448 B
/ui/alert-dialog-title 450 B
/ui/buffering-indicator 2.79 kB
/ui/captions-button 2.96 kB
/ui/captions-radio-group 2.21 kB
/ui/cast-button 2.87 kB
/ui/compounds 8.22 kB
/ui/controls 2.19 kB
/ui/error-dialog 3.25 kB
/ui/fullscreen-button 2.91 kB
/ui/hotkey 2.04 kB
/ui/menu 5.38 kB
/ui/mute-button 2.96 kB
/ui/pip-button 2.88 kB
/ui/play-button 2.88 kB
/ui/playback-rate-button 2.93 kB
/ui/playback-rate-radio-group 2.18 kB
/ui/popover 2.10 kB
/ui/poster 2.59 kB
/ui/seek-button 2.87 kB
/ui/seek-indicator 3.76 kB
/ui/seek-indicator-value 241 B
/ui/slider 1.50 kB
/ui/status-announcer 3.39 kB
/ui/status-indicator 3.48 kB
/ui/status-indicator-value 272 B
/ui/thumbnail 3.16 kB
/ui/time 1.99 kB
/ui/time-slider 2.90 kB
/ui/tooltip 2.23 kB
/ui/volume-indicator 3.67 kB
/ui/volume-indicator-fill 222 B
/ui/volume-indicator-value 222 B
/ui/volume-slider 3.67 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Size
/video (default) 34.72 kB
/video (default + hls) 172.88 kB
/video (minimal) 34.77 kB
/video (minimal + hls) 173.08 kB
/audio (default) 28.43 kB
/audio (minimal) 28.51 kB
/background 754 B
Media (8)
Entry Size
/media/background-video 575 B
/media/dash-video 241.20 kB
/media/hls-video 139.64 kB
/media/mux-audio 162.30 kB
/media/mux-video 162.33 kB
/media/native-hls-video 7.41 kB
/media/simple-hls-audio-only 14.56 kB
/media/simple-hls-video 16.38 kB
Skins (27)
Entry Type Size
/tailwind.css css 228 B
/video/minimal-skin.css css 5.14 kB
/video/skin.css css 5.10 kB
/video/minimal-skin js 34.69 kB
/video/minimal-skin.tailwind js 40.13 kB
/video/skin js 34.63 kB
/video/skin.tailwind js 39.99 kB
/audio/minimal-skin.css css 3.32 kB
/audio/skin.css css 3.23 kB
/audio/minimal-skin js 28.45 kB
/audio/minimal-skin.tailwind js 28.91 kB
/audio/skin js 28.36 kB
/audio/skin.tailwind js 31.97 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.14 kB
/live-video/skin.css css 5.10 kB
/live-video/minimal-skin js 30.93 kB
/live-video/minimal-skin.tailwind js 36.15 kB
/live-video/skin js 30.88 kB
/live-video/skin.tailwind js 36.14 kB
/live-audio/minimal-skin.css css 3.32 kB
/live-audio/skin.css css 3.23 kB
/live-audio/minimal-skin js 20.94 kB
/live-audio/minimal-skin.tailwind js 23.71 kB
/live-audio/skin js 20.96 kB
/live-audio/skin.tailwind js 23.77 kB
UI Components (31)
Entry Size
/ui/airplay-button 2.87 kB
/ui/alert-dialog 1.21 kB
/ui/buffering-indicator 2.60 kB
/ui/captions-button 2.86 kB
/ui/captions-radio-group 2.58 kB
/ui/cast-button 2.88 kB
/ui/controls 2.52 kB
/ui/error-dialog 2.49 kB
/ui/fullscreen-button 2.83 kB
/ui/gesture 2.05 kB
/ui/hotkey 2.65 kB
/ui/live-button 2.78 kB
/ui/menu 5.53 kB
/ui/mute-button 2.94 kB
/ui/pip-button 2.88 kB
/ui/play-button 2.89 kB
/ui/playback-rate 2.54 kB
/ui/playback-rate-button 2.83 kB
/ui/popover 2.42 kB
/ui/poster 2.43 kB
/ui/seek-button 2.91 kB
/ui/seek-indicator 2.06 kB
/ui/slider 4.38 kB
/ui/status-announcer 1.85 kB
/ui/status-indicator 1.96 kB
/ui/thumbnail 2.76 kB
/ui/time 2.66 kB
/ui/time-slider 3.98 kB
/ui/tooltip 2.63 kB
/ui/volume-indicator 2.05 kB
/ui/volume-slider 3.40 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (11)
Entry Size
. 7.94 kB
/dom 16.40 kB
/dom/media/custom-media-element 2.00 kB
/dom/media/dash 236.79 kB
/dom/media/google-cast 4.04 kB
/dom/media/hls 135.58 kB
/dom/media/media-host 1.31 kB
/dom/media/mux 151.26 kB
/dom/media/native-hls 3.02 kB
/dom/media/simple-hls 15.71 kB
/dom/media/simple-hls-audio-only 13.92 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 2.22 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 192 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Size
. 4.45 kB
/dom 6.33 kB
/hls 14.64 kB
/background-looping-video 12.44 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

Comment thread site/src/content/docs/how-to/installation.mdx Outdated

@decepulis decepulis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the comments I made below, it's important for this guide and npx @videojs/cli docs how-to/installation to stay in sync. You should port the changes you made here to that guide as well, so that when agentic readers hit this guide with --skin minimal, they're served the minimal ejected skin as well.

(Though before you spend too much time on that, I'm also starting to wonder if we should link to the eject guide instead of putting eject instructions here... I asked heff about that here. Sorry for bringing up a design conversation like this up after implementation has already started!)

Update: comment below

}

export default function EjectedSkinToggleClient({ children }: Props) {
const [variant, setVariant] = useState<Variant>('default');

@decepulis decepulis May 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo these should listen to the same nanostore that powers the skin toggle higher up the page.
If you select minimal at the top, you should be viewing the minimal code example at the bottom.

Not relevant in light of the comment below

@decepulis

Copy link
Copy Markdown
Collaborator

@ronald-urbina we talked about this in a meeting today. We're imagining a slightly different shape for this flow.
#1385 (comment)
let me know if you have any questions or just want to talk this through!

Comment thread site/src/content/docs/how-to/installation.mdx Outdated

@decepulis decepulis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still some work to do!

  1. a change to the UI, putting ejected skins in the existing header hierarchy and code blocks rather than replacing it
  2. some broken tests and typechecking
  3. some suggestions about refactoring; more logic should live in codegen.ts

--media <html5-video|html5-audio|hls|background-video>
--install-method <cdn|npm|pnpm|yarn|bun>`;
--install-method <cdn|npm|pnpm|yarn|bun>
--embed-method <packaged|ejected>`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if a user does --embed-method ejected --media background-video? Do we error invalid combos?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ignored as it happens if skin is added into a background-video command

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to have an explicit error to help users understand what's going on!

Comment thread packages/cli/src/utils/format.ts Outdated
@@ -1,3 +1,4 @@
import ejectedSkins from '@/content/ejected-skins.json';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the CLI need to import the generated ejected-skins.json directly? The existing installation CLI path treats packages/cli/src/utils/format.ts as a thin markdown wrapper around the shared site installation generators in site/src/utils/installation/codegen.ts.

This adds a second path for installation codegen where the CLI knows about generated content IDs, the JSON shape, and CSS-style filtering. Could we move the ejected-skin lookup/generation behind the shared installation codegen boundary instead, so the CLI remains a wrapper and the site/CLI do not diverge on how ejected snippets are selected?

This will probably help clear up the typecheck/test errors we're seeing, too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great

Comment on lines -46 to +50
--install-method <cdn|npm|pnpm|yarn|bun>
--install-method <cdn|npm|pnpm|yarn|bun> \
--embed-method <packaged|ejected>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you merge in main, you may have to make a small change to this section here

Comment thread site/src/content/docs/how-to/installation.mdx Outdated
</TabsRoot>
</FrameworkCase>

<EmbedMethodSection>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I'd like to propose a pretty big UX change.

  1. Currently, when you select "Eject", the "create your player [react]" and "use your player" headers disappear. We should bring them back.
  2. Currently, when you select "Eject"...
    • in React, the ejected skin currently replaces "create your player" and "use your player". Instead of that, it might be neater if Skin.tsx and skin.css were additional files in "create your player". So, keep the current ./components/player/index.tsx, but add ./components/player/skin.tsx and ./components/player/skin.css. This will require a rethink of how codegen.ts > generateReactCreateCode works, so it returns multiple files and the front-end shows those multiple files (tabs) and the cli shows those multiple files one-after-the-other
      React mockup
    • in HTML, this could be more simple. Just take the ejected skin you have, and replace the code block under "use your player". Again, the logic should live in codegen.ts, not in format.ts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread site/src/utils/installation/codegen.ts Outdated
Comment thread site/src/utils/installation/codegen.ts
Comment thread packages/cli/src/utils/format.ts
@ronald-urbina ronald-urbina requested a review from decepulis May 25, 2026 15:56
Comment thread site/src/utils/installation/codegen.ts Outdated
Comment thread packages/cli/src/commands/tests/docs.test.ts
Comment thread packages/cli/src/utils/tests/format.test.ts
Comment thread site/src/utils/installation/codegen.ts
Comment thread site/src/utils/installation/codegen.ts
Comment thread packages/cli/src/commands/tests/docs.test.ts Outdated
Comment thread packages/cli/src/commands/docs.ts
Comment thread packages/cli/src/commands/tests/docs.test.ts
Comment thread site/src/components/installation/ReactCreateCodeBlock.tsx Outdated

@decepulis decepulis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot closer! Thanks for addressing my feedback.

Here's a few more items for this round 🙂

In addition to my one review comment... two more things!

General comment

Make sure your tests pass before the next round of review!

UX improvement

Image

This is a bit of an awkward experience.

  1. We should use filenames for all three. index.html, skin.css, and maybe index.ts.
  2. They should be in the same tab container, not two separate ones, at this point. If possible, we should only combine them into the same tab container in the ejected case. In the packaged case, it still makes sense to have them as two separate code blocks

let me know if I can clarify!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried the string replacement approach you're taking here is brittle. If the generated code changes too much, then these utils will change, too. I'd sugest instead maybe making build-ejected-skins output some sort of parameterizable output. Stable strings like {{ SRC }} that can be easily replaced wherever it's needed -- EjectedSkin.astro and codegen -- independently

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already done, now the build script outputs stable placeholder tokens and the consumers just swap them in. No more brittle regex.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5dce5fa. Configure here.

function resolveEjectedPlaceholders(html: string, renderer: Renderer, url: string): string {
const tag = renderer === 'hls' ? 'hls-video' : renderer === 'html5-audio' ? 'audio' : 'video';
return html.replaceAll('{{MEDIA_TAG}}', tag).replaceAll('{{SRC}}', url);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreplaced poster placeholder

Medium Severity

Ejected HTML generation resolves {{SRC}} and {{MEDIA_TAG}} but not {{POSTER}}, so install examples and the site code block can still show a literal {{POSTER}} in poster img tags after the build script switched to that placeholder.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5dce5fa. Configure here.

return html
.replaceAll('{{SRC}}', VJS10_DEMO_VIDEO.mp4)
.replaceAll('{{POSTER}}', VJS10_DEMO_VIDEO.poster)
.replaceAll('{{MEDIA_TAG}}', 'video');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Audio skins show video tag

Medium Severity

resolveHtml always substitutes {{MEDIA_TAG}} with video, so HTML ejected skins for audio (default-audio, minimal-audio) display the wrong media element in the customize-skins and concepts docs.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5dce5fa. Configure here.


const isEjected = $embedMethod === 'ejected';

if (isEjected) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ejected UI ignores background preset

Low Severity

The ejected tab layout keys only on embedMethod === 'ejected', not on use case. After choosing ejected then switching to background video, the picker hides but the block still uses the ejected layout while codegen returns packaged HTML without ejected CSS.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5dce5fa. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs: Add Ejected Skins to the Install Guide

2 participants