Code hacks

by

Anoop

Controlled vs uncontrolled components

Every interactive Framer component eventually faces the same question:

who owns the state — the component itself or Framer?

That is the difference between a controlled and an uncontrolled component. Understanding this makes your components predictable, reusable, and compatible with data bindings.

What it means

A controlled component gets its value from props. The parent (Framer or CMS data) decides what the value is.

An uncontrolled component manages its own internal state. It decides what to do when a user interacts with it.

In Framer, this distinction matters because users can connect properties like activeIndex, isOpen, or value to external data.

If your component does not respect controlled mode, it can get out of sync or ignore data bindings entirely.

Example

This example shows a simple accordion item that works in both modes.

// @framerSupportedLayoutWidth any-prefer-fixed
// @framerSupportedLayoutHeight auto

import * as React from "react"
import { addPropertyControls, ControlType } from "framer"

type Props = {
  title?: string
  content?: string
  isOpen?: boolean
  onChange?: (next: boolean) => void
}

export default function AccordionItem({
  title = "Question",
  content = "Answer text goes here",
  isOpen,
  onChange,
}: Props) {
  const [internalOpen, setInternalOpen] = React.useState(false)
  const controlled = isOpen !== undefined
  const open = controlled ? isOpen : internalOpen

  function toggle() {
    const next = !open
    if (controlled) onChange?.(next)
    else setInternalOpen(next)
  }

  return (
    <divstyle={{ border:="" "1px="" solid="" #e5e5e5",="" borderradius:="" 8,="" overflow:="" "hidden",="" }}="">
      <buttononclick={toggle} style="{{" width:="" "100%",="" padding:="" "12px="" 16px",="" background:="" "#f8f8f8",="" fontsize:="" 16,="" textalign:="" "left",="" cursor:="" "pointer",="" border:="" "none",="" }}="">
        {title}
      
      {open && (
        <div style="{{" padding:="" "12px="" 16px",="" fontsize:="" 14,="" color:="" "#333"="" }}="">
          {content}
        </div>
      )}
    
  )
}

addPropertyControls(AccordionItem, {
  title: { type: ControlType.String, title: "Title", defaultValue: "Question" },
  content: {
    type: ControlType.String,
    title: "Content",
    defaultValue: "Answer text goes here",
  },
  isOpen: { type: ControlType.Boolean, title: "Is Open" },
})

</buttononclick={toggle}></divstyle={{

When isOpen is not connected, the component toggles itself and stores state internally.

When a designer binds isOpen to a variable, it becomes controlled and expects the parent to manage its state through onChange.

Why it matters

Controlled mode allows Framer bindings, CMS data, or external logic to drive your component’s behavior.

Uncontrolled mode keeps it simple when used as a standalone component.

Handling both makes your components feel professional and flexible.

Pro tip

Use a consistent pattern to detect control:

const controlled = prop !== undefined
const value = controlled ? prop : internalState

This single check prevents desyncs, double updates, and unpredictable behavior.

Your component should never fight its props — it should cooperate with them.

Code components

Beautiful video player

Free

Beautiful video player

Free

Beautiful video player

Free

HLS video player

$19

HLS video player

$19

HLS video player

$19

Dot Matrix - SVG

Free

Dot Matrix - SVG

Free

Dot Matrix - SVG

Free

FAQs accordion

Free

FAQs accordion

Free

FAQs accordion

Free

Launch count down timer

Free

Launch count down timer

Free

Launch count down timer

Free

View port width

Free

View port width

Free

View port width

Free

Theme switcher

Free

Theme switcher

Free

Theme switcher

Free