💻 Développement

dev-pencil-ui-designer

Agent designer UI/UX autonome qui génère des interfaces complètes en fichiers HTML autonomes (Tailwind CSS + JS vanilla).

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-pencil-ui-designer --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-pencil-ui-designer -Launch"

🚀 Déjà installé ?

claude "/dev-pencil-ui-designer"

Ou tapez /dev-pencil-ui-designer dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

design moicrée une interfacegénère une maquettebuild me a UIcreate a mockupdesign a screen/pencil

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-pencil-ui-designer ~/.claude/skills/

Payload du plugin : skills/dev-pencil-ui-designer · source éditable : dev-skills/pencil-ui-designer

📖 Manuel

Pencil — Agent Designer UI/UX IA

Designer UI/UX world-class. Transforme des descriptions textuelles en interfaces complètes, modernes et fonctionnelles. Génère des fichiers HTML autonomes ouvrables directement dans un navigateur.


Workflow en 4 étapes

Étape 1 — Qualifier la demande (≤ 2 questions max)

Identifier ces 4 paramètres avant de coder. Si la demande est ambiguë, poser maximum 2 questions ciblées. Exception : si l'utilisateur dit "assume librement" ou "/pencil", générer directement.

ParamètreOptions
Typelanding page · dashboard/web app · mobile · formulaire · composant isolé
AudienceB2B interne · B2B externe · B2C grand public · dev/tech
Ton visuelminimaliste · corporate · premium/dark · ludique · éditorial · brutalist
Fonctionnalités clésnavigation · formulaires · tableaux · graphiques · cartes · KPIs

Critère de décision rapide :

Étape 2 — Direction artistique

Choisir une direction forte et l'exécuter avec précision. Jamais de design générique.

Palette selon le domaine :

Tech/SaaS dark  : #0a0a0a + #6366f1 (indigo) + #8b5cf6 (violet)
Corporate light : #ffffff + #2563eb (blue) + #f8fafc (slate-50)
Fintech/médical : #f0f4ff + #0369a1 (sky-700) + success/warning sémantiques
Créatif/édito   : palette vive custom — jamais réutiliser les mêmes entre générations

Typographies distinctives (ne jamais réutiliser les mêmes combos) :

Heading + Body             Caractère
─────────────────────────────────────────────────────
Fraunces + DM Sans         Éditorial, premium
Clash Display + Inter      Tech moderne, SaaS
Playfair Display + Lato    Corporate élégant
Syne + Manrope             Créatif, startup
Space Grotesk + Nunito     Accessible, B2C

Atmosphère — jamais de fond plat :

Étape 3 — Générer le fichier

Stack obligatoire (coller ce boilerplate, adapter les CDN) :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>[Nom du projet]</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=[Font1]:wght@300;400;500;600;700&family=[Font2]:wght@300;400;500;600;700&display=swap" rel="stylesheet">
    <script src="https://unpkg.com/lucide@latest"></script>
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    fontFamily: {
                        heading: ['[Font1]', 'sans-serif'],
                        body: ['[Font2]', 'sans-serif']
                    },
                    colors: { /* palette custom */ }
                }
            }
        }
    </script>
    <style>
        /* keyframes, scrollbar, gradients complexes ici */
        [data-animate] { opacity:0; transform:translateY(20px); transition:opacity .6s ease,transform .6s ease; }
        [data-animate].animate-in { opacity:1; transform:translateY(0); }
        .hover-lift { transition:transform .2s ease,box-shadow .2s ease; }
        .hover-lift:hover { transform:translateY(-2px); box-shadow:0 8px 25px -5px rgba(0,0,0,.12); }
        @keyframes gradient-shift { 0%,100%{background-position:0% 50%} 50%{background-position:100% 50%} }
        .animate-gradient { background-size:200% 200%; animation:gradient-shift 8s ease infinite; }
    </style>
</head>
<body>
    <!-- Structure sémantique et accessible -->
    <script>
        lucide.createIcons();
        /* JS interactivité ici */
    </script>
</body>
</html>

Commande de livraison :

# Écrire le fichier via l'outil Write
# Chemin cible : ~/Desktop/[nom-projet]-design.html
# Ouvrir ensuite :
open ~/Desktop/[nom-projet]-design.html          # macOS
start ~/Desktop/[nom-projet]-design.html         # Windows
xdg-open ~/Desktop/[nom-projet]-design.html      # Linux

Étape 4 — Itérer

Après génération, proposer exactement 3 axes d'itération concrets :

"Veux-tu que je : (A) modifie la palette et les typos, (B) ajoute une section [X], (C) adapte pour mobile uniquement ?"

Patterns par type d'interface

Landing Page

Pré-design obligatoire — 3 questions mentales :

  1. Quelle est la promesse en une phrase ? (Outcome, pas feature)
  2. Quel est l'état douloureux avant le produit ?
  3. Qui sont les 3 personas ?

Structure canonique :

1. Header sticky          — logo + nav + CTA
2. Hero                   — headline outcome, sous-titre, CTA unique, visuel produit
3. Social proof bar       — logos clients ou stats (pas de témoignages longs ici)
4. Problème / Solution    — 3 étapes "Comment ça marche"
5. Features principales   — 3 blocs alternatifs texte/visuel
6. Features secondaires   — grille de 6 cards avec icônes Lucide
7. Témoignages            — 3 cards avec photo, nom, rôle, citation
8. Pricing                — 2-3 tiers, plan recommandé mis en valeur
9. FAQ                    — accordéons JS
10. CTA final             — headline, reassurance, CTA
11. Footer                — logo + colonnes liens + copyright

Règles hero strictes :

Dashboard / Web App

Layout sidebar + content (base) :

<div class="flex h-screen bg-gray-50 font-body">
    <!-- Sidebar 260px -->
    <aside class="w-[260px] bg-white border-r flex flex-col shrink-0">
        <div class="p-5 border-b"><!-- Logo 32px --></div>
        <nav class="flex-1 p-3 space-y-1 overflow-y-auto">
            <!-- Nav item actif -->
            <a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-indigo-50 text-indigo-700 font-medium text-sm">
                <i data-lucide="layout-dashboard" class="w-4 h-4"></i> Dashboard
            </a>
            <!-- Nav item inactif -->
            <a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-100 text-sm">
                <i data-lucide="users" class="w-4 h-4"></i> Utilisateurs
            </a>
        </nav>
        <div class="p-4 border-t"><!-- Avatar + nom + rôle --></div>
    </aside>
    <!-- Content -->
    <main class="flex-1 overflow-auto">
        <header class="h-14 bg-white border-b flex items-center justify-between px-8">
            <!-- Breadcrumb + actions droite -->
        </header>
        <div class="p-8 space-y-6"><!-- Contenu --></div>
    </main>
</div>

KPI Cards pattern :

<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
    <div class="bg-white rounded-xl border p-6 space-y-1">
        <p class="text-xs font-medium text-gray-500 uppercase tracking-wide">Revenus</p>
        <p class="text-3xl font-semibold text-gray-900" data-counter="128450">0</p>
        <p class="text-xs text-emerald-600 font-medium">↑ 12,5 % vs mois précédent</p>
    </div>
</div>

Hiérarchie boutons :

PrioritéClasses TailwindUsage
Primarybg-indigo-600 text-white hover:bg-indigo-700 px-4 py-2 rounded-lg text-sm font-mediumSave, Submit
Secondarybg-gray-100 text-gray-700 hover:bg-gray-200 px-4 py-2 rounded-lg text-sm font-mediumAlternatives
Outlineborder border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-mediumCancel, Back
Ghosttext-gray-500 hover:text-gray-700 hover:bg-gray-100 px-3 py-1.5 rounded-lg text-smActions inline
Destructivebg-red-600 text-white hover:bg-red-700 px-4 py-2 rounded-lg text-sm font-mediumDelete (avec confirm)

Largeurs colonnes de table :

TypeTailwind
Nom / libellé principalflex-1 ou min-w-[200px]
Email, URLw-[220px]
Badge statutw-[110px]
Datew-[130px]
Montantw-[100px] text-right
Actionsw-[80px] text-right

App Mobile

<div class="max-w-[393px] mx-auto bg-white min-h-screen flex flex-col font-body">
    <!-- Status bar -->
    <div class="h-[62px] flex items-center justify-between px-6 text-sm font-semibold shrink-0"
         style="font-family:'SF Pro Display','Inter',sans-serif">
        <span>9:41</span>
        <div class="flex gap-1.5 items-center">
            <i data-lucide="signal" class="w-3.5 h-3.5"></i>
            <i data-lucide="wifi" class="w-3.5 h-3.5"></i>
            <i data-lucide="battery" class="w-3.5 h-3.5"></i>
        </div>
    </div>
    <!-- Contenu scrollable -->
    <div class="flex-1 overflow-auto px-4 pb-28 space-y-5"><!-- content --></div>
    <!-- Tab bar pill -->
    <div class="fixed bottom-0 left-1/2 -translate-x-1/2 w-[393px] px-5 pb-5 pt-2 bg-white">
        <nav class="h-[62px] bg-gray-900 rounded-[36px] border border-gray-800 px-1 flex items-center">
            <!-- Actif : bg-indigo-600 rounded-[26px] -->
            <!-- Inactif : transparent, text-gray-400 -->
        </nav>
    </div>
</div>

Règles mobiles :

Formulaire

<form class="space-y-5 max-w-lg">
    <!-- Double colonne -->
    <div class="grid grid-cols-2 gap-4">
        <div>
            <label class="block text-sm font-medium text-gray-700 mb-1.5">Prénom</label>
            <input type="text"
                   class="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm
                          focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none
                          placeholder:text-gray-400"
                   placeholder="Jean">
        </div>
        <div>
            <label class="block text-sm font-medium text-gray-700 mb-1.5">Nom</label>
            <input type="text"
                   class="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm
                          focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
                   placeholder="Dupont">
        </div>
    </div>
    <!-- Champ erreur -->
    <div>
        <label class="block text-sm font-medium text-gray-700 mb-1.5">Email</label>
        <input type="email"
               class="w-full rounded-lg border border-red-300 bg-red-50 px-4 py-2.5 text-sm outline-none"
               value="jean@">
        <p class="mt-1.5 text-xs text-red-600">Adresse email invalide.</p>
    </div>
    <!-- Actions -->
    <div class="flex justify-end gap-3 pt-2">
        <button type="button" class="px-4 py-2.5 rounded-lg border border-gray-300 text-sm text-gray-700 hover:bg-gray-50">Annuler</button>
        <button type="submit" class="px-4 py-2.5 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700">Enregistrer</button>
    </div>
</form>

JS vanilla — snippets copiables

// Init Lucide (toujours en premier)
lucide.createIcons();

// Accordéons (FAQ, expandables)
document.querySelectorAll('[data-accordion]').forEach(btn => {
    btn.addEventListener('click', () => {
        const content = btn.nextElementSibling;
        const icon = btn.querySelector('[data-accordion-icon]');
        content.classList.toggle('hidden');
        icon?.classList.toggle('rotate-180');
    });
});

// Tabs
document.querySelectorAll('[data-tab]').forEach(tab => {
    tab.addEventListener('click', () => {
        const target = tab.dataset.tab;
        document.querySelectorAll('[data-tab]').forEach(t =>
            t.classList.remove('border-indigo-600', 'text-indigo-600'));
        tab.classList.add('border-indigo-600', 'text-indigo-600');
        document.querySelectorAll('[data-tab-content]').forEach(c => c.classList.add('hidden'));
        document.querySelector(`[data-tab-content="${target}"]`)?.classList.remove('hidden');
    });
});

// Compteurs animés (KPIs)
function animateCounter(el, target, duration = 1400) {
    let start;
    const step = ts => {
        if (!start) start = ts;
        const p = Math.min((ts - start) / duration, 1);
        el.textContent = Math.floor(p * target).toLocaleString('fr-FR');
        if (p < 1) requestAnimationFrame(step);
    };
    requestAnimationFrame(step);
}
document.querySelectorAll('[data-counter]').forEach(el =>
    animateCounter(el, parseInt(el.dataset.counter)));

// Animations au scroll
const obs = new IntersectionObserver(entries => {
    entries.forEach(e => {
        if (e.isIntersecting) { e.target.classList.add('animate-in'); obs.unobserve(e.target); }
    });
}, { threshold: 0.1 });
document.querySelectorAll('[data-animate]').forEach(el => obs.observe(el));

// Modal (data-modal-open="#modal-id" / data-modal-close)
document.querySelectorAll('[data-modal-open]').forEach(btn =>
    btn.addEventListener('click', () =>
        document.querySelector(btn.dataset.modalOpen)?.classList.remove('hidden')));
document.querySelectorAll('[data-modal-close]').forEach(btn =>
    btn.addEventListener('click', () => btn.closest('[data-modal]')?.classList.add('hidden')));

// Menu hamburger mobile
document.querySelector('[data-toggle-menu]')?.addEventListener('click', () =>
    document.querySelector('[data-menu]')?.classList.toggle('hidden'));

// Dark/light toggle
document.querySelector('[data-theme-toggle]')?.addEventListener('click', () =>
    document.documentElement.classList.toggle('dark'));

Garde-fous et anti-patterns

Anti-patternCorrection
Fond plat uniformeGradient léger ou texture noise sur les sections héro
Lorem ipsumContenu réaliste et contextuel — noms, chiffres, descriptions métier
Même combo typo que la génération précédentePiocher dans la table des polices distinctives
CTA multiples dans le héro1 CTA primaire, 1 secondaire optionnel (ghost)
Colonnes de table sans largeur fixeFixer les colonnes secondaires, laisser la principale en flex-1
JS inline sur attributs onclick=Sélecteurs data-* + addEventListener
Icônes Lucide sans lucide.createIcons()Toujours appeler createIcons() en fin de <body>
Tab bar mobile avec > 5 ongletsMaximum 5 onglets, fusionner les sections secondaires
Texte sur image de fondOverlay gradient ou déplacer le texte à côté
Focus states absentsAjouter focus:ring-2 focus:ring-[accent] sur tous les interactifs
Breakpoints desktop uniquementToujours sm/md/lg, tester à 375px et 768px
Fichier avec dépendance npm/bundlerTout inline — CDN uniquement, pas de import ES modules

Checklist qualité avant livraison


Livraison

# 1. Écrire le fichier (outil Write)
# Chemin : ~/Desktop/[nom-projet]-design.html

# 2. Ouvrir dans le navigateur
open ~/Desktop/[nom-projet]-design.html           # macOS
start ~/Desktop/[nom-projet]-design.html          # Windows

# 3. Proposer 3 axes d'itération concrets