smithery/ainergiz

expandable-card

Creates expandable/collapsible cards using CSS grid-rows animation with smooth transitions. Use when building accordions, expandable panels, collapsible sections, or show/hide card content.

Installation

$ npx skills add smithery/ainergiz --skill expandable-card

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/ainergiz.

npx skills add smithery/ainergiz

Browse all from smithery/ainergiz

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,643 B
  • docs SUMMARY.md 212 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Expandable Card Pattern

Build smooth expand/collapse animations using CSS grid-rows, avoiding height:auto animation issues.

Why grid-rows?

Traditional height animation requires explicit pixel values. The grid-rows technique allows smooth animation to/from auto height:

  • grid-rows-[0fr] + overflow-hidden = collapsed (0 height)
  • grid-rows-[1fr] = expanded (natural height)

Core Implementation

"use client";

import { useState } from "react";
import { ChevronDown } from "lucide-react";

function ExpandableCard() {
  const [expanded, setExpanded] = useState(true);

  return (
    <div className="rounded-xl border overflow-hidden transition-all duration-300">
      {/* Header - clickable toggle */}
      <div
        className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-zinc-50 transition-colors"
        onClick={() => setExpanded(!expanded)}
      >
        <span className="font-medium">Card Title</span>
        <ChevronDown
          className={`w-5 h-5 transition-transform duration-200 ${
            expanded ? "rotate-180" : ""
          }`}
        />
      </div>

      {/* Content - animated container */}
      <div
        className={`grid transition-all duration-300 ease-in-out ${
          expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
        }`}
      >
        <div className="overflow-hidden">
          <div className="px-4 py-4 border-t">
            {/* Your content here */}
            <p>Expandable content goes here.</p>
          </div>
        </div>
      </div>
    </div>
  );
}

Key Elements

1. State Management

const [expanded, setExpanded] = useState(true); // Start expanded
// or
const [expanded, setExpanded] = useState(false); // Start collapsed

2. Header Click Handler

<div
  className="cursor-pointer hover:bg-zinc-50 transition-colors"
  onClick={() => setExpanded(!expanded)}
>

3. ChevronDown Rotation

<ChevronDown
  className={`transition-transform duration-200 ${
    expanded ? "rotate-180" : ""
  }`}
/>

4. Grid Container Animation

<div
  className={`grid transition-all duration-300 ease-in-out ${
    expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
  }`}
>
  <div className="overflow-hidden">
    {/* Content wrapper - REQUIRED for animation */}
  </div>
</div>

Timing Recommendations

Duration Use Case
duration-150 Small cards, quick feedback
duration-200 Chevron rotation
duration-300 Content expansion (recommended)
duration-500 Large content areas

Shadow Transition (Optional)

Add shadow that changes with state:

<div
  className={`rounded-xl border overflow-hidden transition-all duration-300 ${
    expanded ? "shadow-lg" : "shadow-sm hover:shadow-md"
  }`}
>

Accessibility Considerations

<div
  role="button"
  tabIndex={0}
  aria-expanded={expanded}
  onClick={() => setExpanded(!expanded)}
  onKeyDown={(e) => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      setExpanded(!expanded);
    }
  }}
>

Common Variations

Multiple Cards (Accordion)

const [expandedId, setExpandedId] = useState<string | null>("first");

// Toggle logic
onClick={() => setExpandedId(expandedId === id ? null : id)}

Nested Content Protection

Prevent clicks on interactive content from toggling:

<a
  href="..."
  onClick={(e) => e.stopPropagation()}
>

Important: Always add stopPropagation to:

  • Links (<a>)
  • Buttons that perform actions other than toggling
  • Form inputs
  • Any interactive element that shouldn't trigger expand/collapse
// Full example with multiple interactive elements
<div className="overflow-hidden">
  <div className="px-4 py-4 border-t">
    <a
      href="https://example.com"
      onClick={(e) => e.stopPropagation()}
      className="text-blue-500 hover:underline"
    >
      External link
    </a>

    <button
      onClick={(e) => {
        e.stopPropagation();
        // Handle button action
      }}
    >
      Action Button
    </button>
  </div>
</div>

Checklist

  • overflow-hidden on inner wrapper (required for animation)
  • transition-all on grid container
  • ChevronDown has transition-transform
  • Header has cursor-pointer and hover state
  • Timing consistent (300ms recommended)