Focus Grid

Grid of items that can be focused; supports dimming unfocused items and showing description only on focus.

Loading block…

Implementation

npx shadcn@latest add @flx/focus-grid

Variations

The block supports two props that change behavior: dimUnfocused (dim non-focused items) and descriptionOnFocus (show description only on focused item). Below are defaults and usage per variation.

Default

Loading block…

import { FocusGrid, type FocusGridProps } from './focus-grid'

export function FocusGridExample() {
  const values = {
    title: 'Our process',
    description: 'From idea to launch—click each phase to see how we work.',
    dimUnfocused: false,
    descriptionOnFocus: true,
    items: [
      {
        image:
          'https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=1099&auto=format&fit=crop',
        title: 'Discovery',
        description:
          'We start by understanding your goals, your users, and the constraints you face.',
        defaultFocus: true,
      },
      {
        image:
          'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=1170&auto=format&fit=crop',
        title: 'Design',
        description:
          'Clean interfaces and clear flows that people love to use every day.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1587563871167-1ee9c731aefb?q=80&w=1131&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Build',
        description:
          'Solid, accessible code that scales with your product and your team.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1713623069173-3afa89926882?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Test',
        description:
          'We validate everything with real users before launch, so nothing is left to chance.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1625245488600-f03fef636a3c?q=80&w=1074&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Launch',
        description:
          'Go live with confidence, with support and iteration when you need it.',
      },
    ],
  } satisfies FocusGridProps

  return (
    <FocusGrid
      title={values.title}
      description={values.description}
      dimUnfocused={values.dimUnfocused}
      descriptionOnFocus={values.descriptionOnFocus}
      items={values.items}
    />
  )
}

Focused items are dimmed

When dimUnfocused is true, unfocused items are dimmed while another item is focused.

Loading block…

focus-grid-dim-unfocused.tsx
import { FocusGrid, type FocusGridProps } from '../focus-grid'

export function FocusGridDimUnfocused() {
  const values = {
    title: 'Our process',
    description: 'From idea to launch—click each phase to see how we work.',
    dimUnfocused: true,
    descriptionOnFocus: true,
    items: [
      {
        image:
          'https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=1099&auto=format&fit=crop',
        title: 'Discovery',
        description:
          'We start by understanding your goals, your users, and the constraints you face.',
        defaultFocus: true,
      },
      {
        image:
          'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=1170&auto=format&fit=crop',
        title: 'Design',
        description:
          'Clean interfaces and clear flows that people love to use every day.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1587563871167-1ee9c731aefb?q=80&w=1131&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Build',
        description:
          'Solid, accessible code that scales with your product and your team.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1713623069173-3afa89926882?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Test',
        description:
          'We validate everything with real users before launch, so nothing is left to chance.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1625245488600-f03fef636a3c?q=80&w=1074&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Launch',
        description:
          'Go live with confidence, with support and iteration when you need it.',
      },
    ],
  } satisfies FocusGridProps

  return (
    <FocusGrid
      title={values.title}
      description={values.description}
      dimUnfocused={values.dimUnfocused}
      descriptionOnFocus={values.descriptionOnFocus}
      items={values.items}
    />
  )
}

Always show descriptions

When descriptionOnFocus is false, the description is always shown for all items.

Loading block…

focus-grid-description-always.tsx
import { FocusGrid, type FocusGridProps } from '../focus-grid'

export function FocusGridDescriptionAlways() {
  const values = {
    title: 'Our process',
    description: 'From idea to launch—click each phase to see how we work.',
    dimUnfocused: false,
    descriptionOnFocus: false,
    items: [
      {
        image:
          'https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=1099&auto=format&fit=crop',
        title: 'Discovery',
        description:
          'We start by understanding your goals, your users, and the constraints you face.',
        defaultFocus: true,
      },
      {
        image:
          'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=1170&auto=format&fit=crop',
        title: 'Design',
        description:
          'Clean interfaces and clear flows that people love to use every day.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1587563871167-1ee9c731aefb?q=80&w=1131&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Build',
        description:
          'Solid, accessible code that scales with your product and your team.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1713623069173-3afa89926882?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Test',
        description:
          'We validate everything with real users before launch, so nothing is left to chance.',
      },
      {
        image:
          'https://images.unsplash.com/photo-1625245488600-f03fef636a3c?q=80&w=1074&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
        title: 'Launch',
        description:
          'Go live with confidence, with support and iteration when you need it.',
      },
    ],
  } satisfies FocusGridProps

  return (
    <FocusGrid
      title={values.title}
      description={values.description}
      dimUnfocused={values.dimUnfocused}
      descriptionOnFocus={values.descriptionOnFocus}
      items={values.items}
    />
  )
}

Use fewer items for a cleaner, minimalist layout. The grid adjusts item widths to fit the content.

Loading block…

focus-grid-minimalist.tsx
import { FocusGrid, type FocusGridProps } from '../focus-grid'

export function FocusGridMinimalist() {
  const values = {
    title: 'Our process',
    description: 'From idea to launch—click each phase to see how we work.',
    dimUnfocused: false,
    descriptionOnFocus: true,
    items: [
      {
        image:
          'https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=1099&auto=format&fit=crop',
        title: 'Discovery',
        description:
          'We start by understanding your goals, your users, and the constraints you face.',
        defaultFocus: true,
      },
      {
        image:
          'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=1170&auto=format&fit=crop',
        title: 'Design',
        description:
          'Clean interfaces and clear flows that people love to use every day.',
      },
    ],
  } satisfies FocusGridProps

  return (
    <FocusGrid
      title={values.title}
      description={values.description}
      dimUnfocused={values.dimUnfocused}
      descriptionOnFocus={values.descriptionOnFocus}
      items={values.items}
    />
  )
}