{
  "contract": "tessli.public-library-item.v1",
  "canonicalPath": "/library/feature-evidence",
  "representations": {
    "json": "/library/feature-evidence/item.json",
    "markdown": "/library/feature-evidence/item.md"
  },
  "preview": "/preview/feature-evidence",
  "item": {
    "slug": "feature-evidence",
    "title": "Feature section, evidence-led",
    "kind": "section",
    "category": "features",
    "summary": "Shows each feature with the artefact that proves it (a UI fragment, a before/after, an exact limit), giving the most important feature the most room.",
    "status": "community",
    "install": "npx shadcn@latest add online-scope-lab/tessli/feature-evidence",
    "registry": {
      "type": "registry:block",
      "registryDependencies": [],
      "dependencies": []
    },
    "notes": {
      "id": "feature-evidence",
      "kind": "section",
      "category": "features",
      "title": "Feature section, evidence-led",
      "summary": "Shows each feature with the artefact that proves it (a UI fragment, a before/after, an exact limit), giving the most important feature the most room.",
      "use_when": [
        "A marketing or product page has to explain two to four features that a buyer will check before switching.",
        "Each feature can be shown with something concrete from the product, such as a screen fragment, an output or a stated limit.",
        "The features differ in importance and the page should say which one matters most."
      ],
      "avoid_when": [
        "You cannot yet show the feature working; ship a short description list and add evidence once it exists.",
        "There are more than about six features of equal weight; use a compact list or a comparison table.",
        "The section is a step-by-step setup flow; use a numbered sequence instead."
      ],
      "rules": [
        "K-04",
        "W-01",
        "W-03",
        "C-03",
        "H-02",
        "T-01",
        "T-03",
        "R-01"
      ],
      "ai_default": "A centred \"Features\" heading over three identical rounded cards, each with a primary-coloured icon in a tinted circle, a two-word title such as \"Lightning fast\" or \"Secure by default\" and one line of filler, often with a \"Learn more →\" link on every card.",
      "why_this_is_better": [
        "Each feature is shown with an artefact a reader can check (a version history, the exported file, a number with a unit) instead of an icon and an adjective.",
        "Emphasis follows importance; the lead feature gets the widest column and a bigger heading, supporting features sit below it, and small features become a plain list.",
        "Headings state the claim (\"Every edit is kept, and any version can be restored\"), so they still make sense out of context and would not fit another product.",
        "Limits are written as exact values in tabular figures, in a row layout, not implied by \"Unlimited\" or a tick.",
        "The section uses no accent colour at all; hierarchy comes from size, width, borders and order, so it holds in any theme.",
        "Demo content is labelled fictional in a footnote, so placeholder proof cannot ship as if it were real."
      ],
      "failures": [
        "Swapping the artefacts back for icons, which turns the section into the default it replaces.",
        "Using a mock-up, number or screenshot that the product cannot actually produce, or leaving the demo limits in place.",
        "Giving every feature the lead treatment, so nothing reads as most important.",
        "Putting live, clickable controls inside an artefact, so people try to use a picture of the product.",
        "Removing the captions, which leaves screen-reader users and skimmers without the point of each artefact."
      ],
      "states": [
        "default",
        "two-features"
      ],
      "responsive": {
        "desktop": "From 1024px the lead feature puts its text beside a wider artefact; two supporting features sit in two columns from 768px, and the remaining features form a three- or two-column list chosen so none is left alone on a row. The before/after goes side by side only when its own container is at least 36rem wide.",
        "mobile": "One column in priority order, lead first; each feature's heading and text come before its artefact, limits stay large, and the before/after stacks so both panels keep a readable width."
      },
      "a11y": [
        "The section is labelled by its h2; every feature title is an h3, including the \"also\" list heading.",
        "Each artefact is a figure with a figcaption that states what it shows; artefacts hold no focusable controls.",
        "Added and removed lines in the diff are announced as \"Added\" and \"Removed\" and shown with + and − marks and strike-through, not by colour; the version being viewed is labelled \"Viewing\" in text.",
        "Checklist marks in the before/after are decorative; their state is given in hidden text (\"Done\", \"To do\").",
        "The only links are real links at least 44px tall with a visible focus ring."
      ],
      "status": "community",
      "reviewed_by": [],
      "reviewed_at": null,
      "contributors": [],
      "provenance": "original",
      "license": "MIT"
    },
    "rules": [
      {
        "id": "K-04",
        "statement": "Shape each block by its content; don't force unlike content into identical icon cards.",
        "path": "/rules/K-04"
      },
      {
        "id": "W-01",
        "statement": "Write headings that make the product's point, not labels like \"Features\" or \"FAQ\".",
        "path": "/rules/W-01"
      },
      {
        "id": "W-03",
        "statement": "Show no ratings, counts, logos, quotes or metrics you can't source.",
        "path": "/rules/W-03"
      },
      {
        "id": "C-03",
        "statement": "Spend the primary colour on the primary action and real status, not decoration.",
        "path": "/rules/C-03"
      },
      {
        "id": "H-02",
        "statement": "One h1 per page; nest h2–h4 in order. Level follows structure, size follows style.",
        "path": "/rules/H-02"
      },
      {
        "id": "T-01",
        "statement": "Cap running text at a readable measure (about 65ch), never the full container width.",
        "path": "/rules/T-01"
      },
      {
        "id": "T-03",
        "statement": "Set prices, counts and numeric columns in tabular figures.",
        "path": "/rules/T-03"
      },
      {
        "id": "R-01",
        "statement": "On phones, restack content by priority and adapt components; don't shrink the desktop.",
        "path": "/rules/R-01"
      }
    ],
    "explanation": "Most feature sections ask the reader to take adjectives on trust: three cards, three icons, \"fast\", \"secure\", \"simple\". This item replaces each adjective with the thing that would prove it: a small static fragment of the product, a before and after, or an exact limit with its unit. If you cannot show it, that is useful to know before you claim it.\n\nFeatures are not equally important, so the layout is not a grid of peers. One **lead** feature gets the most room and a bigger heading. One or two **supporting** features follow, each with its own kind of proof. Small features that need only a sentence go in a plain list at the end.\n\nThe file exports three helpers for the artefacts: `EvidencePanel` frames a fragment of UI, `BeforeAfter` shows an input and its result (it uses a Tailwind v4 container query to decide when to sit side by side), and `ExactLimits` sets numbers large in tabular figures. Keep artefacts static and caption every one. Replace the demo's fictional people, notes and limits with your product's real ones, and keep the footnote only while they are still demo content.",
    "source": {
      "path": "web/registry/items/feature-evidence/feature-evidence.tsx",
      "code": "import * as React from \"react\";\n\nimport { cn } from \"@/registry/shadcn/lib/utils\";\n\nexport type FeatureFact = {\n  /** What is limited or guaranteed: \"History kept on Team\". */\n  label: string;\n  /** The exact value, written out: \"1 year\", not a tick. */\n  value: React.ReactNode;\n};\n\nexport type EvidenceFeature = {\n  id: string;\n  /** A claim the artefact can prove, not an adjective (\"Every edit is kept\"). */\n  title: React.ReactNode;\n  /** Two or three sentences: what it does and the one condition that matters. */\n  description: React.ReactNode;\n  /**\n   * The proof: a small UI fragment, a before/after or an exact limit. Use\n   * EvidencePanel, BeforeAfter or ExactLimits below, or your own markup.\n   * Never an icon standing in for the feature.\n   */\n  evidence: React.ReactNode;\n  /** One sentence saying what the artefact shows. Rendered as its caption. */\n  evidenceCaption: React.ReactNode;\n  /** Exact limits or guarantees, in the order a buyer would check them. */\n  facts?: FeatureFact[];\n  /** Where to read more. The label names the destination. */\n  link?: { label: string; href: string };\n};\n\nexport type FeatureEvidenceProps = Omit<\n  React.ComponentProps<\"section\">,\n  \"title\"\n> & {\n  /** The point of the section, not \"Features\". */\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  /** The feature that matters most. It gets the most room and a bigger heading. */\n  lead: EvidenceFeature;\n  /** One or two features that matter less than the lead, each with its own proof. */\n  supporting?: EvidenceFeature[];\n  /** Smaller features that need a sentence, not a showcase. Shown as a list. */\n  more?: {\n    title: React.ReactNode;\n    items: { term: string; detail: React.ReactNode }[];\n  };\n  /** Anything a reader must know about the claims, such as plan limits. */\n  footnote?: React.ReactNode;\n};\n\nfunction FeatureText({\n  feature,\n  emphasis,\n}: {\n  feature: EvidenceFeature;\n  emphasis: \"lead\" | \"supporting\";\n}) {\n  const isLead = emphasis === \"lead\";\n  return (\n    <div className=\"min-w-0\">\n      <h3\n        className={cn(\n          \"font-semibold tracking-tight text-balance\",\n          isLead ? \"text-xl leading-snug\" : \"text-base leading-snug\",\n        )}\n      >\n        {feature.title}\n      </h3>\n      <div\n        className={cn(\n          \"mt-3 max-w-prose leading-relaxed text-pretty text-muted-foreground\",\n          isLead ? \"text-base\" : \"text-sm\",\n        )}\n      >\n        {feature.description}\n      </div>\n      {feature.facts?.length ? (\n        <dl className=\"mt-6 max-w-prose border-t border-border text-sm\">\n          {feature.facts.map((fact) => (\n            <div\n              key={fact.label}\n              className=\"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 border-b border-border py-3\"\n            >\n              <dt className=\"text-muted-foreground\">{fact.label}</dt>\n              <dd className=\"font-medium tabular-nums\">{fact.value}</dd>\n            </div>\n          ))}\n        </dl>\n      ) : null}\n      {feature.link ? (\n        <a\n          href={feature.link.href}\n          className=\"mt-4 inline-flex min-h-11 items-center rounded-sm text-sm font-medium text-foreground underline underline-offset-4 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\"\n        >\n          {feature.link.label}\n        </a>\n      ) : null}\n    </div>\n  );\n}\n\nfunction FeatureFigure({ feature }: { feature: EvidenceFeature }) {\n  return (\n    <figure className=\"min-w-0\">\n      {feature.evidence}\n      <figcaption className=\"mt-3 text-sm leading-relaxed text-muted-foreground\">\n        {feature.evidenceCaption}\n      </figcaption>\n    </figure>\n  );\n}\n\n/** Text beside its proof on wide screens; text first, then proof, on phones. */\nfunction FeatureRow({\n  feature,\n  emphasis,\n}: {\n  feature: EvidenceFeature;\n  emphasis: \"lead\" | \"supporting\";\n}) {\n  return (\n    <div className=\"grid gap-8 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-12\">\n      <FeatureText feature={feature} emphasis={emphasis} />\n      <FeatureFigure feature={feature} />\n    </div>\n  );\n}\n\nexport function FeatureEvidence({\n  title,\n  description,\n  lead,\n  supporting = [],\n  more,\n  footnote,\n  className,\n  ...props\n}: FeatureEvidenceProps) {\n  const headingId = React.useId();\n\n  return (\n    <section\n      aria-labelledby={headingId}\n      data-slot=\"feature-evidence\"\n      className={cn(\"bg-background text-foreground\", className)}\n      {...props}\n    >\n      <div className=\"mx-auto w-full max-w-6xl px-4 py-12 sm:px-6 sm:py-16 lg:px-8 lg:py-24\">\n        <div className=\"max-w-2xl\">\n          <h2\n            id={headingId}\n            className=\"text-3xl leading-tight font-semibold tracking-tight text-balance sm:text-4xl\"\n          >\n            {title}\n          </h2>\n          {description ? (\n            <p className=\"mt-4 text-base leading-relaxed text-pretty text-muted-foreground\">\n              {description}\n            </p>\n          ) : null}\n        </div>\n\n        <div className=\"mt-10 border-t border-border pt-10 lg:mt-14\">\n          <FeatureRow feature={lead} emphasis=\"lead\" />\n        </div>\n\n        {supporting.length === 1 ? (\n          <div className=\"mt-12 border-t border-border pt-10\">\n            <FeatureRow feature={supporting[0]} emphasis=\"supporting\" />\n          </div>\n        ) : supporting.length > 1 ? (\n          <div className=\"mt-12 grid gap-12 border-t border-border pt-10 md:grid-cols-2 md:gap-8 lg:gap-12\">\n            {supporting.map((feature) => (\n              <div key={feature.id} className=\"flex min-w-0 flex-col gap-6\">\n                <FeatureText feature={feature} emphasis=\"supporting\" />\n                <FeatureFigure feature={feature} />\n              </div>\n            ))}\n          </div>\n        ) : null}\n\n        {more?.items.length ? (\n          <div className=\"mt-12 border-t border-border pt-10\">\n            <h3 className=\"text-base leading-snug font-semibold tracking-tight\">\n              {more.title}\n            </h3>\n            <dl\n              className={cn(\n                \"mt-4 grid gap-x-8 gap-y-5\",\n                // Three per row only when the count divides evenly, so no\n                // item is left alone on the last row.\n                more.items.length % 3 === 0\n                  ? \"md:grid-cols-3\"\n                  : \"md:grid-cols-2\",\n              )}\n            >\n              {more.items.map((item) => (\n                <div key={item.term} className=\"min-w-0\">\n                  <dt className=\"text-sm font-medium\">{item.term}</dt>\n                  <dd className=\"mt-1 max-w-prose text-sm leading-relaxed text-muted-foreground\">\n                    {item.detail}\n                  </dd>\n                </div>\n              ))}\n            </dl>\n          </div>\n        ) : null}\n\n        {footnote ? (\n          <div className=\"mt-10 max-w-prose text-sm leading-relaxed text-muted-foreground\">\n            {footnote}\n          </div>\n        ) : null}\n      </div>\n    </section>\n  );\n}\n\nexport type EvidencePanelProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  /** The object shown, as the app would name it: \"Site visit, north wing\". */\n  title?: React.ReactNode;\n  /** Where in the app this is: \"Version history\". */\n  meta?: React.ReactNode;\n};\n\n/** A bordered frame for a small, static fragment of the product's UI. */\nexport function EvidencePanel({\n  title,\n  meta,\n  children,\n  className,\n  ...props\n}: EvidencePanelProps) {\n  return (\n    <div\n      data-slot=\"evidence-panel\"\n      className={cn(\n        \"overflow-hidden rounded-lg border border-border bg-card text-sm text-card-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {title || meta ? (\n        <div className=\"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 border-b border-border px-4 py-3\">\n          {title ? <p className=\"font-medium\">{title}</p> : null}\n          {meta ? <p className=\"text-muted-foreground\">{meta}</p> : null}\n        </div>\n      ) : null}\n      {children}\n    </div>\n  );\n}\n\nexport type BeforeAfterProps = React.ComponentProps<\"div\"> & {\n  /** Each side is named in text (\"In the app\", \"In the exported file\"). */\n  before: { label: React.ReactNode; content: React.ReactNode };\n  after: { label: React.ReactNode; content: React.ReactNode };\n};\n\n/**\n * Two panels, side by side when their container is at least 36rem wide and\n * stacked otherwise, so it fits a narrow column as well as a wide one.\n */\nexport function BeforeAfter({\n  before,\n  after,\n  className,\n  ...props\n}: BeforeAfterProps) {\n  return (\n    <div\n      data-slot=\"before-after\"\n      className={cn(\"@container\", className)}\n      {...props}\n    >\n      <div className=\"grid gap-3 @xl:grid-cols-2\">\n        {[before, after].map((side, index) => (\n          <EvidencePanel key={index} title={side.label}>\n            <div className=\"min-w-0 px-4 py-3\">{side.content}</div>\n          </EvidencePanel>\n        ))}\n      </div>\n    </div>\n  );\n}\n\nexport type ExactLimitsProps = React.ComponentProps<\"ul\"> & {\n  /** Each limit as a number, its unit and what it applies to. */\n  limits: { value: string; unit: string; label: React.ReactNode }[];\n};\n\n/** Exact limits set large, so the number is the evidence. */\nexport function ExactLimits({ limits, className, ...props }: ExactLimitsProps) {\n  return (\n    <ul\n      data-slot=\"exact-limits\"\n      className={cn(\n        \"divide-y divide-border rounded-lg border border-border bg-card text-card-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {limits.map((limit) => (\n        <li key={`${limit.value}-${limit.unit}`} className=\"px-5 py-4\">\n          <p className=\"flex flex-wrap items-baseline gap-x-2\">\n            <span className=\"text-3xl font-semibold tracking-tight tabular-nums sm:text-4xl\">\n              {limit.value}\n            </span>\n            <span className=\"text-base font-medium\">{limit.unit}</span>\n          </p>\n          <p className=\"mt-1 text-sm leading-relaxed text-muted-foreground\">\n            {limit.label}\n          </p>\n        </li>\n      ))}\n    </ul>\n  );\n}\n"
    }
  },
  "boundaries": [
    "Community items are merged and pass automated checks (schema, build, accessibility, overflow and console errors in three themes at five widths); only items with status reviewed have been checked by a maintainer.",
    "Each rule id links to its page at /rules/<id>; every rule is a draft until a maintainer signs it.",
    "Tessli does not claim that using an item improves AI-generated UI; that claim waits for a published evaluation."
  ]
}
