Skip to content

Instantly share code, notes, and snippets.

@armand1m
Created April 6, 2026 12:05
Show Gist options
  • Select an option

  • Save armand1m/5f8312d4418f1fa1f7ae6d5a0364d912 to your computer and use it in GitHub Desktop.

Select an option

Save armand1m/5f8312d4418f1fa1f7ae6d5a0364d912 to your computer and use it in GitHub Desktop.
Smart quotes but using simple quotes
/**
* typography.tsx
*
* Smart quotes wrapper for Next.js SSR.
* Converts straight quotes to curly quotes at render time (server-side),
* avoiding hydration mismatches and client-side flickering.
*
* Usage:
* <Text>He said "hello" and it's fine.</Text>
* <Text as="h1">The "Best" Title</Text>
*
* Safe to use with dangerouslySetInnerHTML because input is
* always a hardcoded string — never user-generated content.
*/
import React from "react";
// ---------------------------------------------------------------------------
// Smart quotes transformer
// ---------------------------------------------------------------------------
function smartQuotes(text: string): string {
return (
text
// Double quotes: "word" → "word"
.replace(/"([^"]*)"/g, "\u201C$1\u201D")
// Single quoted words: 'word' → 'word'
.replace(/'([^']*)'/g, "\u2018$1\u2019")
// Apostrophes in contractions and possessives: it's, don't, user's
.replace(/(\w)'(\w)/g, "$1\u2019$2")
);
}
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
type TextTag = "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "span" | "li" | "blockquote" | "label";
type TextProps<T extends TextTag = "p"> = {
as?: T;
children: string;
className?: string;
} & Omit<React.ComponentPropsWithoutRef<T>, "children" | "as" | "className" | "dangerouslySetInnerHTML">;
export function Text<T extends TextTag = "p">({
as,
children,
className,
...rest
}: TextProps<T>) {
const Tag = (as ?? "p") as TextTag;
return (
<Tag
className={className}
dangerouslySetInnerHTML={{ __html: smartQuotes(children) }}
{...(rest as object)}
/>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment