Components / Resources
Resource Links Panel
Free Resources React component — ResourceLinksPanel. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
Resource link list with logo, name, description, and domain per row — same layout as the homepage Resources section. Copy and pass title + items.
Preview
Resources

Cursor / For Editor
Cursor/For Editorcursor.com- D
Dither.it / For making images better
Dither.it/For making images betterditherit.com 
Flaticon / For image PNGs
Flaticon/For image PNGsflaticon.comLucide / For icons
Lucide/For iconslucide.dev
Manu sir / For inspiration
Manu sir/For inspirationmanuarora.in
PostHog / For tracking
PostHog/For trackingposthog.com
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/resources/resource-links-panel.tsxin your project. - 4
Import and render:
Exampleimport { ResourceLinksPanel } from "@/components/resources/resource-links-panel";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/resources/resource-links-panel.tsx
import Image from "next/image";import {forwardRef,type ComponentPropsWithoutRef,type ComponentType,type SVGProps,} from "react";import { cn } from "@/lib/cn";export type ResourceLinkItem = Readonly<{name: string;description: string;shortDescription?: string;href: string;domain?: string;color?: string;Icon?: ComponentType<SVGProps<SVGSVGElement> & { size?: number | string }>;imageSrc?: string;letter?: string;}>;const DEMO_ITEMS: ResourceLinkItem[] = [{name: "Cursor",description: "For Editor",href: "https://cursor.com",imageSrc: "/cursor.webp",},{name: "Dither.it",description: "For making images better",href: "https://ditherit.com",letter: "D",color: "text-rose-400",},{name: "Lucide",description: "For icons",href: "https://lucide.dev",imageSrc: "/lucide-logo.svg",},{name: "Manu sir",description: "For inspiration",href: "https://www.manuarora.in",imageSrc: "/manu.webp",},{name: "PostHog",description: "For tracking",href: "https://posthog.com",imageSrc: "/posthog.png",},{name: "Flaticon",description: "For image PNGs",href: "https://www.flaticon.com",imageSrc: "/flaticon.png",},];export type ResourceLinksPanelProps = Readonly<{title?: string;items?: readonly ResourceLinkItem[];sortItems?: boolean;} & ComponentPropsWithoutRef<"div">>;function getDomain(href: string) {try {return new URL(href).hostname.replace(/^www\./, "");} catch {return href;}}function ListIcon({ item }: { item: ResourceLinkItem }) {const { Icon, imageSrc, letter, color } = item;const iconColor = color ?? "text-neutral-800";const letterColor = color ?? "text-neutral-600";return (<div className="flex h-5 w-5 shrink-0 items-center justify-center overflow-hidden">{imageSrc ? (<Imagesrc={imageSrc}alt=""width={20}height={20}className="h-5 w-5 rounded object-contain"/>) : Icon ? (<Icon size={16} className={iconColor} />) : (<span className={`font-sans text-[16px] font-semibold ${letterColor}`}>{letter}</span>)}</div>);}function ListRow({ item }: { item: ResourceLinkItem }) {const domain = item.domain ?? getDomain(item.href);const mobileDescription = item.shortDescription ?? item.description;return (<li className="min-w-0"><ahref={item.href}target="_blank"rel="noopener noreferrer"className="group flex min-w-0 items-center gap-2.5 py-0.5 max-[499px]:gap-2"><ListIcon item={item} /><div className="flex min-w-0 flex-1 items-center gap-3 max-[499px]:gap-2"><p className="hidden min-w-0 flex-1 truncate font-sans text-sm leading-snug min-[500px]:block"><span className="font-semibold text-neutral-900 group-hover:text-neutral-700">{item.name}</span><span className="text-neutral-300"> / </span><span className="text-neutral-500">{item.description}</span></p><div className="flex min-w-0 flex-1 items-baseline overflow-hidden min-[500px]:hidden"><span className="shrink-0 font-sans text-xs leading-snug font-semibold text-neutral-900 group-hover:text-neutral-700">{item.name}</span><span className="shrink-0 px-1 font-sans text-xs text-neutral-300">/</span><span className="min-w-0 truncate font-sans text-xs leading-snug text-neutral-500">{mobileDescription}</span></div><span className="shrink-0 font-mono text-sm text-neutral-400 group-hover:text-neutral-500 max-[499px]:text-[11px]">{domain}</span></div></a></li>);}// Resource link list — same row layout as the homepage Resources section. Copy, pass title + items.export const ResourceLinksPanel = forwardRef<HTMLDivElement, ResourceLinksPanelProps>(function ResourceLinksPanel({ title = "Resources", items = DEMO_ITEMS, sortItems = true, className, ...props },ref,) {const rows = sortItems ? [...items].sort((a, b) => a.name.localeCompare(b.name)) : items;return (<divref={ref}className={cn("min-w-0 max-[499px]:overflow-hidden", className)}{...props}><section><h3 className="font-sans text-sm font-semibold text-neutral-900">{title}</h3><ul className="mt-4 flex flex-col gap-2.5">{rows.map((item) => (<ListRow key={item.name} item={item} />))}</ul></section></div>);},);ResourceLinksPanel.displayName = "ResourceLinksPanel";