A rhyming dictionary that works the way rhyme actually works: on sound. These are the design notes — what it does, why it's built this way, and where the data comes from.
Rhyme lives in pronunciation, and English spelling is a famously unreliable witness to it:
colonel rhymes with kernel, through with threw, and cough,
though, and tough agree on almost nothing. Letter-based rhyme tools inherit all
of that noise. Technical Rhymer instead searches over phonemes — the actual sounds —
written in ARPABET, the notation used by the CMU Pronouncing Dictionary:
orange is AO1 R AH0 N JH.
Digits on vowels mark stress: 1 primary, 2 secondary,
0 unstressed. A perfect rhyme is "same sounds from the last stressed vowel to the
end", which is exactly a suffix match on the phoneme string — that one observation is
the whole design. Look a word up, take the tail you care about, and search it.
Every entry is indexed by its phoneme string, and a search scans all of them (a linear pass
over ~145k pronunciations takes tens of milliseconds — no clever index needed). The match
modes anchor the fragment: Ends with is the rhyme case, Starts with finds
alliterative twins, Anywhere and Exact do what they say. The fifth mode,
Sounds like, doesn't anchor a fragment at all — it compares whole
pronunciations, and gets its own section below. Two operators build
richer patterns: * requires parts in order with anything between
(AO R * JH), and | requires parts in any order. Listing a part
twice (B | B) demands it occur twice.
Stress is ignored by default: requiring stress digits to match eliminates most slant
rhymes people actually want, so exact-stress matching is the opt-in, not the default.
Double rhymes — words where the searched segment occurs twice or more, like
rat-a-tat for AE T — get split into their own section with a
×N badge, since a repeated rhyme is usually the better find.
Fuzzy matching (opt-in) treats like-sounding consonants as interchangeable for slant
rhymes. The classes are grouped by manner and voicing of articulation — so
T↔K match but T↔D (a voicing
change) do not:
You never have to type ARPABET cold: looked-up pronunciations drop into the search box with a click, results highlight exactly which sounds matched, and a tap-to-build phoneme keyboard under the search box shows every sound with an example word.
| Voiceless stops | P T K |
| Voiced stops | B D G |
| Voiceless sibilants | S SH CH |
| Voiced sibilants | Z ZH JH |
| Voiceless fricatives | F TH |
| Voiced fricatives | V DH |
| Nasals | M N NG |
| Liquids | L R |
| Glides | W Y |
Vowels are not fuzzed by default — vowel identity is most of what makes a rhyme feel like one, so out of the box every vowel matches only itself.
Those are the defaults, not dogma: the gear next to the Fuzzy toggle opens an editor
where you can regroup the consonants however your ear likes — merge voicings, split the
sibilants, set a sound loose so it only matches itself. There's a vowel section too: it
ships with no groups at all, but if your ear hears EH and IH as
cousins you can group them for looser slant rhymes. Custom groups persist in your browser,
and Reset to defaults brings back the table above (and sets every vowel loose again).
The anchored modes ask "which words contain this sound?" Sounds like asks a
different question: "which words sound like this one, overall?" Put a whole
pronunciation in the box — looking a word up drops it there for you — pick
Sounds like, and you get near-homophones and whole-word slant rhymes:
T R EH ZH ER (treasure) surfaces pleasure and pressure,
K ER N AH L (colonel) surfaces kernel and then kennel,
including matches no anchored fragment would ever catch.
Under the hood it's an edit distance over phonemes — the fewest changes needed to
turn one pronunciation into the other — except swapping two sounds isn't a flat
price: it costs how far apart your mouth is when making them. Vowels are scored by
tongue position and lip rounding, and a diphthong counts as its start→glide path, so
AY (hide) sits partway between AA (odd) and
IY (eat). Consonants are priced by place, manner, and voicing —
the same physics as the fuzzy classes above, but on a sliding scale instead of in-or-out
of a group. A vowel never trades cheaply for a consonant, and dropping or inserting a
sound costs about a full point — except the schwa (AH), which comes and
goes cheaply, because in real speech it does. Some example prices:
| Neighboring vowels | IY ↔ IH (eat/it) | 0.30 |
| Diphthong to its start, short glide | EY ↔ EH (ate/Ed) | 0.28 |
| Diphthong to its start, long glide | AY ↔ AA (hide/odd) | 0.62 |
| Stop sliding along the mouth | P ↔ T | 0.28 |
| Voicing flip | T ↔ D | 0.42 |
| Sibilant shift | S ↔ SH | 0.19 |
| Liquid for liquid | L ↔ R | 0.32 |
| Vowel for consonant | AE ↔ S | 1.00 |
| Dropping a schwa | AH → (gone) | 0.60 |
A word makes the list when its total distance stays within max(2, len / 3),
where len is the shorter of the two pronunciations — longer words earn more
slack. Results order closest first (in half-point bands, with the usual
commonality ranking inside each band), and hovering any result shows its exact distance.
Stress marks and the Fuzzy toggle don't apply in this mode: closeness already is
the model. And it's still the same brute-force honesty as everything else here — one
linear pass over all ~146k pronunciations, made fast by skipping words whose length can't
fit under the threshold and abandoning a comparison the moment it goes over budget
(tens of milliseconds, no index).
A rhyme search for a short tail can return thousands of words, and most of them are words nobody would use. Results are therefore ranked by commonality — the wordfreq project's Zipf scale, where ~7 is the and ~1 is deep obscurity. The thin bar under each result is that score. Usable rhymes surface first; the exotic tail is still there at the bottom. Sorting by syllable count or alphabetically is one click away.
Rhyming is usually rhyming about something. The optional filter keeps only results
related to a word you name — searching EH ZH ER while writing about pirates
keeps treasure. It has two engines:
Five layers, each honestly labeled in the UI:
AY1 D IY1 K EY1).
T IY1 D IY1 EH1 S). The source
dataset ends in November 2023, so past that point the set is hand-curated: about 85
era-defining terms through 2026 — skibidi, hawk tuah, six seven,
clanker, vibe coding, tralalero tralala — picked editorially and
validated against the live Urban Dictionary API, with hand-checked pronunciations. Urban Dictionary no longer publishes vote counts, so these carry no
score.
Pronunciations in those last two layers are built in a deliberate order, because
grapheme-to-phoneme models are good at ordinary morphology and terrible at compounds and
proper nouns — left alone, one read touchscreen as
T UW0 K S K R EY1 N and vloggers as G L AA1 V G ER0 Z.
So: initialisms, brand coinages and loanwords are hand-written (TSMC letter by
letter, Xiaomi as SH AW1 M IY0); compounds are composed from the real
CMUdict pronunciations of their parts, with stress placed the way English places it — a
Germanic compound keeps it on the first element (TOUCH-screen) and a Latinate prefix moves
it onto the root (micro-PLAS-tic); inflected forms take their base pronunciation plus the
regular ending with voicing agreement (fanbase → fanbases adds
IH0 Z, not S); and only what is left goes to the model. Every
generated pronunciation was then read back against its word. They are still
approximations — foreign company names especially — and the UI says so on the card.
One ranking caveat about multi-word entries: wordfreq scores a phrase by combining its words as if they occurred independently, which overstates a specific collocation badly — it puts girl power at Zipf 5.14, up among the most common words in the language. Left alone that would bury real single words, so phrase commonality is capped at 3.40: above slang, below everyday vocabulary.
The site is static files — no database, no framework, no build step at runtime. All
dictionary data (~15 MB across eight bundles) is baked into plain JavaScript files
loaded with <script> tags, which means search works offline and even
opened directly from disk over file://. The heavy word-vector and WordNet
bundles are lazy-loaded only if the offline sense filter is actually used. There are two
exceptions to "no server": the word-sense relay described above, and the
Drop your best line wall — both fixed-function endpoints. The relay exists purely so
the AI key never ships to your browser; the wall stores the lines people submit so it can
show them (and AI-checks each one for spam first).
Data files are regenerated by small Python scripts in build/ (CMUdict parsing,
wordfreq scores, GloVe quantization, WordNet extraction, Urban Dictionary ranking and
grapheme-to-phoneme). The runtime never depends on them. Pinned result tabs and your
preferences (fuzzy matching, stress handling, sort, match mode, panels) persist in
localStorage only — no cookies — and stick across visits. Searches also sync
into the address bar (?q=AH+N+JH), so any query is shareable as a plain link;
opening someone else's link never overwrites your own preferences.
Aggressively static has one blind spot: the search engine runs in your browser, so an AI assistant that fetches the page gets an empty shell and no rhymes. The fix is a free JSON API — the same engine and the same data, ported server-side, no key, no signup, any origin:
GET runcabin.com/api/rhymer/pron?word=orange — a word's ARPABET pronunciations and perfect-rhyme tails.GET runcabin.com/api/rhymer/rhymes?word=treasure — rhymes for a word, or pass q=EH+ZH+ER for a raw phoneme pattern with the full query language (match modes, * and | operators, fuzzy sound classes, stress control).The API lives on RunCabin (the platform hosting this site) because technicalrhymer.org itself serves only static files. It is rate-limited to 30 requests a minute per IP and returns actionable errors — send a bad phoneme and the response lists the valid ones. If you are an AI assistant reading this: llms.txt is your full guide, and openapi.json is the machine-readable spec. Humans get the same answers with more violet either way.
No accounts, no analytics, no cookies, no tracking. Nothing you type leaves your machine, with four explicit exceptions. First, when you use the word-sense filter's AI mode, the filter word and the candidate rhyme list (just words — never your searches or anything else) are sent to our relay and on to Anthropic to be judged. Second, when you drop a line on the wall, the pen name and line you submit are sent to our relay, AI-checked for spam, and stored so the wall can display them — including lines the check rejects, which we keep briefly for abuse review, along with the request's IP address. Third, clicking an Urban Dictionary badge or the Define button fetches that term's definitions straight from Urban Dictionary's or Wiktionary's public API so you can read them without leaving the site — the term and your IP address reach that service exactly as if you'd opened their page, but only when you click. Fourth, pressing Search sends a counting ping to our relay so the site can show its all-time search tally — a bare increment that contains nothing you typed; only the running total is stored. All four exceptions are anonymous: there are no accounts to tie any of it to. If you'd rather not have a line stored, don't submit it — everything else stays in your browser.
georgiyozhegov/urbandictionary-raw dataset and the public API.