Soft Organic
Cream backdrop with sage / peach blurred blobs, a blob-shaped avatar, Cormorant italic name, pill-shaped link buttons with soft shadows, and squiggle dividers. Renders one latest-post block from rich blocks.
Preview
Source
tsx
"use client";
import {
Instagram,
Twitter,
Youtube,
Mail,
Globe,
ShoppingBag,
PlayCircle,
Linkedin,
Music2,
ArrowUpRight,
type LucideIcon,
} from "lucide-react";
import type { LinkIcon, LinksInBioData, RichBlock } from "../types";
import { defaultData } from "../defaultData";
const ICONS: Record<LinkIcon, LucideIcon> = {
instagram: Instagram,
twitter: Twitter,
tiktok: Music2,
youtube: Youtube,
spotify: Music2,
apple: Music2,
linkedin: Linkedin,
email: Mail,
globe: Globe,
shop: ShoppingBag,
play: PlayCircle,
};
const CREAM = "#fefcf8";
const SAGE = "#cfd8c2";
const PEACH = "#f6c9b1";
const INK = "#28201a";
const INK_SOFT = "#5b4f44";
function Squiggle({ color = INK }: { color?: string }) {
return (
<svg
aria-hidden
width="120"
height="14"
viewBox="0 0 120 14"
fill="none"
className="opacity-30"
>
<path
d="M2 7 Q 12 1, 22 7 T 42 7 T 62 7 T 82 7 T 102 7 T 118 7"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
fill="none"
/>
</svg>
);
}
function LatestPostBlock({ block }: { block: Extract<RichBlock, { kind: "latest-post" }> }) {
return (
<a
href={block.href}
className="group block overflow-hidden rounded-2xl border border-black/5 bg-white shadow-[0_8px_30px_rgba(60,40,20,0.06)] transition-all hover:-translate-y-0.5 hover:shadow-[0_14px_40px_rgba(60,40,20,0.10)]"
>
<div className="aspect-[16/10] overflow-hidden">
<img
src={block.image}
alt={block.title}
width={800}
height={500}
loading="lazy"
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
/>
</div>
<div className="flex items-center justify-between gap-3 p-4">
<div className="min-w-0">
{block.meta && (
<p className="text-[11px] uppercase tracking-[0.18em]" style={{ color: INK_SOFT }}>
{block.meta}
</p>
)}
<p
className="mt-1 truncate text-base"
style={{
fontFamily: "'Cormorant Garamond', serif",
fontStyle: "italic",
color: INK,
}}
>
{block.title}
</p>
</div>
<ArrowUpRight className="h-4 w-4 shrink-0" style={{ color: INK }} />
</div>
</a>
);
}
export interface LinksInBioSoftOrganicProps {
data?: LinksInBioData;
}
export default function LinksInBioSoftOrganic({
data = defaultData,
}: LinksInBioSoftOrganicProps) {
const latestPost = data.richBlocks?.find(
(b): b is Extract<RichBlock, { kind: "latest-post" }> => b.kind === "latest-post"
);
return (
<>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400..700;1,400..700&family=Inter:wght@400;500;600&display=swap"
/>
<section
className="relative min-h-screen w-full overflow-hidden px-4 py-12 sm:py-16"
style={{ background: CREAM, fontFamily: "'Inter', sans-serif", color: INK }}
>
<div
aria-hidden
className="pointer-events-none absolute -left-24 -top-32 h-96 w-96 rounded-full blur-3xl"
style={{ background: SAGE, opacity: 0.55 }}
/>
<div
aria-hidden
className="pointer-events-none absolute -right-32 top-40 h-80 w-80 rounded-full blur-3xl"
style={{ background: PEACH, opacity: 0.5 }}
/>
<div className="relative mx-auto w-full max-w-[440px]">
<div className="flex flex-col items-center text-center">
<div className="relative">
<div
aria-hidden
className="absolute inset-[-12%]"
style={{
background: SAGE,
borderRadius: "65% 35% 50% 50% / 55% 45% 55% 45%",
opacity: 0.85,
}}
/>
<div
className="relative overflow-hidden"
style={{
width: "120px",
height: "120px",
borderRadius: "55% 45% 60% 40% / 50% 60% 40% 50%",
boxShadow: "0 12px 30px rgba(60,40,20,0.15)",
}}
>
<img
src={data.avatar}
alt={data.name}
width={240}
height={240}
loading="eager"
className="h-full w-full object-cover"
/>
</div>
</div>
<h1
className="mt-6"
style={{
fontFamily: "'Cormorant Garamond', serif",
fontStyle: "italic",
fontSize: "clamp(1.85rem, 6vw, 2.4rem)",
lineHeight: 1.1,
letterSpacing: "-0.01em",
}}
>
{data.name}
</h1>
{data.handle && (
<p className="mt-1 text-sm" style={{ color: INK_SOFT }}>
{data.handle}
</p>
)}
{data.bio && (
<p className="mt-4 max-w-[340px] text-[15px] leading-relaxed" style={{ color: INK_SOFT }}>
{data.bio}
</p>
)}
<div className="mt-5 flex justify-center">
<Squiggle color={INK} />
</div>
</div>
<div className="mt-6 space-y-3">
{data.links.map((link) => {
const Icon = link.icon ? ICONS[link.icon] : null;
return (
<a
key={link.label}
href={link.href}
className="flex items-center gap-3 rounded-full bg-white px-5 py-3.5 text-sm font-medium transition-all hover:-translate-y-0.5 hover:shadow-[0_10px_28px_rgba(60,40,20,0.10)]"
style={{
color: INK,
boxShadow: "0 4px 14px rgba(60,40,20,0.06)",
}}
>
{Icon && (
<span
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full"
style={{ background: SAGE, color: INK }}
>
<Icon className="h-4 w-4" strokeWidth={1.6} />
</span>
)}
<span className="flex-1 truncate text-left">{link.label}</span>
{link.badge && (
<span
className="rounded-full px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider"
style={{ background: PEACH, color: INK }}
>
{link.badge}
</span>
)}
</a>
);
})}
</div>
{latestPost && (
<>
<div className="my-8 flex justify-center">
<Squiggle color={INK} />
</div>
<LatestPostBlock block={latestPost} />
</>
)}
{data.socials && data.socials.length > 0 && (
<div className="mt-10 flex items-center justify-center gap-5" style={{ color: INK_SOFT }}>
{data.socials.map((s, i) => {
const Icon = ICONS[s.type] ?? Globe;
return (
<a
key={i}
href={s.href}
aria-label={s.type}
className="transition-colors"
style={{ color: INK_SOFT }}
>
<Icon className="h-4 w-4" strokeWidth={1.6} />
</a>
);
})}
</div>
)}
<p
className="mt-10 text-center text-[11px] uppercase tracking-[0.2em]"
style={{ color: INK_SOFT, opacity: 0.6 }}
>
Made with care
</p>
</div>
</section>
</>
);
} Claude Code Instructions
CLI Install
npx innovations add soft-organicWhere to use it
An organic, calm link-in-bio page with cream cream/sage/peach palette. Loads Cormorant Garamond and Inter from Google Fonts.
Pass a typed 'data' prop (LinksInBioData from src/registry/links-in-bio/types.ts) to swap content. With no prop it renders sample data.
Add a richBlocks entry with kind 'latest-post' to feature your most recent post or article — it renders as a card with image + title + meta below the link stack.
In Astro:
import LinksInBioSoftOrganic from '../components/innovations/links-in-bio/soft-organic';
<LinksInBioSoftOrganic client:load data={myProfile} />
In Next.js:
import LinksInBioSoftOrganic from '@/components/innovations/links-in-bio/soft-organic';
Best for: lifestyle creators, wellness brands, writers, photographers — any audience that responds to warmth and softness.