Docs
Sketch Accordion

Sketch Accordion

An interactive accordion component with a sketchbook-like appearance and paper texture animations.

Installation

Install the required dependencies.

npm install @radix-ui/react-accordion

Copy and paste the following code into your project.

components/ui/sketch-accordion.tsx

"use client";
 
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { cn } from "@/lib/utils";
 
const SketchAccordion = AccordionPrimitive.Root;
 
const SketchAccordionItem = React.forwardRef<
  React.ComponentRef<typeof AccordionPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
  <AccordionPrimitive.Item
    ref={ref}
    className={cn(
      "group relative mb-2 overflow-hidden rounded-lg border border-neutral-200 bg-white transition-all dark:border-neutral-800 dark:bg-neutral-950",
      "before:absolute before:inset-0 before:z-0 before:bg-[url('/paper-texture.png')] before:opacity-50 before:content-['']",
      className,
    )}
    {...props}
  />
));
SketchAccordionItem.displayName = "SketchAccordionItem";
 
const SketchAccordionTrigger = React.forwardRef<
  React.ComponentRef<typeof AccordionPrimitive.Trigger>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
  <AccordionPrimitive.Header className="flex">
    <AccordionPrimitive.Trigger
      ref={ref}
      className={cn(
        "group/trigger flex flex-1 items-center justify-between py-4 pl-4 pr-2 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
        "after:absolute after:left-0 after:top-0 after:h-full after:w-1 after:bg-neutral-200 after:content-[''] dark:after:bg-neutral-800",
        "hover:bg-neutral-50 dark:hover:bg-neutral-900",
        className,
      )}
      {...props}
    >
      {children}
      <div className="relative mr-2 h-5 w-5 shrink-0 overflow-hidden rounded-full border border-neutral-300 dark:border-neutral-700 cursor-pointer">
        <svg
          className="absolute inset-0 h-4 w-4 translate-x-0.5 translate-y-0.5 stroke-[1.5] transition-transform duration-200"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          style={{
            filter: "url(#pencil-stroke)",
          }}
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            d="M19 9l-7 7-7-7"
          />
        </svg>
      </div>
    </AccordionPrimitive.Trigger>
  </AccordionPrimitive.Header>
));
SketchAccordionTrigger.displayName = "SketchAccordionTrigger";
 
const SketchAccordionContent = React.forwardRef<
  React.ComponentRef<typeof AccordionPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
  <AccordionPrimitive.Content
    ref={ref}
    className="overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
    {...props}
  >
    <div
      className={cn(
        "relative px-4 pb-4 pt-0",
        "after:absolute after:left-0 after:top-0 after:h-full after:w-1 after:bg-neutral-200 after:content-[''] dark:after:bg-neutral-800",
        className,
      )}
    >
      {children}
    </div>
  </AccordionPrimitive.Content>
));
SketchAccordionContent.displayName = "SketchAccordionContent";
 
// SVG Filter for pencil effect
const PencilFilter = () => (
  <svg width="0" height="0">
    <defs>
      <filter id="pencil-stroke">
        <feTurbulence
          type="fractalNoise"
          baseFrequency="0.02"
          numOctaves="1"
          result="noise"
        />
        <feDisplacementMap in="SourceGraphic" in2="noise" scale="0.5" />
        <feGaussianBlur stdDeviation="0.2" />
      </filter>
    </defs>
  </svg>
);
 
export {
  SketchAccordion,
  SketchAccordionItem,
  SketchAccordionTrigger,
  SketchAccordionContent,
  PencilFilter,
};

Update the import paths to match your project setup.

import {
  SketchAccordion,
  SketchAccordionContent,
  SketchAccordionItem,
  SketchAccordionTrigger,
  PencilFilter,
} from "@/components/ui/sketch-accordion";

Update your styles.

The component uses Tailwind CSS for styling. Make sure your tailwind.config.js includes the following:

module.exports = {
  theme: {
    extend: {
      keyframes: {
        "accordion-down": {
          from: { height: 0 },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: 0 },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
};

Examples

Basic Usage

<SketchAccordion type="single" collapsible>
  <SketchAccordionItem value="item-1">
    <SketchAccordionTrigger>Section Title</SketchAccordionTrigger>
    <SketchAccordionContent>Section content goes here</SketchAccordionContent>
  </SketchAccordionItem>
</SketchAccordion>

Multiple Sections

<SketchAccordion type="multiple">
  <SketchAccordionItem value="item-1">
    <SketchAccordionTrigger>First Section</SketchAccordionTrigger>
    <SketchAccordionContent>First section content</SketchAccordionContent>
  </SketchAccordionItem>
  <SketchAccordionItem value="item-2">
    <SketchAccordionTrigger>Second Section</SketchAccordionTrigger>
    <SketchAccordionContent>Second section content</SketchAccordionContent>
  </SketchAccordionItem>
</SketchAccordion>

Custom Styling

<SketchAccordion type="single" collapsible>
  <SketchAccordionItem
    value="item-1"
    className="border-blue-200 dark:border-blue-800"
  >
    <SketchAccordionTrigger className="hover:bg-blue-50 dark:hover:bg-blue-900">
      Custom Styled Section
    </SketchAccordionTrigger>
    <SketchAccordionContent>Content with custom styling</SketchAccordionContent>
  </SketchAccordionItem>
</SketchAccordion>

Props

Sketch Accordion

Prop nameTypeDefaultDescription
type"single" | "multiple""single"Determines whether one or multiple items can be opened
collapsiblebooleanfalseWhen type is "single", allows closing content by clicking
valuestring-The controlled value of the opened item
defaultValuestring-The default value of the opened item
classNamestring-Additional CSS classes

Sketch Accordion Item

Prop nameTypeDefaultDescription
valuestring-A unique value for the accordion item
disabledbooleanfalseWhen true, prevents user interaction with the item
classNamestring-Additional CSS classes

Sketch Accordion Trigger

Prop nameTypeDefaultDescription
childrenReact.ReactNode-Content to display in trigger
classNamestring-Additional CSS classes

Sketch Accordion Content

Prop nameTypeDefaultDescription
childrenReact.ReactNode-Content to display when open
classNamestring-Additional CSS classes