Generated banner for Frontend UI Dark TS
Generated logo for Frontend UI Dark TS

Frontend UI Dark TS

MicrosoftOfficialCreated by Microsoft

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Build dark-themed React applications using Tailwind CSS with custom theming, glassmorphism effects, and Framer Motion animations. Use when creating dashboards, admin panels, or data-rich interfaces with a refined dark aesthetic.
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured foundation for building dark-themed React applications. It integrates Tailwind CSS for styling, Framer Motion for animations, and TypeScript for type safety. The system includes pre-configured design tokens, reusable UI components, and layout patterns specifically tailored for dashboards and data-rich interfaces.

What it does

  • Provides a pre-configured Vite project structure with React and TypeScript.
  • Implements a comprehensive dark-themed color palette and design tokens.
  • Includes reusable UI components like buttons, inputs, and cards.
  • Offers glassmorphism utility classes for modern visual effects.
  • Integrates Framer Motion for page transitions and interactive animations.
  • Configures mobile-responsive layouts with safe-area support.

When to use it

  • When starting a new React project requiring a refined dark aesthetic.
  • When building dashboards or admin panels that need data-rich visualization support.
  • When you want to implement consistent glassmorphism effects across your UI.
  • When you need a pre-defined set of design tokens and animation patterns.

Advantages

  • Ready-to-use design system with consistent color and spacing scales.
  • Built-in support for touch-friendly mobile interactions.
  • Includes pre-configured PWA manifest and SEO meta tags.
  • Type-safe component architecture using TypeScript.

Usage examples

“How do I add a glass-styled card to my dashboard?”
The agent provides the HTML structure using the .glass-card utility class defined in the global CSS.
“Show me how to implement a button with a loading state.”
The agent provides the Button component code, demonstrating the use of the isLoading prop and the built-in spinner.

Install

Documentation by Microsoft

Frontend UI Dark Theme (TypeScript)

A modern dark-themed React UI system using Tailwind CSS and Framer Motion. Designed for dashboards, admin panels, and data-rich applications with glassmorphism effects and tasteful animations.

Stack

PackageVersionPurpose
react^18.xUI framework
react-dom^18.xDOM rendering
react-router-dom^6.xRouting
framer-motion^11.xAnimations
clsx^2.xClass merging
tailwindcss^3.xStyling
vite^5.xBuild tool
typescript^5.xType safety

Quick Start

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install framer-motion clsx react-router-dom
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Project Structure

public/
├── favicon.ico                    # Classic favicon (32x32)
├── favicon.svg                    # Modern SVG favicon
├── apple-touch-icon.png           # iOS home screen (180x180)
├── og-image.png                   # Social sharing image (1200x630)
└── site.webmanifest               # PWA manifest
src/
├── assets/
│   └── fonts/
│       ├── Segoe UI.ttf
│       ├── Segoe UI Bold.ttf
│       ├── Segoe UI Italic.ttf
│       └── Segoe UI Bold Italic.ttf
├── components/
│   ├── ui/
│   │   ├── Button.tsx
│   │   ├── Card.tsx
│   │   ├── Input.tsx
│   │   ├── Badge.tsx
│   │   ├── Dialog.tsx
│   │   ├── Tabs.tsx
│   │   └── index.ts
│   └── layout/
│       ├── AppShell.tsx
│       ├── Sidebar.tsx
│       └── PageHeader.tsx
├── styles/
│   └── globals.css
├── App.tsx
└── main.tsx

Configuration

index.html

The HTML entry point with mobile viewport, favicons, and social meta tags:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
    
    <!-- Favicons -->
    <link rel="icon" href="/favicon.ico" sizes="32x32" />
    <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
    <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
    <link rel="manifest" href="/site.webmanifest" />
    
    <!-- Theme color for mobile browser chrome -->
    <meta name="theme-color" content="#18181B" />
    
    <!-- Open Graph -->
    <meta property="og:type" content="website" />
    <meta property="og:title" content="App Name" />
    <meta property="og:description" content="App description" />
    <meta property="og:image" content="https://example.com/og-image.png" />
    <meta property="og:url" content="https://example.com" />
    
    <!-- Twitter Card -->
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="App Name" />
    <meta name="twitter:description" content="App description" />
    <meta name="twitter:image" content="https://example.com/og-image.png" />
    
    <title>App Name</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
public/site.webmanifest

PWA manifest for installable web apps:

{
  "name": "App Name",
  "short_name": "App",
  "icons": [
    { "src": "/favicon.ico", "sizes": "32x32", "type": "image/x-icon" },
    { "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }
  ],
  "theme_color": "#18181B",
  "background_color": "#18181B",
  "display": "standalone"
}
tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Segoe UI', 'system-ui', 'sans-serif'],
      },
      colors: {
        brand: {
          DEFAULT: '#8251EE',
          hover: '#9366F5',
          light: '#A37EF5',
          subtle: 'rgba(130, 81, 238, 0.15)',
        },
        neutral: {
          bg1: 'hsl(240, 6%, 10%)',
          bg2: 'hsl(240, 5%, 12%)',
          bg3: 'hsl(240, 5%, 14%)',
          bg4: 'hsl(240, 4%, 18%)',
          bg5: 'hsl(240, 4%, 22%)',
          bg6: 'hsl(240, 4%, 26%)',
        },
        text: {
          primary: '#FFFFFF',
          secondary: '#A1A1AA',
          muted: '#71717A',
        },
        border: {
          subtle: 'hsla(0, 0%, 100%, 0.08)',
          DEFAULT: 'hsla(0, 0%, 100%, 0.12)',
          strong: 'hsla(0, 0%, 100%, 0.20)',
        },
        status: {
          success: '#10B981',
          warning: '#F59E0B',
          error: '#EF4444',
          info: '#3B82F6',
        },
        dataviz: {
          purple: '#8251EE',
          blue: '#3B82F6',
          green: '#10B981',
          yellow: '#F59E0B',
          red: '#EF4444',
          pink: '#EC4899',
          cyan: '#06B6D4',
        },
      },
      borderRadius: {
        DEFAULT: '0.5rem',
        lg: '0.75rem',
        xl: '1rem',
      },
      boxShadow: {
        glow: '0 0 20px rgba(130, 81, 238, 0.3)',
        'glow-lg': '0 0 40px rgba(130, 81, 238, 0.4)',
      },
      backdropBlur: {
        xs: '2px',
      },
      animation: {
        'fade-in': 'fadeIn 0.3s ease-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'slide-down': 'slideDown 0.3s ease-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { opacity: '0', transform: 'translateY(10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        slideDown: {
          '0%': { opacity: '0', transform: 'translateY(-10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
      },
      // Mobile: safe area insets for notched devices
      spacing: {
        'safe-top': 'env(safe-area-inset-top)',
        'safe-bottom': 'env(safe-area-inset-bottom)',
        'safe-left': 'env(safe-area-inset-left)',
        'safe-right': 'env(safe-area-inset-right)',
      },
      // Mobile: minimum touch target sizes (44px per Apple/Google guidelines)
      minHeight: {
        'touch': '44px',
      },
      minWidth: {
        'touch': '44px',
      },
    },
  },
  plugins: [],
};
postcss.config.js
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Font faces */
@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI Bold Italic.ttf') format('truetype');
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

/* CSS Custom Properties */
:root {
  /* Brand colors */
  --color-brand: #8251EE;
  --color-brand-hover: #9366F5;
  --color-brand-light: #A37EF5;
  --color-brand-subtle: rgba(130, 81, 238, 0.15);

  /* Neutral backgrounds */
  --color-bg-1: hsl(240, 6%, 10%);
  --color-bg-2: hsl(240, 5%, 12%);
  --color-bg-3: hsl(240, 5%, 14%);
  --color-bg-4: hsl(240, 4%, 18%);
  --color-bg-5: hsl(240, 4%, 22%);
  --color-bg-6: hsl(240, 4%, 26%);

  /* Text colors */
  --color-text-primary: #FFFFFF;
  --color-text-secondary: #A1A1AA;
  --color-text-muted: #71717A;

  /* Border colors */
  --color-border-subtle: hsla(0, 0%, 100%, 0.08);
  --color-border-default: hsla(0, 0%, 100%, 0.12);
  --color-border-strong: hsla(0, 0%, 100%, 0.20);

  /* Status colors */
  --color-success: #10B981;
  --color-warning: #F59E0B;
  --color-error: #EF4444;
  --color-info: #3B82F6;

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;

  /* Border radius */
  --radius-sm: 0.375rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-normal: 200ms ease;
  --transition-slow: 300ms ease;
}

/* Base styles */
html {
  color-scheme: dark;
}

body {
  @apply bg-neutral-bg1 text-text-primary font-sans antialiased;
  min-height: 100vh;
}

/* Focus styles */
*:focus-visible {
  @apply outline-none ring-2 ring-brand ring-offset-2 ring-offset-neutral-bg1;
}

/* Scrollbar styling */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  @apply bg-neutral-bg2;
}

::-webkit-scrollbar-thumb {
  @apply bg-neutral-bg5 rounded-full;
}

::-webkit-scrollbar-thumb:hover {
  @apply bg-neutral-bg6;
}

/* Glass utility classes */
@layer components {
  .glass {
    @apply backdrop-blur-md bg-white/5 border border-white/10;
  }

  .glass-card {
    @apply backdrop-blur-md bg-white/5 border border-white/10 rounded-xl;
  }

  .glass-panel {
    @apply backdrop-blur-lg bg-black/40 border border-white/5;
  }

  .glass-overlay {
    @apply backdrop-blur-sm bg-black/60;
  }

  .glass-input {
    @apply backdrop-blur-sm bg-white/5 border border-white/10 focus:border-brand focus:bg-white/10;
  }
}

/* Animation utilities */
@layer utilities {
  .animate-in {
    animation: fadeIn 0.3s ease-out, slideUp 0.3s ease-out;
  }
}
src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './styles/globals.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
src/App.tsx
import { Routes, Route } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import { AppShell } from './components/layout/AppShell';
import { Dashboard } from './pages/Dashboard';
import { Settings } from './pages/Settings';

export default function App() {
  return (
    <AppShell>
      <AnimatePresence mode="wait">
        <Routes>
          <Route path="/" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </AnimatePresence>
    </AppShell>
  );
}

Animation Patterns

Framer Motion Variants
// Fade in on mount
export const fadeIn = {
  initial: { opacity: 0 },
  animate: { opacity: 1 },
  exit: { opacity: 0 },
  transition: { duration: 0.2 },
};

// Slide up on mount
export const slideUp = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: 20 },
  transition: { duration: 0.3, ease: 'easeOut' },
};

// Scale on hover (for buttons/cards)
export const scaleOnHover = {
  whileHover: { scale: 1.02 },
  whileTap: { scale: 0.98 },
  transition: { type: 'spring', stiffness: 400, damping: 17 },
};

// Stagger children
export const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.05,
      delayChildren: 0.1,
    },
  },
};

export const staggerItem = {
  hidden: { opacity: 0, y: 10 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.2, ease: 'easeOut' },
  },
};
Page Transition Wrapper
import { motion } from 'framer-motion';
import { ReactNode } from 'react';

interface PageTransitionProps {
  children: ReactNode;
}

export function PageTransition({ children }: PageTransitionProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      transition={{ duration: 0.3, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  );
}

Glass Effect Patterns

Glass Card
<div className="glass-card p-6">
  <h2 className="text-lg font-semibold text-text-primary">Card Title</h2>
  <p className="text-text-secondary mt-2">Card content goes here.</p>
</div>
Glass Panel (Sidebar)
<aside className="glass-panel w-64 h-screen p-4">
  <nav className="space-y-2">
    {/* Navigation items */}
  </nav>
</aside>
Glass Modal Overlay
<motion.div
  className="fixed inset-0 glass-overlay flex items-center justify-center z-50"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0 }}
>
  <motion.div
    className="glass-card p-6 max-w-md w-full mx-4"
    initial={{ scale: 0.95, opacity: 0 }}
    animate={{ scale: 1, opacity: 1 }}
    exit={{ scale: 0.95, opacity: 0 }}
  >
    {/* Modal content */}
  </motion.div>
</motion.div>

Typography

ElementClasses
Page titletext-2xl font-semibold text-text-primary
Section titletext-lg font-semibold text-text-primary
Card titletext-base font-medium text-text-primary
Body texttext-sm text-text-secondary
Captiontext-xs text-text-muted
Labeltext-sm font-medium text-text-secondary

Color Usage

Use CaseColorClass
Primary actionBrand purplebg-brand text-white
Primary hoverBrand hoverhover:bg-brand-hover
Page backgroundNeutral bg1bg-neutral-bg1
Card backgroundNeutral bg2bg-neutral-bg2
Elevated surfaceNeutral bg3bg-neutral-bg3
Input backgroundNeutral bg2bg-neutral-bg2
Input focusNeutral bg3focus:bg-neutral-bg3
Border defaultBorder defaultborder-border
Border subtleBorder subtleborder-border-subtle
SuccessStatus successtext-status-success
WarningStatus warningtext-status-warning
ErrorStatus errortext-status-error
  • Design Tokens — Complete color system, spacing, typography scales

  • Components — Button, Card, Input, Dialog, Tabs, and more

  • Patterns — Page layouts, navigation, lists, forms

Files

references/patterns.md

Patterns

Page layouts, navigation patterns, and application templates for dark-themed React applications.

App Shell

The main application layout with sidebar navigation:

import { motion } from 'framer-motion';
import { type ReactNode } from 'react';
import { Sidebar } from './Sidebar';

interface AppShellProps {
  children: ReactNode;
}

export function AppShell({ children }: AppShellProps) {
  return (
    <div className="flex min-h-screen bg-neutral-bg1">
      <Sidebar />
      <main className="flex-1 ml-64">
        {children}
      </main>
    </div>
  );
}

Responsive App Shell

Mobile-responsive layout: desktop shows fixed sidebar, mobile shows hamburger menu with slide-in drawer.

Breakpoint: lg (1024px) — sidebar visible on desktop, hidden on mobile.

import { motion, AnimatePresence } from 'framer-motion';
import { type ReactNode, useState } from 'react';
import { Sidebar } from './Sidebar';
import { MobileHeader } from './MobileHeader';
import { MobileDrawer } from './MobileDrawer';

interface ResponsiveAppShellProps {
  children: ReactNode;
}

export function ResponsiveAppShell({ children }: ResponsiveAppShellProps) {
  const [drawerOpen, setDrawerOpen] = useState(false);

  return (
    <div className="flex min-h-screen bg-neutral-bg1">
      {/* Desktop sidebar - hidden on mobile */}
      <div className="hidden lg:block">
        <Sidebar />
      </div>

      {/* Mobile header - visible only on mobile */}
      <MobileHeader onMenuClick={() => setDrawerOpen(true)} />

      {/* Mobile drawer */}
      <MobileDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} />

      {/* Main content - adjusted margins for desktop sidebar and mobile header */}
      <main className="flex-1 lg:ml-64 pt-14 lg:pt-0 pb-safe-bottom">
        {children}
      </main>
    </div>
  );
}

Mobile Header

Fixed top header for mobile with hamburger menu toggle. Uses 44px minimum touch target.

import { Bars3Icon } from '@heroicons/react/24/outline';

interface MobileHeaderProps {
  onMenuClick: () => void;
}

export function MobileHeader({ onMenuClick }: MobileHeaderProps) {
  return (
    <header className="fixed top-0 left-0 right-0 h-14 glass-panel flex items-center justify-between px-4 lg:hidden z-40">
      {/* Logo */}
      <div className="flex items-center gap-2">
        <img src="/logo.svg" alt="Logo" className="w-6 h-6" />
        <span className="text-base font-semibold text-text-primary">AppName</span>
      </div>

      {/* Hamburger menu - 44px touch target */}
      <button
        onClick={onMenuClick}
        className="min-w-touch min-h-touch flex items-center justify-center rounded-lg hover:bg-white/5 active:bg-white/10 transition-colors"
        aria-label="Open menu"
      >
        <Bars3Icon className="w-6 h-6 text-text-primary" />
      </button>
    </header>
  );
}

Mobile Drawer

Animated slide-in navigation drawer for mobile. Includes backdrop and close button.

import { motion, AnimatePresence } from 'framer-motion';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { NavLink, useLocation } from 'react-router-dom';
import { clsx } from 'clsx';
import {
  HomeIcon,
  ChartBarIcon,
  Cog6ToothIcon,
  UsersIcon,
  FolderIcon,
} from '@heroicons/react/24/outline';

const navItems = [
  { path: '/', label: 'Dashboard', icon: HomeIcon },
  { path: '/analytics', label: 'Analytics', icon: ChartBarIcon },
  { path: '/projects', label: 'Projects', icon: FolderIcon },
  { path: '/team', label: 'Team', icon: UsersIcon },
  { path: '/settings', label: 'Settings', icon: Cog6ToothIcon },
];

interface MobileDrawerProps {
  open: boolean;
  onClose: () => void;
}

export function MobileDrawer({ open, onClose }: MobileDrawerProps) {
  const location = useLocation();

  return (
    <AnimatePresence>
      {open && (
        <>
          {/* Backdrop */}
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.2 }}
            className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 lg:hidden"
            onClick={onClose}
          />

          {/* Drawer */}
          <motion.aside
            initial={{ x: '-100%' }}
            animate={{ x: 0 }}
            exit={{ x: '-100%' }}
            transition={{ type: 'spring', stiffness: 400, damping: 30 }}
            className="fixed left-0 top-0 bottom-0 w-72 glass-panel p-4 flex flex-col z-50 lg:hidden pl-safe-left"
          >
            {/* Header with close button */}
            <div className="flex items-center justify-between mb-6">
              <div className="flex items-center gap-2">
                <img src="/logo.svg" alt="Logo" className="w-8 h-8" />
                <span className="text-lg font-semibold text-text-primary">AppName</span>
              </div>
              <button
                onClick={onClose}
                className="min-w-touch min-h-touch flex items-center justify-center rounded-lg hover:bg-white/5 active:bg-white/10 transition-colors"
                aria-label="Close menu"
              >
                <XMarkIcon className="w-6 h-6 text-text-primary" />
              </button>
            </div>

            {/* Navigation */}
            <nav className="flex-1 space-y-1">
              {navItems.map((item) => {
                const isActive = location.pathname === item.path;
                const Icon = item.icon;

                return (
                  <NavLink key={item.path} to={item.path} onClick={onClose}>
                    <motion.div
                      className={clsx(
                        'relative flex items-center gap-3 px-3 py-3 rounded-lg',
                        'text-base font-medium transition-colors duration-150',
                        'min-h-touch', // 44px touch target
                        isActive
                          ? 'text-text-primary bg-brand/20 border border-brand/30'
                          : 'text-text-muted hover:text-text-secondary hover:bg-white/5 active:bg-white/10'
                      )}
                      whileTap={{ scale: 0.98 }}
                    >
                      <Icon className={clsx('w-5 h-5', isActive && 'text-brand')} />
                      <span>{item.label}</span>
                    </motion.div>
                  </NavLink>
                );
              })}
            </nav>

            {/* User profile at bottom */}
            <div className="mt-auto pt-4 border-t border-white/10 pb-safe-bottom">
              <div className="flex items-center gap-3 px-3 py-2">
                <div className="w-10 h-10 rounded-full bg-brand-subtle flex items-center justify-center">
                  <span className="text-sm font-medium text-brand">JD</span>
                </div>
                <div className="flex-1 min-w-0">
                  <p className="text-sm font-medium text-text-primary truncate">John Doe</p>
                  <p className="text-xs text-text-muted truncate">[email protected]</p>
                </div>
              </div>
            </div>
          </motion.aside>
        </>
      )}
    </AnimatePresence>
  );
}

Sidebar Navigation

Glass-effect sidebar with animated navigation items:

import { motion } from 'framer-motion';
import { NavLink, useLocation } from 'react-router-dom';
import { clsx } from 'clsx';
import {
  HomeIcon,
  ChartBarIcon,
  Cog6ToothIcon,
  UsersIcon,
  FolderIcon,
} from '@heroicons/react/24/outline';

const navItems = [
  { path: '/', label: 'Dashboard', icon: HomeIcon },
  { path: '/analytics', label: 'Analytics', icon: ChartBarIcon },
  { path: '/projects', label: 'Projects', icon: FolderIcon },
  { path: '/team', label: 'Team', icon: UsersIcon },
  { path: '/settings', label: 'Settings', icon: Cog6ToothIcon },
];

export function Sidebar() {
  const location = useLocation();

  return (
    <aside className="fixed left-0 top-0 h-screen w-64 glass-panel p-4 flex flex-col">
      {/* Logo */}
      <div className="flex items-center gap-3 px-3 py-4 mb-6">
        <img src="/logo.svg" alt="Logo" className="w-8 h-8" />
        <span className="text-lg font-semibold text-text-primary">AppName</span>
      </div>

      {/* Navigation */}
      <nav className="flex-1 space-y-1">
        {navItems.map((item) => {
          const isActive = location.pathname === item.path;
          const Icon = item.icon;

          return (
            <NavLink key={item.path} to={item.path}>
              <motion.div
                className={clsx(
                  'relative flex items-center gap-3 px-3 py-2.5 rounded-lg',
                  'text-sm font-medium transition-colors duration-150',
                  isActive
                    ? 'text-text-primary'
                    : 'text-text-muted hover:text-text-secondary hover:bg-white/5'
                )}
                whileHover={{ x: 4 }}
                transition={{ type: 'spring', stiffness: 400, damping: 25 }}
              >
                {isActive && (
                  <motion.div
                    layoutId="activeNavItem"
                    className="absolute inset-0 bg-brand/20 border border-brand/30 rounded-lg"
                    transition={{ type: 'spring', stiffness: 500, damping: 30 }}
                  />
                )}
                <Icon className={clsx('w-5 h-5 relative z-10', isActive && 'text-brand')} />
                <span className="relative z-10">{item.label}</span>
              </motion.div>
            </NavLink>
          );
        })}
      </nav>

      {/* User profile at bottom */}
      <div className="mt-auto pt-4 border-t border-white/10">
        <div className="flex items-center gap-3 px-3 py-2">
          <div className="w-8 h-8 rounded-full bg-brand-subtle flex items-center justify-center">
            <span className="text-sm font-medium text-brand">JD</span>
          </div>
          <div className="flex-1 min-w-0">
            <p className="text-sm font-medium text-text-primary truncate">John Doe</p>
            <p className="text-xs text-text-muted truncate">[email protected]</p>
          </div>
        </div>
      </div>
    </aside>
  );
}

Page Header

Reusable header component for pages:

import { motion } from 'framer-motion';
import { type ReactNode } from 'react';

interface PageHeaderProps {
  title: string;
  description?: string;
  actions?: ReactNode;
}

export function PageHeader({ title, description, actions }: PageHeaderProps) {
  return (
    <motion.header
      initial={{ opacity: 0, y: -10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
      className="flex items-center justify-between mb-8"
    >
      <div>
        <h1 className="text-2xl font-semibold text-text-primary">{title}</h1>
        {description && (
          <p className="text-sm text-text-secondary mt-1">{description}</p>
        )}
      </div>
      {actions && <div className="flex items-center gap-3">{actions}</div>}
    </motion.header>
  );
}

Page Transition Wrapper

Animate page transitions with route changes:

import { motion } from 'framer-motion';
import { type ReactNode } from 'react';

interface PageTransitionProps {
  children: ReactNode;
}

const pageVariants = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -20 },
};

export function PageTransition({ children }: PageTransitionProps) {
  return (
    <motion.div
      variants={pageVariants}
      initial="initial"
      animate="animate"
      exit="exit"
      transition={{ duration: 0.3, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  );
}
Usage with React Router
import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';

function App() {
  const location = useLocation();

  return (
    <AppShell>
      <AnimatePresence mode="wait">
        <Routes location={location} key={location.pathname}>
          <Route path="/" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </AnimatePresence>
    </AppShell>
  );
}

Basic Page Template

Standard page layout with header and content area:

import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Button } from '../components/ui';

export function BasicPage() {
  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Page Title"
          description="A brief description of this page"
          actions={
            <Button>Primary Action</Button>
          }
        />

        <div className="space-y-6">
          {/* Page content */}
        </div>
      </div>
    </PageTransition>
  );
}

List Page Template

Page with search, filters, and data table:

import { useState } from 'react';
import { motion } from 'framer-motion';
import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Button, SearchInput, Card, Badge } from '../components/ui';
import { PlusIcon } from '@heroicons/react/20/solid';

interface Item {
  id: string;
  name: string;
  status: 'active' | 'inactive' | 'pending';
  createdAt: string;
}

const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.05 },
  },
};

const staggerItem = {
  hidden: { opacity: 0, y: 10 },
  visible: { opacity: 1, y: 0 },
};

export function ListPage() {
  const [search, setSearch] = useState('');
  const [items] = useState<Item[]>([
    { id: '1', name: 'Project Alpha', status: 'active', createdAt: '2024-01-15' },
    { id: '2', name: 'Project Beta', status: 'pending', createdAt: '2024-01-14' },
    { id: '3', name: 'Project Gamma', status: 'inactive', createdAt: '2024-01-13' },
  ]);

  const filteredItems = items.filter((item) =>
    item.name.toLowerCase().includes(search.toLowerCase())
  );

  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Projects"
          description="Manage your projects and their settings"
          actions={
            <Button leftIcon={<PlusIcon className="w-4 h-4" />}>
              New Project
            </Button>
          }
        />

        {/* Filters */}
        <div className="flex items-center gap-4 mb-6">
          <div className="w-72">
            <SearchInput
              value={search}
              onChange={setSearch}
              placeholder="Search projects..."
            />
          </div>
        </div>

        {/* Table */}
        <Card padding="none">
          <table className="w-full">
            <thead>
              <tr className="border-b border-border">
                <th className="text-left text-xs font-medium text-text-muted uppercase tracking-wide px-4 py-3">
                  Name
                </th>
                <th className="text-left text-xs font-medium text-text-muted uppercase tracking-wide px-4 py-3">
                  Status
                </th>
                <th className="text-left text-xs font-medium text-text-muted uppercase tracking-wide px-4 py-3">
                  Created
                </th>
              </tr>
            </thead>
            <motion.tbody
              variants={staggerContainer}
              initial="hidden"
              animate="visible"
            >
              {filteredItems.map((item) => (
                <motion.tr
                  key={item.id}
                  variants={staggerItem}
                  className="border-b border-border last:border-0 hover:bg-neutral-bg3 transition-colors cursor-pointer"
                >
                  <td className="px-4 py-3">
                    <span className="text-sm font-medium text-text-primary">
                      {item.name}
                    </span>
                  </td>
                  <td className="px-4 py-3">
                    <Badge
                      variant={
                        item.status === 'active' ? 'success' :
                        item.status === 'pending' ? 'warning' : 'default'
                      }
                    >
                      {item.status}
                    </Badge>
                  </td>
                  <td className="px-4 py-3">
                    <span className="text-sm text-text-secondary">
                      {item.createdAt}
                    </span>
                  </td>
                </motion.tr>
              ))}
            </motion.tbody>
          </table>
        </Card>
      </div>
    </PageTransition>
  );
}

Tabs Page Template

Page with tab navigation:

import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Tabs, TabsList, TabsTrigger, TabsContent, Card } from '../components/ui';

export function TabsPage() {
  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Settings"
          description="Manage your account and preferences"
        />

        <Tabs defaultValue="general">
          <TabsList>
            <TabsTrigger value="general">General</TabsTrigger>
            <TabsTrigger value="security">Security</TabsTrigger>
            <TabsTrigger value="notifications">Notifications</TabsTrigger>
            <TabsTrigger value="integrations">Integrations</TabsTrigger>
          </TabsList>

          <TabsContent value="general">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                General Settings
              </h3>
              {/* General settings form */}
            </Card>
          </TabsContent>

          <TabsContent value="security">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                Security Settings
              </h3>
              {/* Security settings form */}
            </Card>
          </TabsContent>

          <TabsContent value="notifications">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                Notification Preferences
              </h3>
              {/* Notification settings */}
            </Card>
          </TabsContent>

          <TabsContent value="integrations">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                Connected Integrations
              </h3>
              {/* Integrations list */}
            </Card>
          </TabsContent>
        </Tabs>
      </div>
    </PageTransition>
  );
}

Dashboard Template

Dashboard with stat cards and data visualization:

import { motion } from 'framer-motion';
import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Card, CardTitle, CardContent } from '../components/ui';
import {
  ArrowUpIcon,
  ArrowDownIcon,
  UsersIcon,
  CurrencyDollarIcon,
  ChartBarIcon,
  ClockIcon,
} from '@heroicons/react/24/outline';

const stats = [
  { label: 'Total Revenue', value: '$45,231', change: '+12.5%', trend: 'up', icon: CurrencyDollarIcon },
  { label: 'Active Users', value: '2,345', change: '+8.2%', trend: 'up', icon: UsersIcon },
  { label: 'Conversion Rate', value: '3.2%', change: '-2.1%', trend: 'down', icon: ChartBarIcon },
  { label: 'Avg. Session', value: '4m 32s', change: '+18.7%', trend: 'up', icon: ClockIcon },
];

const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.1 },
  },
};

const staggerItem = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
};

export function Dashboard() {
  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Dashboard"
          description="Welcome back! Here's what's happening."
        />

        {/* Stat Cards */}
        <motion.div
          variants={staggerContainer}
          initial="hidden"
          animate="visible"
          className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8"
        >
          {stats.map((stat) => {
            const Icon = stat.icon;
            const TrendIcon = stat.trend === 'up' ? ArrowUpIcon : ArrowDownIcon;

            return (
              <motion.div key={stat.label} variants={staggerItem}>
                <Card variant="glass" className="relative overflow-hidden">
                  <div className="flex items-start justify-between">
                    <div>
                      <p className="text-sm text-text-muted">{stat.label}</p>
                      <p className="text-2xl font-semibold text-text-primary mt-1">
                        {stat.value}
                      </p>
                      <div className="flex items-center gap-1 mt-2">
                        <TrendIcon
                          className={`w-4 h-4 ${
                            stat.trend === 'up' ? 'text-status-success' : 'text-status-error'
                          }`}
                        />
                        <span
                          className={`text-sm ${
                            stat.trend === 'up' ? 'text-status-success' : 'text-status-error'
                          }`}
                        >
                          {stat.change}
                        </span>
                        <span className="text-xs text-text-muted">vs last month</span>
                      </div>
                    </div>
                    <div className="p-2 rounded-lg bg-brand-subtle">
                      <Icon className="w-5 h-5 text-brand" />
                    </div>
                  </div>
                </Card>
              </motion.div>
            );
          })}
        </motion.div>

        {/* Main Content Grid */}
        <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
          {/* Chart Card */}
          <Card variant="glass" className="lg:col-span-2">
            <CardTitle>Revenue Overview</CardTitle>
            <CardContent className="h-80">
              {/* Chart component would go here */}
              <div className="w-full h-full flex items-center justify-center text-text-muted">
                Chart placeholder
              </div>
            </CardContent>
          </Card>

          {/* Recent Activity */}
          <Card variant="glass">
            <CardTitle>Recent Activity</CardTitle>
            <CardContent>
              <ul className="space-y-4">
                {[1, 2, 3, 4].map((i) => (
                  <li key={i} className="flex items-start gap-3">
                    <div className="w-8 h-8 rounded-full bg-brand-subtle flex items-center justify-center flex-shrink-0">
                      <UsersIcon className="w-4 h-4 text-brand" />
                    </div>
                    <div>
                      <p className="text-sm text-text-primary">New user signed up</p>
                      <p className="text-xs text-text-muted">2 minutes ago</p>
                    </div>
                  </li>
                ))}
              </ul>
            </CardContent>
          </Card>
        </div>
      </div>
    </PageTransition>
  );
}

Stagger Animation Patterns

Reusable animation variants for list items:

// variants.ts
export const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.05,
      delayChildren: 0.1,
    },
  },
};

export const staggerItem = {
  hidden: { opacity: 0, y: 10 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.2, ease: 'easeOut' },
  },
};

export const fadeInScale = {
  hidden: { opacity: 0, scale: 0.95 },
  visible: {
    opacity: 1,
    scale: 1,
    transition: { duration: 0.2, ease: 'easeOut' },
  },
};

export const slideInFromRight = {
  hidden: { opacity: 0, x: 20 },
  visible: {
    opacity: 1,
    x: 0,
    transition: { duration: 0.3, ease: 'easeOut' },
  },
};

export const slideInFromLeft = {
  hidden: { opacity: 0, x: -20 },
  visible: {
    opacity: 1,
    x: 0,
    transition: { duration: 0.3, ease: 'easeOut' },
  },
};
Using Stagger Animations
import { motion } from 'framer-motion';
import { staggerContainer, staggerItem } from './variants';

function ItemList({ items }) {
  return (
    <motion.ul
      variants={staggerContainer}
      initial="hidden"
      animate="visible"
      className="space-y-2"
    >
      {items.map((item) => (
        <motion.li key={item.id} variants={staggerItem}>
          {item.name}
        </motion.li>
      ))}
    </motion.ul>
  );
}

Glass Panel Layouts

Two-Column Layout with Glass Panels
export function TwoColumnLayout() {
  return (
    <div className="flex gap-6 p-8">
      {/* Main content */}
      <div className="flex-1">
        <Card variant="glass">
          <h2 className="text-lg font-semibold text-text-primary mb-4">
            Main Content
          </h2>
          {/* Content */}
        </Card>
      </div>

      {/* Sidebar */}
      <aside className="w-80">
        <Card variant="glass" className="sticky top-8">
          <h3 className="text-sm font-medium text-text-muted uppercase tracking-wide mb-4">
            Quick Actions
          </h3>
          {/* Sidebar content */}
        </Card>
      </aside>
    </div>
  );
}
Grid of Glass Cards
export function CardGrid({ items }) {
  return (
    <motion.div
      variants={staggerContainer}
      initial="hidden"
      animate="visible"
      className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
    >
      {items.map((item) => (
        <motion.div key={item.id} variants={staggerItem}>
          <Card variant="glass" interactive>
            <h3 className="text-base font-medium text-text-primary">
              {item.title}
            </h3>
            <p className="text-sm text-text-secondary mt-2">
              {item.description}
            </p>
          </Card>
        </motion.div>
      ))}
    </motion.div>
  );
}

Form Patterns

Settings Form
import { Button, Input, Select, Checkbox, Card } from '../components/ui';

export function SettingsForm() {
  return (
    <Card>
      <h2 className="text-lg font-semibold text-text-primary mb-6">
        Account Settings
      </h2>

      <form className="space-y-6">
        {/* Two-column grid for larger screens */}
        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
          <Input
            label="First Name"
            placeholder="Enter your first name"
          />
          <Input
            label="Last Name"
            placeholder="Enter your last name"
          />
        </div>

        <Input
          label="Email Address"
          type="email"
          placeholder="[email protected]"
        />

        <Select
          label="Timezone"
          placeholder="Select timezone"
          options={[
            { value: 'utc', label: 'UTC' },
            { value: 'pst', label: 'Pacific Time (PST)' },
            { value: 'est', label: 'Eastern Time (EST)' },
          ]}
        />

        <div className="border-t border-border pt-6">
          <h3 className="text-sm font-medium text-text-primary mb-4">
            Notification Preferences
          </h3>
          <div className="space-y-3">
            <Checkbox label="Email notifications" defaultChecked />
            <Checkbox label="Push notifications" />
            <Checkbox label="Weekly digest" defaultChecked />
          </div>
        </div>

        <div className="flex justify-end gap-3 pt-4">
          <Button variant="secondary">Cancel</Button>
          <Button>Save Changes</Button>
        </div>
      </form>
    </Card>
  );
}

Empty States

import { motion } from 'framer-motion';
import { FolderIcon } from '@heroicons/react/24/outline';
import { Button } from '../components/ui';

interface EmptyStateProps {
  icon?: typeof FolderIcon;
  title: string;
  description: string;
  action?: {
    label: string;
    onClick: () => void;
  };
}

export function EmptyState({ icon: Icon = FolderIcon, title, description, action }: EmptyStateProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      className="flex flex-col items-center justify-center py-16 px-4"
    >
      <div className="w-16 h-16 rounded-full bg-neutral-bg3 flex items-center justify-center mb-4">
        <Icon className="w-8 h-8 text-text-muted" />
      </div>
      <h3 className="text-lg font-medium text-text-primary mb-2">{title}</h3>
      <p className="text-sm text-text-secondary text-center max-w-sm mb-6">
        {description}
      </p>
      {action && (
        <Button onClick={action.onClick}>{action.label}</Button>
      )}
    </motion.div>
  );
}
Usage
<EmptyState
  title="No projects yet"
  description="Get started by creating your first project."
  action={{
    label: 'Create Project',
    onClick: () => setShowCreateModal(true),
  }}
/>

Loading States

Skeleton Loader
import { clsx } from 'clsx';

interface SkeletonProps {
  className?: string;
}

export function Skeleton({ className }: SkeletonProps) {
  return (
    <div
      className={clsx(
        'animate-pulse bg-neutral-bg4 rounded',
        className
      )}
    />
  );
}

// Usage
<Skeleton className="h-4 w-32" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-40 w-full rounded-xl" />
Card Skeleton
export function CardSkeleton() {
  return (
    <Card>
      <div className="space-y-4">
        <Skeleton className="h-5 w-1/3" />
        <Skeleton className="h-4 w-full" />
        <Skeleton className="h-4 w-2/3" />
      </div>
    </Card>
  );
}
Table Skeleton
export function TableSkeleton({ rows = 5 }: { rows?: number }) {
  return (
    <Card padding="none">
      <div className="divide-y divide-border">
        {Array.from({ length: rows }).map((_, i) => (
          <div key={i} className="flex items-center gap-4 p-4">
            <Skeleton className="h-8 w-8 rounded-full" />
            <Skeleton className="h-4 flex-1" />
            <Skeleton className="h-4 w-20" />
            <Skeleton className="h-4 w-24" />
          </div>
        ))}
      </div>
    </Card>
  );
}

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/skills · .github/plugins/azure-sdk-typescript/skills/frontend-ui-dark-ts · commit 0696600
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.

Get it from the source

4 of this skill’s files are not mirrored here, so there is no ZIP. Get the complete skill from its source: microsoft/skills