Skip to content

Collapse

Show and hide content with smooth animations. Perfect for FAQs, expandable sections, and "read more" functionality. Built for Pulse Framework with full reactivity support.

Import

tsx
import { Collapse, Pulse } from '@odyssee/components';

Basic Usage

Code Éditable
Résultat

Read More Pattern

A common pattern for showing additional content.

Code Éditable
Résultat

Trigger Variants

The Collapse component supports two trigger variants: button and link.

Code Éditable
Résultat

With Icon

Control the visibility of the chevron icon.

Code Éditable
Résultat

Reactive State

Control collapse state with Pulse signals.

tsx
const isOpen = Pulse.signal(false);

const toggleButton = (
  <button onClick={() => isOpen(!isOpen())}>
    External Toggle
  </button>
);

const reactiveCollapse = (
  <div>
    {toggleButton}
    <Collapse
      trigger="Controlled Collapse"
      isOpen={isOpen}
    >
      <p>This collapse is controlled by an external button</p>
    </Collapse>
  </div>
);

With Callbacks

React to state changes with callback functions.

tsx
const collapse = (
  <Collapse
    trigger="Collapse with Callbacks"
    onToggle={(isOpen) => {
      console.log('Toggled:', isOpen);
    }}
    onOpen={() => {
      console.log('Opened!');
    }}
    onClose={() => {
      console.log('Closed!');
    }}
  >
    <p>Check console for callback logs</p>
  </Collapse>
);

Custom Animation Duration

Adjust the transition speed.

tsx
const fastCollapse = (
  <Collapse
    trigger="Fast Animation"
    duration={150}
  >
    <p>This collapses and expands quickly</p>
  </Collapse>
);

const slowCollapse = (
  <Collapse
    trigger="Slow Animation"
    duration={600}
  >
    <p>This has a slower, more gradual animation</p>
  </Collapse>
);

FAQ Accordion

Build FAQ sections with multiple collapse components.

tsx
const FAQSection = () => {
  const faq1Open = Pulse.signal(false);
  const faq2Open = Pulse.signal(false);
  const faq3Open = Pulse.signal(false);

  return (
    <div class="space-y-3">
      <div class="border border-gray-200 rounded-lg p-4 dark:border-gray-700">
        <Collapse
          trigger="What is Pulse Framework?"
          isOpen={faq1Open}
          triggerVariant="link"
          triggerClassName="font-semibold text-gray-800 dark:text-white"
        >
          <p class="mt-2 text-gray-600 dark:text-gray-400">
            Pulse is a lightweight reactive framework for building modern web applications.
          </p>
        </Collapse>
      </div>

      <div class="border border-gray-200 rounded-lg p-4 dark:border-gray-700">
        <Collapse
          trigger="How do I get started?"
          isOpen={faq2Open}
          triggerVariant="link"
          triggerClassName="font-semibold text-gray-800 dark:text-white"
        >
          <p class="mt-2 text-gray-600 dark:text-gray-400">
            Install the package via npm and follow our quick start guide.
          </p>
        </Collapse>
      </div>

      <div class="border border-gray-200 rounded-lg p-4 dark:border-gray-700">
        <Collapse
          trigger="Is it compatible with TypeScript?"
          isOpen={faq3Open}
          triggerVariant="link"
          triggerClassName="font-semibold text-gray-800 dark:text-white"
        >
          <p class="mt-2 text-gray-600 dark:text-gray-400">
            Yes! Pulse has full TypeScript support with complete type definitions.
          </p>
        </Collapse>
      </div>
    </div>
  );
};

Complete Example

Here's a comprehensive example with multiple features:

tsx
import { Collapse, Card, Badge, Pulse } from '@odyssee/components';

const ProductDetails = () => {
  const specsOpen = Pulse.signal(false);
  const reviewsOpen = Pulse.signal(false);
  const shippingOpen = Pulse.signal(false);

  return (
    <Card title="Product Information" size="lg">
      {/* Product overview */}
      <p class="mb-6 text-gray-600">
        Premium wireless headphones with active noise cancellation...
      </p>

      {/* Collapsible sections */}
      <div class="space-y-4">
        {/* Specifications */}
        <div class="border-b border-gray-200 pb-4 dark:border-gray-700">
          <Collapse
            trigger="Technical Specifications"
            isOpen={specsOpen}
            triggerVariant="link"
            triggerClassName="font-semibold text-lg"
            onOpen={() => console.log('Specs opened')}
          >
            <div class="mt-4 space-y-2">
              <div class="flex justify-between">
                <span class="text-gray-600">Battery Life:</span>
                <span class="font-medium">30 hours</span>
              </div>
              <div class="flex justify-between">
                <span class="text-gray-600">Bluetooth:</span>
                <span class="font-medium">5.2</span>
              </div>
              <div class="flex justify-between">
                <span class="text-gray-600">Weight:</span>
                <span class="font-medium">250g</span>
              </div>
            </div>
          </Collapse>
        </div>

        {/* Reviews */}
        <div class="border-b border-gray-200 pb-4 dark:border-gray-700">
          <Collapse
            trigger={
              <div class="flex items-center gap-2">
                <span>Customer Reviews</span>
                <Badge variant="soft" color="success" size="sm">
                  4.8/5
                </Badge>
              </div>
            }
            isOpen={reviewsOpen}
            triggerVariant="link"
            triggerClassName="font-semibold text-lg"
          >
            <div class="mt-4 space-y-3">
              <p class="text-gray-600">
                "Amazing sound quality and comfort!" - ⭐⭐⭐⭐⭐
              </p>
              <p class="text-gray-600">
                "Best headphones I've ever owned." - ⭐⭐⭐⭐⭐
              </p>
            </div>
          </Collapse>
        </div>

        {/* Shipping */}
        <div>
          <Collapse
            trigger="Shipping & Returns"
            isOpen={shippingOpen}
            triggerVariant="link"
            triggerClassName="font-semibold text-lg"
          >
            <div class="mt-4">
              <p class="text-gray-600 mb-2">
                Free shipping on orders over $50
              </p>
              <p class="text-gray-600">
                30-day return policy with full refund
              </p>
            </div>
          </Collapse>
        </div>
      </div>
    </Card>
  );
};

Separate Trigger and Content

For more control, use CollapseTrigger and CollapseContent separately.

tsx
const isOpen = Pulse.signal(false);
const contentId = 'custom-collapse-content';

const separateComponents = (
  <div>
    <CollapseTrigger
      targetId={contentId}
      isOpen={isOpen}
      variant="button"
    >
      Custom Trigger
    </CollapseTrigger>

    <CollapseContent
      id={contentId}
      isOpen={isOpen}
    >
      <p>Content controlled by separate trigger</p>
    </CollapseContent>
  </div>
);

Props

Collapse Props

PropTypeDefaultDescription
triggerstring | HTMLElement-Trigger button content
childrenstring | HTMLElement | Array-Collapsible content
isOpenboolean | Signal<boolean>falseControl open/closed state
openTextstring-Text when open (alternative to trigger)
closedTextstring-Text when closed (alternative to trigger)
showIconbooleantrueShow chevron icon
triggerVariant"button" | "link""button"Trigger style variant
triggerClassNamestring-Additional classes for trigger
durationnumber300Animation duration in ms
onToggle(isOpen: boolean) => void-Called when toggled
onOpen() => void-Called when opened
onClose() => void-Called when closed
classNamestring-Additional CSS classes for content
idstringAuto-generatedHTML id attribute

CollapseTrigger Props

PropTypeDefaultDescription
targetIdstringRequiredID of content to control
childrenstring | HTMLElement-Trigger content
isOpenboolean | Signal<boolean>falseControl open state
showIconbooleantrueShow chevron icon
variant"button" | "link""button"Trigger style
onToggle(isOpen: boolean) => void-Toggle callback
classNamestring-Additional CSS classes

CollapseContent Props

PropTypeDefaultDescription
childrenstring | HTMLElement | Array-Content to collapse
isOpenboolean | Signal<boolean>falseControl visibility
durationnumber300Animation duration in ms
triggerIdstring-ID of controlling trigger
classNamestring-Additional CSS classes
idstringAuto-generatedHTML id attribute

Accessibility

The Collapse component follows accessibility best practices:

  • ✅ Proper aria-expanded attribute on trigger
  • aria-controls linking trigger to content
  • aria-labelledby linking content to trigger
  • ✅ Keyboard navigation support
  • ✅ Focus management
  • ✅ Semantic HTML structure

Best Practices

✅ Do

  • Use meaningful trigger text
  • Keep collapsed content concise
  • Use appropriate animation speeds
  • Provide visual feedback (icons)
  • Group related collapsible sections
tsx
// Good: Clear trigger and organized content
const goodCollapse = (
  <Collapse
    trigger="View Product Details"
    triggerVariant="link"
  >
    <div class="mt-2">
      <p>Organized, scannable content...</p>
    </div>
  </Collapse>
);

❌ Don't

  • Don't hide critical information by default
  • Don't nest collapses too deeply
  • Don't use very fast animations (jarring)
  • Don't forget to handle state properly
  • Don't use for navigation (use accordions)
tsx
// Bad: Critical info hidden, vague trigger
const badCollapse = (
  <Collapse trigger="Click here" duration={50}>
    <p>IMPORTANT: Your account will be deleted!</p>
  </Collapse>
);

// Better: Clear and visible
const betterAlert = (
  <Alert color="danger" dismissible={false}>
    <p>IMPORTANT: Your account will be deleted!</p>
  </Alert>
);

Use Cases

Blog Post Excerpt

tsx
const BlogPostExcerpt = ({ content }) => (
  <div>
    <p>{content.excerpt}</p>
    <Collapse
      openText="Show less"
      closedText="Continue reading"
      triggerVariant="link"
    >
      <div class="mt-4">
        {content.fullText}
      </div>
    </Collapse>
  </div>
);

Terms and Conditions

tsx
const TermsSection = () => (
  <div class="space-y-2">
    <Collapse
      trigger="Privacy Policy"
      triggerVariant="link"
      triggerClassName="text-blue-600 hover:underline"
    >
      <div class="mt-2 p-4 bg-gray-50 rounded">
        <p>Privacy policy content...</p>
      </div>
    </Collapse>

    <Collapse
      trigger="Terms of Service"
      triggerVariant="link"
      triggerClassName="text-blue-600 hover:underline"
    >
      <div class="mt-2 p-4 bg-gray-50 rounded">
        <p>Terms of service content...</p>
      </div>
    </Collapse>
  </div>
);

Filter Panel

tsx
const FilterPanel = () => {
  const filtersOpen = Pulse.signal(true);

  return (
    <Collapse
      trigger="Filters"
      isOpen={filtersOpen}
    >
      <div class="mt-4 space-y-3">
        <Checkbox label="In Stock" />
        <Checkbox label="On Sale" />
        <Checkbox label="Free Shipping" />
      </div>
    </Collapse>
  );
};

Styling & Theming

All collapse styles use Tailwind CSS classes and support dark mode automatically.

Custom Styling

tsx
const customCollapse = (
  <Collapse
    trigger="Custom Styled"
    triggerClassName="bg-blue-50 hover:bg-blue-100 p-3 rounded"
    className="bg-gray-50 p-4 rounded mt-2"
  >
    <p>Custom styled content</p>
  </Collapse>
);

TypeScript

Full TypeScript support with complete type definitions:

tsx
import type { CollapseProps } from '@odyssee/components';

const props: CollapseProps = {
  trigger: 'TypeScript Collapse',
  isOpen: false,
  duration: 300,
  onToggle: (isOpen: boolean) => {
    console.log('Toggled:', isOpen);
  }
};

const collapse = <Collapse {...props}>Content</Collapse>;
  • Accordion - Multiple collapsible sections
  • Modal - Full overlay content
  • Tabs - Switch between content panels

Version: 1.0.0
Last Updated: January 2025

Released under the MIT License.