# Video Recording Embed

> A section embedding a recorded talk video alongside its title and context. Lets visitors watch you present without ever leaving your portfolio.

A shadcn/ui block from the [@shadcn-ui-blocks](https://www.shadcn-ui-blocks.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcn-ui-blocks/portfolio-portfolio-event-speaking-and-workshops-video-recording-embed).

- Page, with a live preview: https://shadcn.exchange/r/shadcn-ui-blocks/portfolio-portfolio-event-speaking-and-workshops-video-recording-embed
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcn-ui-blocks/portfolio-portfolio-event-speaking-and-workshops-video-recording-embed-light-1354d79648c1f3f1.png
- Registry item: https://www.shadcn-ui-blocks.com/r/portfolio-portfolio-event-speaking-and-workshops-video-recording-embed.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.shadcn-ui-blocks.com/r/portfolio-portfolio-event-speaking-and-workshops-video-recording-embed.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `class-variance-authority`, `lucide-react`, `radix-ui`

## Files

### `components/blocks/portfolio/portfolio-event-speaking-and-workshops/video-recording-embed.tsx`

```tsx
import {
  ArrowRight,
  PlayCircle,
  Calendar,
  MapPin,
  Clock,
  Download,
  Mic,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

// Sample featured recordings data
const featuredRecordings = [
  {
    id: 1,
    title: 'AI-Driven Interfaces',
    event: 'UXConf 2024',
    role: 'Keynote Speaker',
    date: 'April 18, 2024',
    location: 'San Francisco, CA',
    duration: '45 min',
    description:
      'In this keynote presentation, I explored how artificial intelligence is revolutionizing the way we approach web design, from automated layouts to personalized user experiences. Drawing on case studies and real-world examples, I demonstrated how designers can embrace AI as a powerful collaborator rather than viewing it as a replacement.',
    videoId: '8WXN9M79ZYA', // YouTube video ID (AI in Web Design talk)
    thumbnailImage:
      'https://images.unsplash.com/photo-1587825140708-dfaf72ae4b04?q=80&w=2070&auto=format&fit=crop',
    slides: '#', // Link to slides
    resources: '#', // Link to additional resources
    tags: ['AI', 'Web Design', 'UX', 'Future Trends'],
  },
  {
    id: 2,
    title: 'Designs That Scale',
    event: 'DesignOps Global',
    role: 'Workshop Leader',
    date: 'September 18, 2023',
    location: 'Berlin, Germany',
    duration: '90 min',
    description:
      'In this hands-on workshop, I guided participants through the process of creating, implementing, and maintaining design systems for enterprise organizations. We covered everything from component architecture to team workflows, documentation, and scaling strategies.',
    videoId: 'f1x-g5uTt1E', // YouTube video ID
    thumbnailImage:
      'https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070&auto=format&fit=crop',
    slides: '#', // Link to slides
    resources: '#', // Link to additional resources
    tags: ['Design Systems', 'DesignOps', 'Enterprise', 'Workflow'],
  },
  {
    id: 3,
    title: 'Building Accessible Applications',
    event: 'A11y Summit',
    role: 'Panel Moderator',
    date: 'November 12, 2023',
    location: 'Chicago, IL',
    duration: '60 min',
    description:
      'I moderated this panel discussion with leading accessibility experts, exploring best practices for building truly accessible digital products from the ground up. The conversation covered technical implementation, inclusive design, testing methodologies, and organizational approaches to accessibility.',
    videoId: 'ZYSceVMX3-U', // YouTube video ID
    thumbnailImage:
      'https://images.unsplash.com/photo-1505373877841-8d25f7d46678?q=80&w=2112&auto=format&fit=crop',
    slides: '#', // Link to slides
    resources: '#', // Link to additional resources
    tags: ['Accessibility', 'WCAG', 'Inclusive Design', 'Panel Discussion'],
  },
];

export default function VideoRecordingEmbed() {
  return (
    <section className="bg-background py-16 md:py-24">
      <div className="container mx-auto px-4 2xl:max-w-[1400px]">
        {/* Section header */}
        <div className="mb-12 text-center md:mb-16">
          <Badge className="mb-4" variant="outline">
            Watch & Learn
          </Badge>
          <h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
            Speaking Recordings
          </h2>
          <p className="text-muted-foreground mx-auto mt-4 max-w-2xl text-lg">
            Watch recordings of my keynotes, workshops, and panel discussions
            from events around the world
          </p>
        </div>

        <div className="mx-auto max-w-5xl">
          <Tabs
            className="space-y-8"
            defaultValue={featuredRecordings[0]!.id.toString()}
          >
            <TabsList className="h-fit w-full flex-wrap">
              {featuredRecordings.map((recording) => (
                <TabsTrigger
                  key={recording.id}
                  value={recording.id.toString()}
                  className="data-[state=active]:bg-background rounded-md px-3 py-2"
                >
                  <div className="text-center">
                    <h3 className="text-sm font-medium md:text-base">
                      {recording.title}
                    </h3>
                    <p className="text-muted-foreground text-xs">
                      {recording.event}
                    </p>
                  </div>
                </TabsTrigger>
              ))}
            </TabsList>

            {featuredRecordings.map((recording) => (
              <TabsContent
                key={recording.id}
                value={recording.id.toString()}
                className="mt-6"
              >
                <div className="bg-card overflow-hidden rounded-xl border shadow-sm">
                  {/* Video embed */}
                  <div className="aspect-video w-full bg-black">
                    <iframe
                      width="100%"
                      height="100%"
                      src={`https://www.youtube.com/embed/${recording.videoId}`}
                      title={recording.title}
                      frameBorder="0"
                      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                      allowFullScreen
                    ></iframe>
                  </div>

                  {/* Content */}
                  <div className="p-6 md:p-8">
                    <div className="flex flex-wrap items-start justify-between gap-4">
                      <div>
                        <Badge className="mb-2" variant="outline">
                          {recording.role}
                        </Badge>
                        <h3 className="mb-1 text-xl font-semibold md:text-2xl">
                          {recording.title}
                        </h3>
                        <p className="text-primary font-medium">
                          {recording.event}
                        </p>
                      </div>
                      <div className="flex space-x-2">
                        <Button variant="outline" size="sm" asChild>
                          <a
                            href={recording.slides}
                            className="inline-flex items-center gap-1"
                          >
                            <Download className="h-3.5 w-3.5" />
                            <span>Slides</span>
                          </a>
                        </Button>
                        <Button variant="outline" size="sm" asChild>
                          <a
                            href={recording.resources}
                            className="inline-flex items-center gap-1"
                          >
                            <Download className="h-3.5 w-3.5" />
                            <span>Resources</span>
                          </a>
                        </Button>
                      </div>
                    </div>

                    <div className="mt-6 mb-6 grid grid-cols-3 gap-4">
                      <div className="flex flex-col">
                        <div className="text-muted-foreground mb-1 flex items-center text-xs">
                          <Calendar className="mr-1.5 h-3.5 w-3.5" />
                          Date
                        </div>
                        <div className="text-sm font-medium">
                          {recording.date}
                        </div>
                      </div>
                      <div className="flex flex-col">
                        <div className="text-muted-foreground mb-1 flex items-center text-xs">
                          <MapPin className="mr-1.5 h-3.5 w-3.5" />
                          Location
                        </div>
                        <div className="text-sm font-medium">
                          {recording.location}
                        </div>
                      </div>
                      <div className="flex flex-col">
                        <div className="text-muted-foreground mb-1 flex items-center text-xs">
                          <Clock className="mr-1.5 h-3.5 w-3.5" />
                          Duration
                        </div>
                        <div className="text-sm font-medium">
                          {recording.duration}
                        </div>
                      </div>
                    </div>

                    <div className="mb-6">
                      <h4 className="mb-2 text-lg font-medium">
                        About this talk
                      </h4>
                      <p className="text-muted-foreground text-sm">
                        {recording.description}
                      </p>
                    </div>

                    <div className="flex flex-wrap gap-1.5">
                      {recording.tags.map((tag) => (
                        <Badge
                          key={tag}
                          variant="secondary"
                          className="text-xs"
                        >
                          {tag}
                        </Badge>
                      ))}
                    </div>
                  </div>
                </div>
              </TabsContent>
            ))}
          </Tabs>

          {/* Other recordings preview */}
          <div className="mt-16">
            <div className="mb-6 flex items-center justify-between">
              <h3 className="text-2xl font-semibold">More Recordings</h3>
              <Button variant="outline" asChild size="sm">
                <a href="#" className="inline-flex items-center gap-1">
                  <span>View all</span>
                  <ArrowRight className="h-3.5 w-3.5" />
                </a>
              </Button>
            </div>

            <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
              {[1, 2, 3].map((index) => (
                <div
                  key={`more-${index}`}
                  className="group overflow-hidden rounded-lg border transition-all duration-300 hover:shadow-md"
                >
                  <div className="relative aspect-video w-full overflow-hidden">
                    <img
                      src={featuredRecordings[index - 1]!.thumbnailImage}
                      alt={featuredRecordings[index - 1]!.title}
                      className="object-cover transition-transform duration-500 group-hover:scale-105"
                    />
                    <div className="absolute inset-0 flex items-center justify-center bg-black/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
                      <PlayCircle className="h-12 w-12 text-white" />
                    </div>
                  </div>
                  <div className="p-4">
                    <h4 className="mb-1 line-clamp-1 text-sm font-medium">
                      {featuredRecordings[index - 1]!.title}
                    </h4>
                    <p className="text-muted-foreground text-xs">
                      {featuredRecordings[index - 1]!.event}
                    </p>
                  </div>
                </div>
              ))}
            </div>
          </div>

          {/* CTA */}
          <div className="mt-12 text-center">
            <Button asChild>
              <a href="#" className="inline-flex items-center gap-2">
                <Mic className="h-4 w-4" />
                <span>Book me for your next event</span>
              </a>
            </Button>
          </div>
        </div>
      </div>
    </section>
  );
}
```

### `components/ui/button.tsx`

```tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/80",
        outline:
          "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
        ghost:
          "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
        destructive:
          "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default:
          "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
        sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
        lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        icon: "size-9",
        "icon-xs":
          "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
        "icon-sm":
          "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
        "icon-lg": "size-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant = "default",
  size = "default",
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean
  }) {
  const Comp = asChild ? Slot.Root : "button"

  return (
    <Comp
      data-slot="button"
      data-variant={variant}
      data-size={size}
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { Button, buttonVariants }
```

### `components/ui/badge.tsx`

```tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
        secondary:
          "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
        destructive:
          "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
        outline:
          "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
        ghost:
          "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function Badge({
  className,
  variant = "default",
  asChild = false,
  ...props
}: React.ComponentProps<"span"> &
  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
  const Comp = asChild ? Slot.Root : "span"

  return (
    <Comp
      data-slot="badge"
      data-variant={variant}
      className={cn(badgeVariants({ variant }), className)}
      {...props}
    />
  )
}

export { Badge, badgeVariants }
```

### `components/ui/tabs.tsx`

```tsx
"use client"

import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Tabs as TabsPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"

function Tabs({
  className,
  orientation = "horizontal",
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
  return (
    <TabsPrimitive.Root
      data-slot="tabs"
      data-orientation={orientation}
      className={cn(
        "group/tabs flex gap-2 data-horizontal:flex-col",
        className
      )}
      {...props}
    />
  )
}

const tabsListVariants = cva(
  "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
  {
    variants: {
      variant: {
        default: "bg-muted",
        line: "gap-1 bg-transparent",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function TabsList({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<typeof TabsPrimitive.List> &
  VariantProps<typeof tabsListVariants>) {
  return (
    <TabsPrimitive.List
      data-slot="tabs-list"
      data-variant={variant}
      className={cn(tabsListVariants({ variant }), className)}
      {...props}
    />
  )
}

function TabsTrigger({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
  return (
    <TabsPrimitive.Trigger
      data-slot="tabs-trigger"
      className={cn(
        "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
        "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
        "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
        className
      )}
      {...props}
    />
  )
}

function TabsContent({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
  return (
    <TabsPrimitive.Content
      data-slot="tabs-content"
      className={cn("flex-1 text-sm outline-none", className)}
      {...props}
    />
  )
}

export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
```
