{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "skip-link", "title": "Skip Link", "description": "Focus-revealed bypass links (SkipLink + SkipLinks landmark) that let keyboard and screen-reader users jump past repeated blocks, with the same AAA colour variants as the Masthead.", "dependencies": [ "class-variance-authority", "clsx", "tailwind-merge" ], "registryDependencies": [ "https://ui.digital.nsw.gov.au/registry/r/theme.json" ], "files": [ { "path": "src/components/skip-link.tsx", "content": "'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport React from 'react'\n\nimport { cn } from '@/lib/utils'\n\n// Same curated WCAG 2.2 AAA (7:1) pairs as the Masthead — the two components\n// are designed to be themed together, mirroring how the legacy\n// `masthead-theme` option drove both `.nsw-masthead--light` and\n// `.nsw-skip--light`. Kept as a local map (not imported from masthead.tsx) so\n// each file stays standalone for registry distribution.\nconst skipLinkColors = {\n dark: 'bg-primary-800 text-white dark:bg-primary-950',\n light: 'bg-grey-100 text-grey-900',\n white: 'bg-white text-grey-900',\n grey: 'bg-grey-800 text-white',\n}\n\nconst skipLinkVariants = cva(\n [\n // Parked above the viewport until focused (legacy translateY(-101%)\n // pattern) — still in the tab order, so it is the first thing keyboard\n // and screen-reader users reach.\n 'absolute inset-x-0 top-0 w-full -translate-y-[101%]',\n 'motion-safe:transition-transform',\n 'focus:translate-y-0',\n // min-h-11 keeps the revealed bar at ≥44px — 2.5.5 Target Size (AAA).\n //\n // text-base, not the legacy text-xs: this is the first thing a keyboard or\n // screen-magnifier user meets on the page, and 12px is below the smallest\n // step DESIGN.md names for running copy. font-medium keeps it in the\n // system's label voice. The bar is min-h-11, and 16px text on py-2 comes\n // to 40px, so the floor still governs the height — nothing moves.\n 'group flex min-h-11 items-center px-4 py-2 text-base font-medium no-underline',\n // The visible focus indicator is the ring around the label span\n // (2.4.13 Focus Appearance): outline-current guarantees it contrasts with\n // the bar on every colour variant. outline-hidden (not outline-none) on\n // the anchor keeps a forced-colors fallback outline.\n 'focus:outline-hidden',\n ],\n {\n variants: {\n color: skipLinkColors,\n },\n defaultVariants: {\n color: 'dark',\n },\n },\n)\n\n/**\n * Move focus to the skip target. Fragment navigation alone only moves the\n * sequential-focus starting point; explicitly focusing the target makes\n * screen readers announce it. Non-focusable targets get a temporary\n * `tabindex=\"-1\"` (removed on blur), matching the legacy skip-to behaviour.\n */\nfunction focusSkipTarget(event: React.MouseEvent) {\n const hash = event.currentTarget.hash\n if (!hash || hash === '#') {\n return\n }\n\n let id = hash.slice(1)\n try {\n id = decodeURIComponent(id)\n } catch {\n // Malformed escape sequence — fall back to the raw fragment.\n }\n\n const target = document.getElementById(id)\n if (!target) {\n return\n }\n\n if (target.tabIndex < 0 && !target.hasAttribute('tabindex')) {\n target.setAttribute('tabindex', '-1')\n target.addEventListener(\n 'blur',\n () => {\n target.removeAttribute('tabindex')\n },\n { once: true },\n )\n }\n\n target.focus()\n}\n\ntype SkipLinkProps = React.ComponentPropsWithoutRef<'a'> &\n VariantProps & {\n /** Same-page fragment target, e.g. `\"#content\"`. */\n href: string\n ref?: React.Ref\n }\n\n/**\n * A single focus-revealed bypass link (2.4.1 Bypass Blocks). Hidden above the\n * viewport until it receives keyboard focus, then slides in as a full-width\n * bar. Designed to sit inside `SkipLinks`, which provides the positioned\n * `\n )\n}\n\nexport { SkipLink, SkipLinks, skipLinkVariants }\nexport type { SkipLinkProps, SkipLinksProps }\n", "type": "registry:ui", "target": "components/skip-link.tsx" }, { "path": "src/lib/utils.ts", "content": "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n", "type": "registry:lib", "target": "lib/utils.ts" } ], "type": "registry:ui", "meta": { "nswdsVersion": "5.1.0" } }