Skip to content
Docs
VPN Connection

VPN Connection

See server, speed, and connection status at a glance — with a slide-to-connect control.

requires interactionclick
Loading...

How it works

serverName, latency, and downloadMbps are display-only. Connection state is uncontrolled (defaultConnected) or controlled (connected + onConnectedChange). The toggle is a real role="switch" button with a 44px hit area, not a hover-only chip.

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/widget/vpn-widget.json

Manual

Install dependencies

npm install lucide-react

Run the following command

It will create a new file called vpn-widget.tsx inside the components/animata/widget directory.

mkdir -p components/animata/widget && touch components/animata/widget/vpn-widget.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import { FlagIcon, ShieldBan, ShieldCheck } from "lucide-react";
import { useState } from "react";
 
import { cn } from "@/lib/utils";
 
type SlidingButtonProps = {
  connected: boolean;
  onConnectedChange: (connected: boolean) => void;
  className?: string;
};
 
function SlidingButton({ connected, onConnectedChange, className }: SlidingButtonProps) {
  return (
    <button
      type="button"
      role="switch"
      aria-checked={connected}
      className={cn(
        "touch-manipulation relative flex min-h-11 w-full items-center justify-between overflow-hidden rounded-2xl bg-muted transition-colors",
        className,
      )}
      onClick={() => onConnectedChange(!connected)}
    >
      <span className="flex h-11 w-8 items-center justify-center" aria-hidden>
        <span
          className={cn("size-2.5 rounded-full bg-red-500 transition-opacity", {
            "opacity-0": connected,
            "opacity-100": !connected,
          })}
        />
      </span>
      <span
        className={cn(
          "absolute z-10 flex h-10 w-[8.75rem] flex-col items-center justify-center rounded-[14px] bg-background py-1 shadow-sm transition-[left] duration-200 ease-out",
          connected ? "left-0.5" : "left-[34px]",
        )}
      >
        <span className="text-sm font-semibold text-foreground">
          {connected ? "Connected" : "Disconnected"}
        </span>
        <span
          className={cn("inline-flex items-center gap-1 text-xs font-medium", {
            "text-sky-700 dark:text-sky-400": connected,
            "text-red-700 dark:text-red-400": !connected,
          })}
        >
          {connected ? <ShieldCheck size={12} aria-hidden /> : <ShieldBan size={12} aria-hidden />}
          {connected ? "Secured" : "Not secured"}
        </span>
      </span>
      <span className="flex h-11 w-8 items-center justify-center" aria-hidden>
        <span
          className={cn("size-2.5 rounded-full bg-emerald-500 transition-opacity", {
            "opacity-0": !connected,
            "opacity-100": connected,
          })}
        />
      </span>
    </button>
  );
}
 
export type VpnConnectionProps = {
  className?: string;
  serverName?: string;
  latency?: string;
  downloadMbps?: string;
  defaultConnected?: boolean;
  connected?: boolean;
  onConnectedChange?: (connected: boolean) => void;
};
 
export default function VpnConnection({
  className,
  serverName = "Tokyo",
  latency = "53 ms",
  downloadMbps = "57.4",
  defaultConnected = false,
  connected: controlledConnected,
  onConnectedChange,
}: VpnConnectionProps) {
  const [internalConnected, setInternalConnected] = useState(defaultConnected);
  const connected = controlledConnected ?? internalConnected;
 
  const setConnected = (next: boolean) => {
    if (controlledConnected === undefined) {
      setInternalConnected(next);
    }
    onConnectedChange?.(next);
  };
 
  return (
    <div className={className}>
      <div className="flex size-52 flex-col rounded-3xl border border-sky-900/30 bg-sky-950 p-0.5 shadow-md">
        <div className="flex items-center gap-2 px-3 pb-1.5 pt-3">
          <FlagIcon className="size-5 fill-amber-400 text-amber-400" aria-hidden />
          <p className="truncate text-sm font-semibold tracking-tight text-amber-400">
            {serverName}
          </p>
        </div>
        <div className="flex min-h-0 flex-1 flex-col gap-2 rounded-[22px] bg-background p-3">
          <div className="inline-flex max-w-fit items-center gap-1.5 rounded-lg bg-emerald-500/15 px-2 py-0.5">
            <span className="size-2 animate-pulse rounded-full bg-emerald-500" aria-hidden />
            <span className="text-xs font-medium tabular-nums text-emerald-700 dark:text-emerald-400">
              {latency}
            </span>
          </div>
          <p className="text-3xl font-semibold tabular-nums tracking-tight text-foreground">
            {downloadMbps}
            <span className="ml-0.5 text-sm font-medium text-muted-foreground">Mbps</span>
          </p>
          <div className="mt-auto">
            <SlidingButton connected={connected} onConnectedChange={setConnected} />
          </div>
        </div>
      </div>
    </div>
  );
}

Credits

Built by Chiranjibi Ranabhat yevhenyurchuk.com