CtrlK
BlogDocsLog inGet started
Tessl Logo

foundry-nextgen-frontend

Build elegant frontend UIs following Microsoft Foundry's NextGen Design System using Vite + React + pnpm + Framer Motion. Use when creating dashboards, agent builders, data grids, entity management interfaces, or any application matching Foundry's refined dark-themed aesthetic.

Install with Tessl CLI

npx tessl i github:microsoft/agent-skills --skill foundry-nextgen-frontend
What are skills?

Overall
score

84%

Does it follow best practices?

Validation for skill structure

SKILL.md
Review
Evals

Microsoft Foundry NextGen Frontend Skill

Build elegant, production-ready interfaces following Microsoft Foundry's NextGen Design System - a refined neutral dark-themed design language with minimal purple accents and subtle animations.

⚠️ CRITICAL: Color Usage Rules

Purple (#8251EE / #A37EF5) is ONLY for:

  • Primary action buttons (filled background)
  • Active tab indicators (2px underline)
  • Row selection indicators (left border bar)
  • Active sidebar navigation icons
  • Links and interactive text
  • Progress indicators and sliders

Everything else uses NEUTRAL DARK GREYS:

  • Backgrounds: Near-black (#0A0A0A, #0D0D0D)
  • Cards/Surfaces: Dark grey (#141414, #1C1C1C) with NO visible borders or very subtle ones
  • Text: White (#FFFFFF), grey (#A1A1A1), muted (#6B6B6B)
  • Borders: Mostly invisible or very subtle (#1F1F1F)

Preferred Stack

pnpm create vite@latest my-foundry-app --template react-ts
cd my-foundry-app
pnpm install
pnpm add framer-motion lucide-react

Required packages:

  • framer-motion - for subtle, elegant animations
  • lucide-react - for icons

⚠️ CRITICAL: Spacing & Padding Rules

Consistent spacing is non-negotiable. Use the spacing scale:

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
}

Standard padding patterns:

ElementPadding
Page content32px (--space-8)
Card20px (--space-5)
Card header16px 20px
Button8px 16px
Input10px 12px
Table cell12px 16px
Modal body24px
Badge/Tag4px 10px

Grid gaps:

LayoutGap
Card grid16px (--space-4)
Form fields20px (--space-5)
Button group12px (--space-3)
Tag group8px (--space-2)

Core Design Tokens

:root {
  /* BACKGROUNDS - Neutral Darks */
  --bg-page: #0A0A0A;
  --bg-sidebar: #0D0D0D;
  --bg-card: #141414;
  --bg-surface: #1C1C1C;
  --bg-elevated: #242424;
  --bg-hover: rgba(255, 255, 255, 0.05);
  --bg-active: rgba(255, 255, 255, 0.08);

  /* TEXT */
  --text-primary: #FFFFFF;
  --text-secondary: #A1A1A1;
  --text-muted: #6B6B6B;
  --text-disabled: #4A4A4A;
  --text-link: #A37EF5;

  /* BRAND - Use Sparingly! */
  --brand-primary: #8251EE;
  --brand-hover: #9366F5;
  --brand-light: #A37EF5;

  /* BORDERS - Keep Subtle! */
  --border-subtle: rgba(255, 255, 255, 0.06);
  --border-default: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.12);

  /* STATUS */
  --success: #10B981;
  --success-bg: rgba(16, 185, 129, 0.12);
  --warning: #F59E0B;
  --warning-bg: rgba(245, 158, 11, 0.12);
  --error: #EF4444;
  --error-bg: rgba(239, 68, 68, 0.12);
  --info: #3B82F6;
  --info-bg: rgba(59, 130, 246, 0.12);

  /* RADIUS */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 12px;
}

⚠️ CRITICAL: Animation with Framer Motion

Always add subtle animations. Never skip animations.

import { motion } from 'framer-motion';

// Page/container fade in
const pageVariants = {
  hidden: { opacity: 0 },
  visible: { 
    opacity: 1,
    transition: { duration: 0.3, ease: 'easeOut' }
  }
};

// Stagger children (for lists, grids)
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.05,
      delayChildren: 0.1
    }
  }
};

// Individual item animation
const itemVariants = {
  hidden: { opacity: 0, y: 8 },
  visible: { 
    opacity: 1, 
    y: 0,
    transition: { duration: 0.3, ease: 'easeOut' }
  }
};

// Hover scale for cards/buttons
const hoverScale = {
  whileHover: { scale: 1.01 },
  whileTap: { scale: 0.99 },
  transition: { duration: 0.15 }
};

Standard animation patterns:

// Page wrapper - always animate page entry
<motion.div
  initial="hidden"
  animate="visible"
  variants={pageVariants}
  className="page"
>
  {children}
</motion.div>

// Card grid with stagger
<motion.div
  variants={containerVariants}
  initial="hidden"
  animate="visible"
  className="card-grid"
>
  {items.map(item => (
    <motion.div key={item.id} variants={itemVariants}>
      <Card {...item} />
    </motion.div>
  ))}
</motion.div>

// Interactive card with hover
<motion.div
  className="card"
  whileHover={{ scale: 1.01, backgroundColor: 'rgba(255,255,255,0.02)' }}
  whileTap={{ scale: 0.99 }}
  transition={{ duration: 0.15 }}
>
  {content}
</motion.div>

// Button with press feedback
<motion.button
  className="btn btn-primary"
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
  transition={{ duration: 0.1 }}
>
  Create
</motion.button>

// Modal with backdrop
<motion.div
  className="modal-overlay"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0 }}
>
  <motion.div
    className="modal"
    initial={{ opacity: 0, scale: 0.95, y: 10 }}
    animate={{ opacity: 1, scale: 1, y: 0 }}
    exit={{ opacity: 0, scale: 0.95, y: 10 }}
    transition={{ duration: 0.2, ease: 'easeOut' }}
  >
    {content}
  </motion.div>
</motion.div>

Card Component (Correct Implementation)

Cards should have NO visible borders or very subtle ones.

import { motion } from 'framer-motion';

function Card({ title, description, status, tags, meta }) {
  return (
    <motion.div 
      className="card"
      whileHover={{ 
        scale: 1.01,
        backgroundColor: 'rgba(255, 255, 255, 0.02)'
      }}
      transition={{ duration: 0.15 }}
    >
      <div className="card-header">
        <h3 className="card-title">{title}</h3>
        {status && <StatusBadge status={status} />}
      </div>
      
      <p className="card-description">{description}</p>
      
      {tags && (
        <div className="card-tags">
          {tags.map(tag => (
            <span key={tag} className="tag">{tag}</span>
          ))}
        </div>
      )}
      
      {meta && (
        <div className="card-meta">
          {meta}
        </div>
      )}
    </motion.div>
  );
}
.card {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  padding: var(--space-5);  /* 20px */
  /* NO border or very subtle */
  border: 1px solid transparent;
  /* Use subtle shadow instead of border */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.card:hover {
  /* Subtle border on hover only */
  border-color: var(--border-subtle);
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: var(--space-3);  /* 12px */
}

.card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}

.card-description {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 0 0 var(--space-4);  /* 16px bottom */
  line-height: 1.5;
}

.card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);  /* 8px */
  margin-bottom: var(--space-4);
}

.card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  font-size: 12px;
  color: var(--text-muted);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

Tags & Badges

/* Service/integration tags - subtle, not colorful */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text-secondary);
}

/* Status badges */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-radius: var(--radius-sm);
}

.badge-success {
  background: var(--success-bg);
  color: var(--success);
}

.badge-info {
  background: var(--info-bg);
  color: var(--info);
}

.badge-warning {
  background: var(--warning-bg);
  color: var(--warning);
}

Card Grid Layout

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--space-4);  /* 16px - consistent gap */
  padding: var(--space-8);  /* 32px page padding */
}

/* For fixed 4-column layout */
.card-grid-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4);
}

/* For 3-column layout */
.card-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}

Page Layout with Sidebar

function AppLayout({ children }) {
  return (
    <div className="app-layout">
      <Sidebar />
      <main className="main-content">
        <TopBar />
        <motion.div 
          className="page-content"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 0.3 }}
        >
          {children}
        </motion.div>
      </main>
    </div>
  );
}
.app-layout {
  display: flex;
  min-height: 100vh;
  background: var(--bg-page);
}

.sidebar {
  width: 56px;
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border-subtle);
  flex-shrink: 0;
}

.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  height: 48px;
  background: var(--bg-sidebar);
  border-bottom: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  padding: 0 var(--space-6);
}

.page-content {
  flex: 1;
  padding: var(--space-8);  /* 32px consistent padding */
  overflow-y: auto;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-6);  /* 24px before content */
}

.page-title {
  font-size: 20px;
  font-weight: 600;
  color: var(--text-primary);
}

Buttons

<motion.button
  className="btn btn-primary"
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
>
  Create
</motion.button>

<motion.button
  className="btn btn-secondary"
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
>
  Cancel
</motion.button>
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 500;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.15s ease;
}

.btn-primary {
  background: var(--brand-primary);
  color: white;
  border: none;
}

.btn-primary:hover {
  background: var(--brand-hover);
}

.btn-secondary {
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
}

.btn-secondary:hover {
  background: var(--bg-hover);
}

Critical Don'ts ❌

  • Don't use visible card borders - cards blend into background
  • Don't use inconsistent padding - follow the spacing scale
  • Don't forget animations - every list, modal, page needs motion
  • Don't use purple for cards/backgrounds - neutral greys only
  • Don't skip hover states - everything interactive needs feedback
  • Don't use hard-coded pixel values - use CSS variables

Critical Do's ✅

  • Use Framer Motion for all animations
  • Use consistent spacing scale (4, 8, 12, 16, 20, 24, 32px)
  • Use 32px padding for page content areas
  • Use 16px gap for card grids
  • Use transparent or very subtle borders
  • Add hover animations to interactive elements
  • Stagger list/grid item animations

File References

  • Design Tokens: See references/design-tokens.md
  • Components: See references/components.md
  • Patterns: See references/patterns.md
Repository
github.com/microsoft/agent-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.