Docs/Components/LayoutGroup

LayoutGroup

LayoutGroup scopes components that share layoutId values so they can animate together even when they live in different branches of the tree. Use it for shared underlines, sliding pills, and hero transitions.

import { LayoutGroup, motion } from 'motion-solidjs'

function Tabs() {
  return (
    <LayoutGroup id="tabs">
      <motion.span layoutId="active" />
    </LayoutGroup>
  )
}
layoutId
πŸ…
shared-layout-tabs.tsxOpen β†—
import { AnimatePresence, motion } from 'motion-solidjs'
import { For, Show, createSignal } from 'solid-js'

export const meta = {
  slug: 'shared-layout-tabs',
  title: 'Shared layout β€” tabs',
  category: 'layout',
  description: 'layoutId slides the active underline; AnimatePresence swaps the body.',
  tag: 'layoutId',
} as const

const tabs = [
  { id: 'tomato', icon: 'πŸ…', label: 'Tomato' },
  { id: 'lettuce', icon: 'πŸ₯¬', label: 'Lettuce' },
  { id: 'cheese', icon: 'πŸ§€', label: 'Cheese' },
] as const

type TabId = (typeof tabs)[number]['id']

export default function SharedLayoutTabs() {
  const [active, setActive] = createSignal<TabId>('tomato')
  const selected = () => tabs.find((t) => t.id === active())!

  return (
    <div class="flex w-72 flex-col overflow-hidden rounded-2xl border border-border bg-card">
      <nav class="border-b border-border px-1.5 pt-1.5">
        <ul class="flex gap-1" style={{ 'list-style': 'none', padding: 0, margin: 0 }}>
          <For each={tabs}>
            {(t) => (
              <motion.li
                class="relative flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-t-lg px-3 py-2 text-xs font-medium select-none"
                initial={false}
                animate={{
                  backgroundColor:
                    t.id === active() ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0)',
                }}
                onClick={() => setActive(t.id)}
              >
                <span>{t.icon}</span>
                <span>{t.label}</span>
                <Show when={t.id === active()}>
                  <motion.span
                    layoutId="shared-layout-tabs-underline"
                    class="absolute right-0 -bottom-px left-0 h-[2px] bg-grad-rose"
                    transition={{ type: 'spring', stiffness: 380, damping: 30 }}
                  />
                </Show>
              </motion.li>
            )}
          </For>
        </ul>
      </nav>
      <main class="flex h-32 items-center justify-center">
        <AnimatePresence mode="wait">
          <Show when={selected()} keyed>
            {(tab) => (
              <motion.div
                initial={{ y: 10, opacity: 0 }}
                animate={{ y: 0, opacity: 1 }}
                exit={{ y: -10, opacity: 0 }}
                transition={{ duration: 0.2 }}
                class="text-6xl"
              >
                {tab.icon}
              </motion.div>
            )}
          </Show>
        </AnimatePresence>
      </main>
    </div>
  )
}

Props

  • id - optional namespace; if omitted a stable one is generated
  • inherit - boolean (default true), 'id', or 'group'. true inherits both id and group from the surrounding LayoutGroup; 'id' and 'group' inherit only that half.

See also