screen-share
Screen sharing components for LiveKit video rooms — toggle button, full-width screen share view with PiP camera overlay, and an auto-switching presenter layout. Use this skill when the user says "add screen share", "setup screen sharing", "presenter mode", "screen share view", or "setup screen-share".
What this skill does
# Screen Share
Screen sharing components that extend the `video-room` and `video-ui` skills with a dedicated toggle button, a full-width screen share view with picture-in-picture camera overlay, and an auto-switching presenter layout that detects when someone is sharing their screen and swaps between grid mode and presenter mode.
## Prerequisites
- Next.js app with `src/` directory and App Router
- `video-room` skill installed (LiveKit client, server SDK, types)
- `video-ui` skill installed (VideoRoomProvider, ParticipantGrid, ControlsBar, ParticipantTile)
- No additional packages needed — uses `livekit-client` and `@livekit/components-react` already installed by `video-room` and `video-ui`
## Installation
No additional packages required. All dependencies are already installed by the `video-room` and `video-ui` skills:
```bash
# Already installed:
# livekit-client
# @livekit/components-react
# @livekit/components-styles
```
## What Gets Created
```
src/
├── components/
│ └── video/
│ ├── screen-share-button.tsx # Toggle button using localParticipant.setScreenShareEnabled
│ ├── screen-share-view.tsx # Full-width screen share track + PiP camera overlay
│ └── presenter-layout.tsx # Auto-switches between grid and presenter mode
└── lib/
└── video/
└── use-screen-share.ts # Hook: isScreenSharing, screenShareTrack, toggleScreenShare, screenShareParticipant
```
## Setup Steps
### Step 1: Create `src/lib/video/use-screen-share.ts`
```typescript
"use client";
import { useState, useCallback, useMemo } from "react";
import {
useTracks,
useLocalParticipant,
} from "@livekit/components-react";
import { Track, type RemoteTrackPublication, type LocalTrackPublication } from "livekit-client";
import type { TrackReferenceOrPlaceholder } from "@livekit/components-react";
type ScreenShareParticipantInfo = {
sid: string;
identity: string;
name: string;
isLocal: boolean;
};
type UseScreenShareReturn = {
/** Whether anyone in the room is currently sharing their screen */
isScreenSharing: boolean;
/** Whether the local participant is the one sharing */
isLocalScreenSharing: boolean;
/** The screen share track reference, or null if no one is sharing */
screenShareTrack: TrackReferenceOrPlaceholder | null;
/** Information about the participant who is sharing, or null */
screenShareParticipant: ScreenShareParticipantInfo | null;
/** Toggle screen sharing for the local participant */
toggleScreenShare: () => Promise<void>;
/** All screen share tracks (in case multiple participants share simultaneously) */
allScreenShareTracks: TrackReferenceOrPlaceholder[];
};
export function useScreenShare(): UseScreenShareReturn {
const { localParticipant } = useLocalParticipant();
const [isToggling, setIsToggling] = useState(false);
const screenShareTracks = useTracks(
[{ source: Track.Source.ScreenShare, withPlaceholder: false }],
{ onlySubscribed: false }
);
const primaryScreenShare = screenShareTracks.length > 0 ? screenShareTracks[0] : null;
const isScreenSharing = screenShareTracks.length > 0;
const isLocalScreenSharing = localParticipant.isScreenShareEnabled;
const screenShareParticipant = useMemo((): ScreenShareParticipantInfo | null => {
if (!primaryScreenShare) return null;
const participant = primaryScreenShare.participant;
return {
sid: participant.sid,
identity: participant.identity,
name: participant.name ?? participant.identity,
isLocal: participant.sid === localParticipant.sid,
};
}, [primaryScreenShare, localParticipant.sid]);
const toggleScreenShare = useCallback(async () => {
if (isToggling) return;
setIsToggling(true);
try {
await localParticipant.setScreenShareEnabled(
!localParticipant.isScreenShareEnabled
);
} finally {
setIsToggling(false);
}
}, [localParticipant, isToggling]);
return {
isScreenSharing,
isLocalScreenSharing,
screenShareTrack: primaryScreenShare,
screenShareParticipant,
toggleScreenShare,
allScreenShareTracks: screenShareTracks,
};
}
```
### Step 2: Create `src/components/video/screen-share-button.tsx`
```tsx
"use client";
import { Button } from "@/components/ui/button";
import { useScreenShare } from "@/lib/video/use-screen-share";
type ScreenShareButtonProps = {
className?: string;
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link";
size?: "default" | "sm" | "lg" | "icon";
};
export function ScreenShareButton({
className,
variant,
size = "sm",
}: ScreenShareButtonProps) {
const { isLocalScreenSharing, toggleScreenShare } = useScreenShare();
const resolvedVariant = variant ?? (isLocalScreenSharing ? "default" : "secondary");
return (
<Button
variant={resolvedVariant}
size={size}
onClick={toggleScreenShare}
className={className}
title={isLocalScreenSharing ? "Stop sharing screen" : "Share your screen"}
>
<ScreenShareIcon className="mr-2 h-4 w-4" />
{isLocalScreenSharing ? "Stop Sharing" : "Share Screen"}
</Button>
);
}
function ScreenShareIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M13 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-3" />
<path d="M8 21h8" />
<path d="M12 17v4" />
<path d="m17 8 5-5" />
<path d="M17 3h5v5" />
</svg>
);
}
```
### Step 3: Create `src/components/video/screen-share-view.tsx`
```tsx
"use client";
import { useRef, useEffect, useId } from "react";
import { useTracks, useLocalParticipant } from "@livekit/components-react";
import { Track } from "livekit-client";
import type { TrackReferenceOrPlaceholder } from "@livekit/components-react";
import { Card } from "@/components/ui/card";
type ScreenShareViewProps = {
/** The screen share track to render. If omitted, auto-detects from room. */
screenShareTrackRef?: TrackReferenceOrPlaceholder | null;
/** Whether to show the PiP camera overlay */
showPiP?: boolean;
className?: string;
};
export function ScreenShareView({
screenShareTrackRef,
showPiP = true,
className,
}: ScreenShareViewProps) {
const pipId = useId();
const screenVideoRef = useRef<HTMLVideoElement>(null);
const pipVideoRef = useRef<HTMLVideoElement>(null);
const { localParticipant } = useLocalParticipant();
// Auto-detect screen share if not provided
const screenShareTracks = useTracks(
[{ source: Track.Source.ScreenShare, withPlaceholder: false }],
{ onlySubscribed: false }
);
const activeScreenShare = screenShareTrackRef ?? (screenShareTracks.length > 0 ? screenShareTracks[0] : null);
// Get camera tracks for PiP overlay
const cameraTracks = useTracks(
[{ source: Track.Source.Camera, withPlaceholder: false }],
{ onlySubscribed: false }
);
// Find the screen share presenter's camera track for PiP
const presenterCameraTrack = activeScreenShare
? cameraTracks.find(
(t) => t.participant.sid === activeScreenShare.participant.sid
)
: null;
// Attach screen share video
useEffect(() => {
const videoEl = screenVideoRef.current;
const track = activeScreenShare?.publication?.track;
if (!videoEl || !track) return;
track.attach(videoEl);
return () => {
track.detach(videoEl);
};
}, [activeScreenShare]);
// Attach PiP camera video
useEffect(() => {
const videoEl = pipVideoRef.current;
const track = presenterCameraTrack?.publication?.track;
if (!videoEl || !track) return;
track.attach(videoEl);
return () => {
track.detach(videoEl);
};
}, [presenterCameraTrack]);
if (!activeScreenShare) {
return null;
}
Related in Image & Video
watch
IncludedWatch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or Whisper API fallback), and hands the result to Claude so it can answer questions about what's in the video.
physical-ai-defect-image-generation
IncludedUse when the user wants to orchestrate defect image generation, run associated setup, or handle outputs on OSMO. The Day 0 path handles cold-start with USD-to-ROI, image-edit augmentation, and AnomalyGen to create initial PCBA datasets. The Day 1 path performs inference and labeling on real images. This skill helps with first-time asset setup, creation of finetuning checkpoints, and configuring deployment. Trigger keywords: defect image generation, dig workflow, dig pipeline, defect image detection workflow, aoi pipeline, aoi anomalygen, usd2roi anomalygen, day 0 pcba, day 1 pcba, day 1 real-photo alignment, day 1 manual roi, metal surface anomaly, glass defect, anomalygen finetune, setup_pcb, setup_metal, setup_glass, setup_pretrained, dig setup, dig datasets, dig pretrained checkpoint, dig image-edit endpoint.
accelint-react-best-practices
IncludedReact performance optimization and best practices. ALWAYS use this skill when working with any React code - writing components, hooks, JSX; refactoring; optimizing re-renders, memoization, state management; reviewing for performance; fixing hydration mismatches; debugging infinite re-renders, stale closures, input focus loss, animations restarting; preventing remounting; implementing transitions, lazy initialization, effect dependencies. Even simple React tasks benefit from these patterns. Covers React 19+ (useEffectEvent, Activity, ref props). Triggers - useEffect, useState, useMemo, useCallback, memo, inline components, nested components, components inside components, re-render, performance, hydration, SSR, Next.js, useDeferredValue, combined hooks.
elevenlabs-agents
IncludedBuild conversational AI voice agents with ElevenLabs Platform using React, JavaScript, React Native, or Swift SDKs. Configure agents, tools (client/server/MCP), RAG knowledge bases, multi-voice, and Scribe real-time STT. Use when: building voice chat interfaces, implementing AI phone agents with Twilio, configuring agent workflows or tools, adding RAG knowledge bases, testing with CLI "agents as code", or troubleshooting deprecated @11labs packages, Android audio cutoff, CSP violations, dynamic variables, or WebRTC config. Keywords: ElevenLabs Agents, ElevenLabs voice agents, AI voice agents, conversational AI, @elevenlabs/react, @elevenlabs/client, @elevenlabs/react-native, @elevenlabs/elevenlabs-js, @elevenlabs/agents-cli, elevenlabs SDK, voice AI, TTS, text-to-speech, ASR, speech recognition, turn-taking model, WebRTC voice, WebSocket voice, ElevenLabs conversation, agent system prompt, agent tools, agent knowledge base, RAG voice agents, multi-voice agents, pronunciation dictionary, voice speed control, elevenlabs scribe, @11labs deprecated, Android audio cutoff, CSP violation elevenlabs, dynamic variables elevenlabs, case-sensitive tool names, webhook authentication
humanizer
IncludedHumanize AI-generated text by detecting and removing patterns typical of LLM output. Rewrites text to sound natural, specific, and human. Uses 28 pattern detectors, 560+ AI vocabulary terms across 3 tiers, and statistical analysis (burstiness, type-token ratio, readability) for comprehensive detection. Use when asked to humanize text, de-AI writing, make content sound more natural/human, review writing for AI patterns, score text for AI detection, or improve AI-generated drafts. Covers content, language, style, communication, and filler categories.
generating-mermaid-diagrams
IncludedSalesforce architecture diagrams using Mermaid with ASCII fallback. Use this skill when generating text-based diagrams for Salesforce architecture, OAuth flows, ERDs, integration sequences, or Agentforce structure. TRIGGER when: user says "diagram", "visualize", "ERD", or asks for sequence diagrams, flowcharts, class diagrams, or architecture visualizations in Mermaid. DO NOT TRIGGER when: user wants PNG/SVG image output (use generating-visual-diagrams), or asks about non-Salesforce systems.