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-software/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-software/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-software/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.