Logo generator
Placeholder brands from a seed for logo clouds, mockups and case studies. Twelve families of marks, seeded wordmarks in the one typeface, lockups, stacked logos and app icons. The same name always gives the same brand, none of them looks like a real company.
One of 3 components in Placeholders and generators
Variants
Details
What Logo generator brings along, read from the registry and the source.
- Category
- Placeholders and generators
- Area
- Visuals
- Variants
- 5
- Version
- 1.0.0 · since 1.1.0
- File
- components/ui/logo-generator.tsx
- Brings along
- utilskit
- npm packages
- None
- Languages
- Texts come through props
- Studio
- All 5 variants editable
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Props
The GeneratedLogoProps interface from components/ui/logo-generator.tsx, with the comments from the code.
namerequiredstringThe brand name. Also the seed unless seed is given.
seedoptionalstringlayoutoptionalLogoLayoutmarkwordmarklockupstackedapp-icon
toneoptionalLogoTonemonoaccentcolor
sizeoptionalnumberHeight of the mark in pixels, the text scales with it. Default 32.
animateoptionalbooleanDraw the mark in when it scrolls into view
markoptionalnumberPick the family of the mark yourself, 0 to 11. Default: from the seed.
Accessibility
Read from the source of the component.
- Sets aria-label, so screen readers announce purpose and state.
- Uses the role img.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- With reduced motion (prefers-reduced-motion) it stays still. 2 places in the code switch motion off then.
- No animations, it stands still from the start.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 256 lines
'use client';
import { cn, round, useInView } from '@/lib/kit';
/*
* Placeholder brands from a seed: a mark, a wordmark, or both. For logo
* clouds, app mockups, case studies and anywhere a real logo is not there
* yet. The same seed always gives the same brand. Nothing here resembles a
* real company, the marks are built from simple geometry and the words are
* set in the kit's one typeface.
*
* mark the symbol alone
* wordmark the name in a seeded treatment (weight, case, tracking, a dot or a bar)
* lockup symbol and name side by side
* stacked symbol over name, centered
* app-icon the symbol on a rounded tile with a soft light, like a home screen icon
*
* tone: mono follows the text color (for logo rows), accent uses the theme
* accent, color picks one of the theme colors from the seed.
*/
export type LogoLayout = 'mark' | 'wordmark' | 'lockup' | 'stacked' | 'app-icon';
export type LogoTone = 'mono' | 'accent' | 'color';
export interface GeneratedLogoProps {
/** The brand name. Also the seed unless seed is given. */
name: string;
seed?: string;
layout?: LogoLayout;
tone?: LogoTone;
/** Height of the mark in pixels, the text scales with it. Default 32. */
size?: number;
/** Draw the mark in when it scrolls into view */
animate?: boolean;
/** Pick the family of the mark yourself, 0 to 11. Default: from the seed. */
mark?: number;
className?: string;
}
function hash(s: string) {
let h = 2166136261;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
function random(seed: number) {
let a = seed;
return () => {
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const COLORS = ['var(--primary)', 'var(--success)', 'var(--warning)', 'var(--info)', 'var(--primary-2)', 'var(--destructive)'];
/** The geometry of a mark on a 32 × 32 grid. Twelve families, each with seeded details. */
function markShapes(kind: number, r: () => number, letter: string): React.ReactNode {
const v = Math.floor(r() * 3);
switch (kind) {
case 0: // sliced circle
return (
<>
<path d="M16 3a13 13 0 1 1 0 26Z" fill="currentColor" />
<path d="M16 3a13 13 0 0 0 0 26" fill="currentColor" opacity="0.4" />
{v > 0 && <circle cx="16" cy="16" r={v === 1 ? 4 : 6} fill="var(--kl-bg, var(--background))" />}
</>
);
case 1: // stacked bars
return (
<>
{[0, 1, 2].map((i) => (
<rect key={i} x={4 + (v === 1 ? i * 3 : 0)} y={5 + i * 8.5} width={v === 2 ? 24 - i * 6 : 24 - (v === 1 ? i * 3 : 0)} height="5.5" rx="2.75" fill="currentColor" opacity={1 - i * 0.25} />
))}
</>
);
case 2: // triangle pair
return (
<>
<path d="M3 27 14 6l11 21Z" fill="currentColor" />
<path d="M14 27 22 12l8 15Z" fill="currentColor" opacity="0.45" />
</>
);
case 3: // orbit
return (
<>
<circle cx="16" cy="16" r="11.5" fill="none" stroke="currentColor" strokeWidth="3" />
<circle cx={round(16 + Math.cos(-0.7 - v) * 11.5, 1)} cy={round(16 + Math.sin(-0.7 - v) * 11.5, 1)} r="4.5" fill="currentColor" />
</>
);
case 4: // four petals
return (
<g fill="currentColor">
{[0, 90, 180, 270].map((a, i) => (
<path key={a} d="M16 16C16 9 20 4 26 4c0 6-5 12-10 12Z" transform={`rotate(${a + v * 15} 16 16)`} opacity={i % 2 ? 0.55 : 1} />
))}
</g>
);
case 5: // dot grid
return (
<g fill="currentColor">
{Array.from({ length: 9 }, (_, i) => {
const x = 6 + (i % 3) * 10;
const y = 6 + Math.floor(i / 3) * 10;
const on = i === 4 || (i + v) % 2 === 0;
return <circle key={i} cx={x} cy={y} r={on ? 3.6 : 2} opacity={on ? 1 : 0.4} />;
})}
</g>
);
case 6: // wave
return (
<>
<path d="M3 13c4-5 7-5 10 0s6 5 10 0 6-5 6-5" fill="none" stroke="currentColor" strokeWidth="3.4" strokeLinecap="round" />
<path d="M3 22c4-5 7-5 10 0s6 5 10 0 6-5 6-5" fill="none" stroke="currentColor" strokeWidth="3.4" strokeLinecap="round" opacity="0.45" />
</>
);
case 7: // monogram in a square
return (
<>
<rect x="3" y="3" width="26" height="26" rx={v === 0 ? 7 : v === 1 ? 13 : 2.5} fill="currentColor" />
<text x="16" y="16" dy="0.35em" textAnchor="middle" fontSize="16" fontWeight="700" fill="var(--kl-bg, var(--background))">
{letter}
</text>
</>
);
case 8: // arch
return (
<>
<path d="M5 28V16a11 11 0 0 1 22 0v12h-6V16a5 5 0 0 0-10 0v12Z" fill="currentColor" />
{v === 1 && <circle cx="16" cy="6" r="2.5" fill="currentColor" opacity="0.5" />}
</>
);
case 9: // diamond fold
return (
<>
<path d="M16 2 30 16 16 30 2 16Z" fill="currentColor" opacity="0.4" />
<path d="M16 2 30 16 16 30Z" fill="currentColor" />
</>
);
case 10: // sun rays
return (
<g fill="currentColor">
<circle cx="16" cy="16" r="6" />
{Array.from({ length: 8 }, (_, i) => (
<rect key={i} x="15" y="2" width="2" height="6" rx="1" transform={`rotate(${i * 45} 16 16)`} opacity={i % 2 ? 0.5 : 1} />
))}
</g>
);
default: // interlocking rings
return (
<g fill="none" stroke="currentColor" strokeWidth="3.2">
<circle cx="12" cy="16" r="8" />
<circle cx="20" cy="16" r="8" opacity="0.5" />
</g>
);
}
}
/** Seeded word treatments, all in the one typeface */
function wordStyle(r: () => number) {
const variant = Math.floor(r() * 5);
return {
weight: [600, 700, 500, 800, 600][variant],
tracking: ['-0.03em', '-0.045em', '0.08em', '-0.02em', '0.01em'][variant],
upper: variant === 2,
lower: variant === 1 || variant === 4,
dot: variant === 1 || variant === 3,
bar: variant === 4,
};
}
/**
* A placeholder brand from a seed.
* <GeneratedLogo name="Brightwell" layout="lockup" tone="accent" size={32} />
*/
export function GeneratedLogo({ name, seed, layout = 'lockup', tone = 'mono', size = 32, animate = false, mark: family, className }: GeneratedLogoProps) {
const h = hash(seed ?? name);
const r = random(h);
r();
// The family comes from the high bits of the hash, which spread better over twelve than the first random draw
const kind = family !== undefined ? ((Math.round(family) % 12) + 12) % 12 : (h >>> 20) % 12;
const color = tone === 'mono' ? 'currentColor' : tone === 'accent' ? 'var(--primary)' : COLORS[Math.floor(r() * COLORS.length)];
const letter = (name.trim()[0] ?? 'A').toUpperCase();
const word = wordStyle(r);
const [ref, inView] = useInView<HTMLSpanElement>();
const hidden = animate && !inView;
const mark = (box: number, onTile = false) => (
<svg
viewBox="0 0 32 32"
aria-hidden
className={cn('shrink-0 transition-[transform,opacity] duration-700 ease-[cubic-bezier(0.34,1.4,0.64,1)] motion-reduce:transition-none', hidden && 'rotate-[-30deg] scale-50 opacity-0')}
style={{ width: box, height: box, color: onTile ? 'white' : color, ['--kl-bg' as string]: onTile ? color : undefined }}
>
{markShapes(kind, random(hash(`${seed ?? name}m`)), letter)}
</svg>
);
// Case is a style, the text itself stays the name, so it reads and copies as written
const caseClass = word.upper ? 'uppercase' : word.lower ? 'lowercase' : '';
const wordmark = (fs: number) => (
<span
className={cn('relative inline-flex items-baseline whitespace-nowrap leading-none transition-[opacity,transform,filter]', caseClass, ' duration-700 ease-[var(--ease-kit)] motion-reduce:transition-none', hidden && 'translate-y-1 opacity-0 blur-[2px]')}
style={{ fontSize: fs, fontWeight: word.weight, letterSpacing: word.tracking, color: tone === 'mono' ? 'currentColor' : 'var(--foreground)', transitionDelay: animate ? '150ms' : undefined }}
>
{name}
{word.dot && <span style={{ color, marginLeft: '0.04em' }}>.</span>}
{word.bar && <span aria-hidden className="absolute -bottom-[0.18em] left-0 h-[0.09em] w-[38%] rounded-full" style={{ background: color }} />}
</span>
);
if (layout === 'app-icon') {
return (
<span ref={ref} role="img" aria-label={name} className={cn('relative inline-grid shrink-0 place-items-center overflow-hidden', className)} style={{ width: size, height: size, borderRadius: round(size * 0.24), background: tone === 'mono' ? 'var(--foreground)' : color }}>
<span aria-hidden className="absolute inset-0 bg-[linear-gradient(160deg,rgb(255_255_255/0.28),transparent_55%)]" />
<span aria-hidden className="absolute inset-0 rounded-[inherit] shadow-[inset_0_1px_0_rgb(255_255_255/0.35),inset_0_-1px_0_rgb(0_0_0/0.12)]" />
<span className="relative" style={{ ['--kl-bg' as string]: tone === 'mono' ? 'var(--foreground)' : color }}>
{mark(round(size * 0.56), true)}
</span>
</span>
);
}
if (layout === 'mark') {
return (
<span ref={ref} role="img" aria-label={name} className={cn('inline-flex', className)}>
{mark(size)}
</span>
);
}
if (layout === 'wordmark') {
return (
<span ref={ref} className={cn('inline-flex', className)}>
{wordmark(round(size * 0.82))}
</span>
);
}
if (layout === 'stacked') {
return (
<span ref={ref} className={cn('inline-flex flex-col items-center', className)} style={{ gap: round(size * 0.28) }}>
{mark(size)}
{wordmark(round(size * 0.5))}
</span>
);
}
return (
<span ref={ref} className={cn('inline-flex items-center', className)} style={{ gap: round(size * 0.3) }}>
{mark(size)}
{wordmark(round(size * 0.62))}
</span>
);
}
/** Names that sound like small, believable companies. Combine with a seed for endless placeholders. */
export const PLACEHOLDER_BRANDS = ['Brightwell', 'Fernhall', 'Kestrel', 'Lumora', 'Northbay', 'Oakline', 'Quillo', 'Rivetta', 'Solace', 'Tidewell', 'Veridian', 'Wrenfield', 'Halden', 'Marisol', 'Corvana', 'Ostara'];Frequent questions
How do I install Logo generator?
With aiw add @ai-website/logo-generator. The command puts components/ui/logo-generator.tsx into your project, the code is yours from then on. It needs no further npm packages.
Which variants does Logo generator have?
5 variants, Marks, Lockups, App icons, Logo cloud and Brand sheet.
Can an AI assistant build with Logo generator?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Logo generator with aiw_components and install it with aiw_add @ai-website/logo-generator.
Can I adjust Logo generator without code?
Yes, in the Studio. All 5 variants are editable there, texts, lists and switches. The Studio writes the matching GeneratedLogo code to copy.