Nakodo shows brands creators from YouTube, Instagram and TikTok in the same list. Every row has the creator's platform next to them, and that little mark turned out to be the most rule-bound component in the codebase.
The reason it exists at all is not design. YouTube's Developer Policies (III.F.2) and the YouTube API Services Branding Guidelines require the YouTube logo next to data that came from the API, as a link to the content on YouTube. We decided that if YouTube's data gets an attribution, the other two should get one too, so three platforms get three marks drawn to three different rulebooks.
The rules, written where the paths are
Each component carries its own rules in a header comment, because the next person to touch the file is the one who needs them:
// YouTube's logo and icon, shown next to YouTube data as the API terms require
// (Developer Policies III.F.2, YouTube API Services Branding Guidelines). The
// paths are YouTube's own SVG, unmodified, so the colours are YouTube's and not
// from our palette: red #FF0000, the wordmark Almost Black #282828 on light
// backgrounds and white on dark.
//
// The rules this follows:
// - always a link to YouTube content, opened on YouTube;
// - at least 20px tall, with clear space at least the size of the triangle
// (about 9px at 20px), which the link's padding gives;
// - never the most prominent thing on the screen, and never inside a sentence.
// The rules this follows, from Meta's brand resources:
// - the glyph, not the app icon, to link to an Instagram account;
// - always a link to the account, opened on Instagram;
// - the gradient or a single colour, never recoloured or redrawn;
// - clear space around it, which the link's padding gives.
// TikTok's note and wordmark, next to TikTok data the way YouTube's logo is
// next to YouTube's (see ./youtube). The paths are TikTok's own, unmodified:
// cyan #00F2EA and red #FF004F around a black note, which turns white on dark
// backgrounds as TikTok's reversed logo does. TikTok's colours, not our
// palette; always a link to the account, opened on TikTok, with the link's
// padding as clear space.
Three things follow from all three lists at once, and they are the design constraints:
- The paths are theirs, unmodified. No redrawing to match our stroke weight, no simplifying, no running them through an icon set.
-
The colours are theirs, not ours. Our palette is defined in OKLCH custom properties, so a hex code in a component is a smell. Grep the whole app for six-digit hex values and you get these three files (
#FF0000,#282828,#00F2EA,#FF004F,#262626and Instagram's gradient stops) and exactly one other line:themeColor: "#f9f5ed"in the root layout, because atheme-colormeta cannot be a CSS variable. - It is always a link, and never the loudest thing on the screen.
Sizing is a compliance detail, not a taste one
Every mark is h-5 w-auto, which is 20px tall, and every link around them is p-2.5, which is 10px of padding:
className={cn(
"inline-flex shrink-0 items-center rounded-sm p-2.5 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
className,
)}
20px is YouTube's stated minimum height. The clear space rule asks for at least the width of the play triangle, which at a 20px logo is about 9px, and 10px of link padding satisfies it without a single margin anywhere else. The padding is doing a second job as the tap target: 20 + 10 + 10 is a 40px control, four pixels under the 44px usually recommended, which is a compromise I made knowingly to keep dense result rows from growing.
There is exactly one exception, and it is a typographic one:
// The glyph with the wordmark beside it, its height level with the capital.
// Taller than YouTube's logo because the wordmark's g hangs below the line.
export function InstagramLogo({ className }: { className?: string }) {
return (
<svg viewBox="0 48.3 1002 224.7" className={cn("h-6 w-auto shrink-0", className)} ...>
Instagram's wordmark has a descender. Setting it to the same 20px as the others makes its capitals look smaller than YouTube's, because 20px of box is being shared with the tail of the g. h-6 (24px) puts the cap heights level. If you have ever had two logos look wrong side by side at identical heights, this is why: you matched the boxes and the eye matches the letters.
One set of paths, two components, four viewBoxes
The icon and the full logo are not two drawings. They are the same paths with a different window onto them:
| Mark | Icon viewBox | Logo viewBox |
|---|---|---|
| YouTube | 1.9 0.4 155.7 109.2 |
0 0 492 110 |
18 18 96 96 |
0 48.3 1002 224.7 |
|
| TikTok | 0 0 258 291.379 |
0 0 1000 291.379 |
The icon viewBox is cropped to the glyph, so the wordmark simply falls outside the window. Nothing is duplicated, nothing can drift, and a correction to a path corrects both renderings.
Instagram needs slightly more than a crop, because its glyph artwork and its wordmark artwork come from different coordinate systems:
<g transform="translate(0 64) scale(1.5625) translate(-18 -18)">
<Glyph />
</g>
<path transform="translate(186 0)" d={WORDMARK} className="fill-[#262626] dark:fill-white" />
Read the transforms right to left: move the glyph's own 18 unit origin to zero, scale it up by 1.5625, then drop it 64 units so its cap height sits on the wordmark's baseline grid. Written once in a component, rather than in Illustrator and then forgotten.
Dark mode happens in the fill class, not in a prop
None of these components take a theme prop. The parts that are allowed to change colour say so themselves:
{WORDMARK.map((d) => (
<path key={d.slice(0, 12)} d={d} className="fill-[#282828] dark:fill-white" />
))}
<path d="M177.083,93.525c..." className="fill-black dark:fill-white" />
Each platform's brand rules allow the reversed version of their mark on a dark surface, so the wordmark and TikTok's note flip to white while the colour parts (the red rounded rectangle, the cyan and red offsets, Instagram's gradient) stay exactly as they are. A dark panel inside a light page gets the right mark with no JavaScript and no context.
The id that can blank the others
Instagram's glyph is two copies of the same path filled with two radial gradients, and gradients need ids. Ids are global to the document.
// Instagram's gradient, in the glyph's own units. Each drawing needs its own
// ids, or a hidden copy elsewhere on the page can blank the others.
function Glyph() {
const id = useId().replace(/[^\w-]/g, "");
return (
<>
<defs>
<linearGradient id={`${id}-b`}>...</linearGradient>
<linearGradient id={`${id}-a`}>...</linearGradient>
<radialGradient id={`${id}-c`} href={`#${id}-a`} ... />
<radialGradient id={`${id}-d`} href={`#${id}-b`} ... />
</defs>
<path fill={`url(#${id}-c)`} d={GLYPH} />
<path fill={`url(#${id}-d)`} d={GLYPH} />
</>
);
}
The failure mode is specific and confusing when you meet it in the wild. Hardcode the ids, render a results page with 20 Instagram creators on it, and every glyph resolves url(#a) against the first definition in the document. That is fine until something unmounts or hides the first one: a collapsed row, a dialog that closed, a virtualised list. The definition goes, and every other glyph on the page loses its fill and renders as nothing. Not an error, not a warning, just invisible logos.
useId() is the fix because it is stable across server render and hydration, so the ids in the HTML match the ids React expects on the client. The replace(/[^\w-]/g, "") is there because React's generated ids contain characters that are awkward inside a url(#...) reference, and a fragment reference is not the place to find out.
One accessible name, on the thing you can click
Every mark is decorative in the accessibility tree, and the link carries the name:
<svg viewBox="18 18 96 96" className={...} aria-hidden="true" focusable="false">
const label =
platform === "web" ? `Open ${name ?? "this business"}'s website` : `Open ${name ?? "this creator"} on ${PLATFORM_LABELS[platform]}`;
aria-hidden plus focusable="false" on the svg, aria-label plus title on the a. A screen reader hears "Open Ridge on YouTube, link" once, not a path description followed by the word "image". focusable="false" is still worth writing: older Internet Explorer behaviour made SVGs focusable, and some embedded webviews inherit surprising defaults.
One component picks, and the fourth mark is ours
const MARKS: Record<Exclude<Platform, "youtube">, Record<"icon" | "logo", React.ComponentType>> = {
instagram: { icon: InstagramIcon, logo: InstagramLogo },
tiktok: { icon: TikTokIcon, logo: TikTokLogo },
web: { icon: WebsiteIcon, logo: WebsiteLogo },
};
// A link to the creator on their platform, marked with that platform's own
// icon (rows) or logo (the creator's page). YouTube's API terms require its
// logo next to YouTube data; Instagram and TikTok get theirs the same way.
// Same padding for all three, so rows line up.
YouTube is deliberately excluded from that record and routed to its own YouTubeLink. Not because it needs different markup, but because its rules are a contract with an API provider rather than a brand preference, and a component with a comment full of policy references is harder to casually restyle than a row in a lookup table.
The fourth mark is the odd one out, and it says so:
// A business's own website. Unlike the platforms' marks, this is ours, so it
// takes the ink colour.
export function WebsiteIcon({ className }: { className?: string }) {
return <GlobeIcon className={cn("size-5 shrink-0 text-ink-700 dark:text-ink-300", className)} aria-hidden="true" />;
}
Business campaigns find businesses that have a website and no platform account, and a globe is not anybody's trademark. It is the one of the four that is allowed to take our palette, and marking that difference in a comment is what stops the next person "tidying up" the other three to match it.
Where to see the platforms this is all for
The marks themselves live behind sign in, next to creator rows, but the three platforms each have a public side on our site: YouTubers by niche, Instagrammers by niche and TikTokers by niche are the hubs for each platform, and how it works describes what we read from each one and what the API terms let us keep.
If you are building something that displays another platform's data, the summary is: read their branding guidelines before you draw anything, keep their artwork unmodified and their colours out of your palette, put the accessible name on the link rather than the graphic, and give every gradient its own id.
Top comments (0)