Branding
Logo, LogoSpinner, Wordmark, Logomark, and social icons.
Branding primitives live in @flow-industries/ui/logo and @flow-industries/ui/icons.
Logo, LogoSpinner, Wordmark, Logomark
Usage:
import { Logo, LogoSpinner, Wordmark, Logomark } from "@flow-industries/ui/logo";
<Logo size={28} />
<Logo size={64} color="oklch(0.7 0.2 320)" />
<LogoSpinner size={32} />
<Wordmark start="flow" end="industries" />
<Logomark />
<Logomark start="FLOW" end="DOCS" size="lg" />Logomark sizes: sm, default, lg, xl, 2xl, 3xl. LogoSpinner respects prefers-reduced-motion — animation pauses for users who've opted out.
Source:
import { motion, useReducedMotion } from "motion/react";
import type { AriaAttributes } from "react";
import { cn } from "../utils/cn";
interface LogoProps {
className?: string;
size?: number;
color?: string;
}
export function Logo({ className, size = 28, color }: LogoProps) {
return (
<div
className={cn("bg-pink rounded-sm", className)}
style={{
width: size,
height: size,
backgroundColor: color,
}}
/>
);
}
interface LogoSpinnerProps extends LogoProps {
label?: string;
"aria-hidden"?: AriaAttributes["aria-hidden"];
}
export function LogoSpinner({
className,
size = 64,
label = "Loading",
"aria-hidden": ariaHidden,
}: LogoSpinnerProps) {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
role="status"
aria-label={label}
aria-hidden={ariaHidden}
className={cn("bg-pink rounded-sm", className)}
style={{ width: size, height: size }}
animate={{ rotate: shouldReduceMotion ? 0 : 90 }}
transition={{
type: "spring",
stiffness: 150,
damping: 14,
repeat: Infinity,
repeatDelay: 0.05,
}}
>
<span className="sr-only">{label}</span>
</motion.div>
);
}
type Size = "sm" | "default" | "lg" | "xl" | "2xl" | "3xl";
const wordmarkSizes = {
sm: "text-xs tracking-widest gap-0.5",
default: "text-sm tracking-widest gap-0.5",
lg: "text-lg tracking-widest gap-0.5",
xl: "text-2xl tracking-widest gap-0.5",
"2xl": "text-4xl tracking-widest gap-0.5",
"3xl": "text-6xl tracking-widest gap-0.5",
} as const;
export function Wordmark({
start,
end,
size = "default",
className,
}: {
start: string;
end: string;
size?: Size;
className?: string;
}) {
return (
<span
className={cn(
"inline-flex items-baseline uppercase",
wordmarkSizes[size],
className,
)}
>
<span className="font-extralight">{start}</span>
<span className="font-semibold">{end}</span>
</span>
);
}
const logomarkSizes = {
sm: { logo: 14, wordmark: "sm" as const, gap: "gap-1.5" },
default: { logo: 20, wordmark: "default" as const, gap: "gap-2" },
lg: { logo: 28, wordmark: "lg" as const, gap: "gap-2.5" },
xl: { logo: 36, wordmark: "xl" as const, gap: "gap-3" },
"2xl": { logo: 48, wordmark: "2xl" as const, gap: "gap-3.5" },
"3xl": { logo: 64, wordmark: "3xl" as const, gap: "gap-4" },
} as const;
export function Logomark({
start = "Flow",
end = "Game",
size = "default",
className,
color,
}: {
start?: string;
end?: string;
size?: Size;
className?: string;
color?: string;
}) {
const s = logomarkSizes[size];
return (
<span className={cn("inline-flex items-center", s.gap, className)}>
<Logo size={s.logo} color={color} />
<Wordmark start={start} end={end} size={s.wordmark} />
</span>
);
}Social icons
XIcon, DiscordIcon, GitHubIcon, BlueskyIcon. All follow currentColor and accept className.
Usage:
import { XIcon, DiscordIcon, GitHubIcon, BlueskyIcon } from "@flow-industries/ui/icons";
<a href="https://x.com/flowindustries" aria-label="X">
<XIcon className="size-5" />
</a>Source:
import type * as React from "react";
type IconProps = React.SVGProps<SVGSVGElement>;
export function XIcon(props: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" role="img" {...props}>
<title>X</title>
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
);
}
export function DiscordIcon(props: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" role="img" {...props}>
<title>Discord</title>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
);
}
export function GitHubIcon(props: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" role="img" {...props}>
<title>GitHub</title>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
);
}
export function BlueskyIcon(props: IconProps) {
return (
<svg viewBox="0 0 568 501" fill="currentColor" role="img" {...props}>
<title>Bluesky</title>
<path d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 192.94 552.332 218.303C536.046 290.003 461.001 307.578 394.592 296.04C513.055 317.572 544.13 394.735 473.795 471.899C338.979 619.778 289.59 483.693 284.624 468.379C284.039 466.593 284 466.014 284 467.076C284 466.014 283.961 466.593 283.376 468.379C278.41 483.693 229.021 619.778 94.2049 471.899C23.8699 394.735 54.9454 317.572 173.408 296.04C106.999 307.578 31.9545 290.003 15.6684 218.303C9.94551 192.94 0 75.2916 0 57.9464C0 -28.9064 76.1343 -1.61183 123.121 33.6637Z" />
</svg>
);
}Usage guidelines
- The Logo and Logomark are brand-reserved — use them to represent Flow, not as decorative fills.
- Avoid placing the Logo on backgrounds that match its color (default pink
--brand). If you need to, override--brandfor the region or passcolorto<Logo />/<Logomark />. - The social icons are SVG and scale cleanly.
LogoandLogoSpinnerare CSS boxes (bg-pink),WordmarkandLogomarkare live text — render them as components, never as a rasterized screenshot.