Skip to content
Template Empire
Browse components

Media Annotation Pins

A free image review component for numbered notes on a caller-supplied image.

Review two image proofs

Each example keeps its accepted note in caller-owned local state. Turn off acceptance to see a declined request leave the selected note in place.

Workshop label proof

Review trim, heading clearance and closely spaced colour transitions.

Image review: Workshop label print proof showing a heading, colour strip and crop marks
Workshop label print proof showing a heading, colour strip and crop marks
Open original image/components/media-annotation-pins/workshop-proof.png

Selected: Heading clearance

  1. 01
    Outer trim

    Check that the upper-left cut leaves the label stock visible

  2. 02
    Heading clearanceSelected

    The WORKSHOP heading should keep its gap from the angled top edge

  3. 03
    Colour strip

    Confirm the terracotta block begins at the approved guide

  4. 04
    Swatch boundary

    Review the closely spaced first colour transition in the original proof

Initial review note supplied by the caller.

Editorial cover proof

Review the edition line, title rhythm and cover details.

Image review: Editorial field-notes cover with a serif title and warm arch
Editorial field-notes cover with a serif title and warm arch
Open original image/components/media-annotation-pins/edition-cover-proof.svg

Selected: Title baseline

  1. 01
    Edition line

    Check the tracked edition line before export

  2. 02
    Title baselineSelected

    The second title line sets the main reading rhythm

  3. 03
    Arch crown

    Confirm the arch remains clear of the supporting line

  4. 04
    Footer rule

    The rule must finish at the same right margin as the edition line

Initial review note supplied by the caller.

Customise

Code

"use client";

import { MediaAnnotationPins } from "@/components/media-annotation-pins";

const source = {
  key: "workshop-proof-v1",
  src: "/media-annotation-pins/workshop-proof.png",
  alt: "Workshop label print proof showing a heading and colour strip",
  width: 800,
  height: 600,
};
const annotations = [
  { id: "trim", label: "Outer trim", detail: "Check the upper-left cut leaves the stock visible", x: 0, y: 0 },
  { id: "heading", label: "Heading clearance", detail: "Keep the heading clear of the angled top edge", x: 143, y: 129 },
];
const value = { sourceKey: source.key, id: "heading" };

// This read-only snapshot needs no callback. Add onValueChange and accept its request by updating value.

<MediaAnnotationPins
  source={source} annotations={annotations} value={value}
/>

Place numbered review notes on a caller-supplied image, then select them through accessible pins and a complete ordered note list.

Media Annotation Pins is a free React component for reviewing image proofs and editorial artwork. Your application owns the image, note content and accepted selection; the component keeps every valid note available in the ordered list, including notes whose nearby positions share a grouped pin.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/media-annotation-pins

Usage

"use client";

import { useState } from "react";
import { MediaAnnotationPins, type MediaAnnotationPinsProps } from "@/components/media-annotation-pins";

const source = {
  key: "workshop-proof-v1",
  src: "/media-annotation-pins/workshop-proof.png",
  alt: "Workshop label print proof showing a heading and colour strip",
  width: 800,
  height: 600,
} satisfies MediaAnnotationPinsProps["source"];
const annotations = [
  { id: "trim", label: "Outer trim", detail: "Check the upper-left cut leaves the stock visible", x: 0, y: 0 },
  { id: "heading", label: "Heading clearance", detail: "Keep the heading clear of the angled top edge", x: 143, y: 129 },
] satisfies MediaAnnotationPinsProps["annotations"];

export function Example() {
  const [value, setValue] = useState<MediaAnnotationPinsProps["value"]>({ sourceKey: source.key, id: "heading" });
  return <MediaAnnotationPins source={source} annotations={annotations} value={value}
    onValueChange={setValue} />;
}

Props

PropTypeDefaultDescription
source{ key; src; alt; width; height }requiredkey identifies one immutable image generation. Change it whenever image bytes, URL, orientation, design extents or note coordinate basis changes. src must be a root-relative path or absolute HTTPS URL without credentials, fragment, whitespace, controls or backslashes. alt is nonblank plain text. Width and height are finite design units from 0.001 to 1,000,000; valid points include the closed bottom-right edge.
annotationsreadonly { id; label; detail; x; y }[]requiredSupply 2 to 12 ordered notes. IDs are unique nonblank keys; labels and detail are plain text. A point is continuous and must be finite inside 0..width and 0..height. Invalid rows remain visible with an explanation and cannot be selected; invalid positions have no pin.
value{ sourceKey; id } | nullrequiredThe parent-owned accepted selection. Both sourceKey and id must match a current valid note. A stale value is shown as unavailable; the first note is never selected as a fallback. Selecting the accepted note requests null to clear it.
onValueChange(request: { sourceKey; id } | null) => voidundefinedRequests a selection or clear. The parent decides whether to accept it. Without this callback the component is read-only and exposes no inactive action buttons.
classNamestringundefinedOptional utilities merged onto the root figure. The complete image retains its source ratio and the component reserves a 26px or 30px pin gutter as note density requires.

Customisation

The leaf exposes only these five props and uses native buttons. Grouped pins move focus to the first matching note action; each note stays independently selectable from the ordered list. Overlay pins appear only after hydration, successful image load, ratio verification and finite usable geometry. If enhancement is unavailable, use the complete list. Reduced motion, forced colours and print keep selection static. The accepted halo moves only after a matching parent value update and only while source, ordered notes, coordinates and image geometry remain unchanged. The component does not persist notes or image data. Scoped aliases can be restyled with --annotation-pins-ink, --annotation-pins-surface, --annotation-pins-border, --annotation-pins-accent and --annotation-pins-ring. For the bundled examples, copy public/media-annotation-pins/workshop-proof.png and public/media-annotation-pins/edition-cover-proof.svg to the same paths under your public directory. These byte-identical originals are optional examples; the leaf accepts any caller-supplied safe image URL.

.review-surface {
  --annotation-pins-ink: var(--foreground);
  --annotation-pins-surface: var(--background);
  --annotation-pins-border: var(--border);
  --annotation-pins-accent: var(--primary);
  --annotation-pins-ring: var(--ring);
}

/* Add className="review-surface" to MediaAnnotationPins. */

Frequently asked questions

Is Media Annotation Pins free for commercial use?
Yes, for personal and commercial projects under the repository's MIT licence with the Commons Clause condition. Keep the required copyright, licence and Commons Clause notices in source. Distributing these components as a component library requires a separate agreement. See LICENCE.md for the complete terms.
Can I use the note list when pins are unavailable or JavaScript is disabled?
Yes. Server output includes the source image, original-image link and complete ordered note text. Selection actions appear after hydration. If image loading, ratio, geometry or browser enhancement checks fail, the list remains available and explains that pin enhancement is unavailable.
Does the component save notes or accept a selection automatically?
No. Your application supplies the image, ordered annotations and accepted value. A pin or note action only requests a value; update value in your parent to accept it. The component does not persist review data.

Related components

Build a page around your reviewExplore Template Empire layouts when you need a complete site around an image review tool.Browse templates