Skip to content

Included and excluded checklist

What the product does and does not do, side by side.

Marketingstarterfeaturesscopelimitshonesty

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

This exact file renders the preview above.

import { Check, X } from 'lucide-react'
import { Container } from '@/components/ui/layout'

/**
 * Checklist split
 *
 * What the product does and does not do, side by side. Stating the limits
 * explicitly is more persuasive than a longer feature list, and it filters out
 * the people who would have churned.
 */
const included = [
  'Every component, section and starter as source',
  'Light and dark, authored separately',
  'Five palettes and three densities',
  'Keyboard and screen-reader contracts',
  'Server components with small client islands',
  'Documentation generated from the real files',
]

const excluded = [
  'A published npm package to install',
  'A backend, database or CMS',
  'Authentication or payment providers',
  'Charting or animation libraries',
  'A Figma library (the tokens map to variables)',
]

export default function ChecklistSplitFeatures() {
  return (
    <section className="border-b border-line bg-canvas py-section">
      <Container>
        <div className="max-w-2xl">
          <h2 className="display-type text-2xl font-semibold text-ink-strong sm:text-3xl">
            What is in the box — and what is not.
          </h2>
          <p className="mt-3 text-md text-ink-muted">
            Foundry is a template demonstration. Being explicit about the edges is part of the
            product.
          </p>
        </div>

        <div className="mt-10 grid gap-4 lg:grid-cols-2">
          <div className="rounded-xl border border-success-line bg-success-soft p-6">
            <h3 className="label-caps text-success">Included</h3>
            <ul className="mt-4 flex flex-col gap-3">
              {included.map((item) => (
                <li key={item} className="flex items-start gap-2.5 text-sm text-ink">
                  <Check className="mt-0.5 size-4 shrink-0 text-success" aria-hidden="true" />
                  {item}
                </li>
              ))}
            </ul>
          </div>

          <div className="rounded-xl border border-line bg-surface-sunken p-6">
            <h3 className="label-caps text-ink-muted">Not included</h3>
            <ul className="mt-4 flex flex-col gap-3">
              {excluded.map((item) => (
                <li key={item} className="flex items-start gap-2.5 text-sm text-ink-muted">
                  <X className="mt-0.5 size-4 shrink-0 text-ink-subtle" aria-hidden="true" />
                  {item}
                </li>
              ))}
            </ul>
          </div>
        </div>
      </Container>
    </section>
  )
}

components/blocks/sections/features/checklist-split.tsx

import { Check, X } from 'lucide-react'
import { Container } from '@/components/ui/layout'

/**
 * Checklist split
 *
 * What the product does and does not do, side by side. Stating the limits
 * explicitly is more persuasive than a longer feature list, and it filters out
 * the people who would have churned.
 */
const included = [
  'Every component, section and starter as source',
  'Light and dark, authored separately',
  'Five palettes and three densities',
  'Keyboard and screen-reader contracts',
  'Server components with small client islands',
  'Documentation generated from the real files',
]

const excluded = [
  'A published npm package to install',
  'A backend, database or CMS',
  'Authentication or payment providers',
  'Charting or animation libraries',
  'A Figma library (the tokens map to variables)',
]

export default function ChecklistSplitFeatures() {
  return (
    <section className="border-b border-line bg-canvas py-section">
      <Container>
        <div className="max-w-2xl">
          <h2 className="display-type text-2xl font-semibold text-ink-strong sm:text-3xl">
            What is in the box — and what is not.
          </h2>
          <p className="mt-3 text-md text-ink-muted">
            Foundry is a template demonstration. Being explicit about the edges is part of the
            product.
          </p>
        </div>

        <div className="mt-10 grid gap-4 lg:grid-cols-2">
          <div className="rounded-xl border border-success-line bg-success-soft p-6">
            <h3 className="label-caps text-success">Included</h3>
            <ul className="mt-4 flex flex-col gap-3">
              {included.map((item) => (
                <li key={item} className="flex items-start gap-2.5 text-sm text-ink">
                  <Check className="mt-0.5 size-4 shrink-0 text-success" aria-hidden="true" />
                  {item}
                </li>
              ))}
            </ul>
          </div>

          <div className="rounded-xl border border-line bg-surface-sunken p-6">
            <h3 className="label-caps text-ink-muted">Not included</h3>
            <ul className="mt-4 flex flex-col gap-3">
              {excluded.map((item) => (
                <li key={item} className="flex items-start gap-2.5 text-sm text-ink-muted">
                  <X className="mt-0.5 size-4 shrink-0 text-ink-subtle" aria-hidden="true" />
                  {item}
                </li>
              ))}
            </ul>
          </div>
        </div>
      </Container>
    </section>
  )
}

components/ui/layout.tsx

import type { ElementType, HTMLAttributes, ReactNode } from 'react'
import { cn } from '@/lib/cn'

/**
 * Layout primitives
 *
 * Five zero-JavaScript building blocks that account for the majority of
 * structure in the library. They exist so that spacing decisions are made once,
 * against density tokens, instead of being re-typed as ad-hoc utilities in
 * every section.
 */

export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {
  /** `prose` narrows to a comfortable reading measure. */
  size?: 'prose' | 'narrow' | 'default' | 'wide' | 'full'
  as?: ElementType
  /** Removes the responsive horizontal gutter. */
  bleed?: boolean
}

const containerSizes = {
  prose: 'max-w-[var(--layout-prose-max)]',
  narrow: 'max-w-3xl',
  default: 'max-w-[var(--layout-content-max)]',
  wide: 'max-w-[110rem]',
  full: 'max-w-none',
} as const

export function Container({
  size = 'default',
  as: Tag = 'div',
  bleed = false,
  className,
  children,
  ...props
}: ContainerProps) {
  return (
    <Tag
      className={cn(
        'mx-auto w-full',
        containerSizes[size],
        !bleed && 'px-4 sm:px-6 lg:px-8',
        className,
      )}
      {...props}
    >
      {children}
    </Tag>
  )
}

export interface StackProps extends HTMLAttributes<HTMLDivElement> {
  direction?: 'row' | 'column'
  gap?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
  align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline'
  justify?: 'start' | 'center' | 'end' | 'between' | 'around'
  wrap?: boolean
  as?: ElementType
}

const gapMap = {
  none: 'gap-0',
  xs: 'gap-1',
  sm: 'gap-2',
  md: 'gap-gap',
  lg: 'gap-stack',
  xl: 'gap-8',
} as const

const alignMap = {
  start: 'items-start',
  center: 'items-center',
  end: 'items-end',
  stretch: 'items-stretch',
  baseline: 'items-baseline',
} as const

const justifyMap = {
  start: 'justify-start',
  center: 'justify-center',
  end: 'justify-end',
  between: 'justify-between',
  around: 'justify-around',
} as const

export function Stack({
  direction = 'column',
  gap = 'md',
  align,
  justify,
  wrap = false,
  as: Tag = 'div',
  className,
  children,
  ...props
}: StackProps) {
  return (
    <Tag
      className={cn(
        'flex',
        direction === 'column' ? 'flex-col' : 'flex-row',
        gapMap[gap],
        align && alignMap[align],
        justify && justifyMap[justify],
        wrap && 'flex-wrap',
        className,
      )}
      {...props}
    >
      {children}
    </Tag>
  )
}

export interface GridProps extends HTMLAttributes<HTMLDivElement> {
  /** Column count at the largest breakpoint; smaller breakpoints step down. */
  cols?: 1 | 2 | 3 | 4 | 5 | 6
  gap?: StackProps['gap']
  as?: ElementType
  /** Auto-fit tracks with a minimum width instead of a fixed column count. */
  minItemWidth?: string
}

const colMap = {
  1: 'grid-cols-1',
  2: 'grid-cols-1 sm:grid-cols-2',
  3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
  4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',
  5: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5',
  6: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',
} as const

export function Grid({
  cols = 3,
  gap = 'lg',
  as: Tag = 'div',
  minItemWidth,
  className,
  style,
  children,
  ...props
}: GridProps) {
  return (
    <Tag
      className={cn('grid', minItemWidth ? undefined : colMap[cols], gapMap[gap], className)}
      style={
        minItemWidth
          ? {
              ...style,
              gridTemplateColumns: `repeat(auto-fit, minmax(min(${minItemWidth}, 100%), 1fr))`,
            }
          : style
      }
      {...props}
    >
      {children}
    </Tag>
  )
}

export interface DividerProps extends HTMLAttributes<HTMLDivElement> {
  orientation?: 'horizontal' | 'vertical'
  /** Renders a centred text label interrupting the rule. */
  label?: ReactNode
  weight?: 'subtle' | 'default' | 'strong'
}

const dividerWeight = {
  subtle: 'border-line-subtle',
  default: 'border-line',
  strong: 'border-line-strong',
} as const

export function Divider({
  orientation = 'horizontal',
  label,
  weight = 'default',
  className,
  ...props
}: DividerProps) {
  if (orientation === 'vertical') {
    return (
      <div
        role="separator"
        aria-orientation="vertical"
        className={cn('h-full w-px self-stretch border-l', dividerWeight[weight], className)}
        {...props}
      />
    )
  }

  if (label) {
    return (
      <div className={cn('flex items-center gap-3', className)} {...props}>
        <span className={cn('h-px flex-1 border-t', dividerWeight[weight])} role="separator" />
        <span className="label-caps text-ink-subtle">{label}</span>
        <span className={cn('h-px flex-1 border-t', dividerWeight[weight])} aria-hidden="true" />
      </div>
    )
  }

  return (
    <div
      role="separator"
      className={cn('w-full border-t', dividerWeight[weight], className)}
      {...props}
    />
  )
}

Demo source — adapt to your project. Foundry is not published as a package.

Usage

Stating the limits explicitly is more persuasive than a longer feature list, and it filters out the people who would have churned.

  • Be specific in the excluded column; vagueness there reads as evasion.
  • Keep both lists roughly the same length or the section looks defensive.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Included column
  • Excluded column
  • Two columns at lg

Accessibility

Paired semantics
Both columns are lists with real headings, not styled paragraphs.
Tone independence
Tick and cross icons are paired with distinct copy, never colour alone.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.