Recipes
Short, copyable solutions to common tasks. Each one uses only the public API and plain JavaScript.
Look up every kanji in a text#
Split the text into characters and keep the ones the dictionary knows. getDetails() returns null for kana, punctuation and Latin letters, so a filter removes them.
function kanjiIn (text) {const seen = new Set()return [...text].filter(ch => !seen.has(ch) && seen.add(ch)).map(ch => Kanji.getDetails(ch)).filter(Boolean)}kanjiIn('今日は良い天気です').map(k => `${k.literal}: ${k.meanings[0]}`)// ['今: now', '日: day', '良: good', '天: heavens', '気: spirit']
Build a study list#
Filter by JLPT level, then sort by frequency so the most useful characters come first. Entries without a frequency rank go last.
const byFrequency = (a, b) => (a.freq ?? Infinity) - (b.freq ?? Infinity)const level3 = Kanji.search({ jlpt: 3 }).sort(byFrequency)level3.length // 181level3.slice(0, 5).map(k => k.literal).join('') // '同事自民発'
For school kanji up to a grade, use a range that starts at 1. Leaving out min would also include every character that has no grade.
const firstThreeGrades = Kanji.search({ grade: { min: 1, max: 3 } })firstThreeGrades.length // 440
Pick a random character from one level#
random() picks from the whole dictionary, which is mostly rare characters. For flashcards, filter first and then pick from the result.
const pool = Kanji.search({ jlpt: 4, sort: false })function flashcard () {return pool[Math.floor(Math.random() * pool.length)]}
Passing sort: false skips sorting, which you don't need here. Run the search once and reuse the pool.
Top matches for a meaning#
A meaning search ranks the whole dictionary, so take the first few results. Add a filter to stick to common characters. Matching is fuzzy, so results further down may only be loosely related.
Kanji.search({ meaning: 'mountain' }).slice(0, 3).map(k => k.literal)// ['岳', '山', '岾']Kanji.search({ meaning: 'mountain', grade: { min: 1, max: 8 } }).slice(0, 3).map(k => k.literal)// ['岳', '山', '泉']
Find characters with an exact reading#
A romaji search is fuzzy and returns everything, ranked. To get only the characters that have a reading, compare against the reading lists yourself. Remove the hyphens first, because they only mark prefix and suffix forms.
const clean = r => r.replace(/-/g, '')function withReading (kana) {return Kanji.dump().filter(k =>k.kunyomi.some(r => clean(r) === kana) ||k.onyomi.some(r => clean(r) === kana))}withReading('みず').map(k => k.literal) // ['水', '瑞', '瀣']
Pass kana in the same script as the list you're checking: hiragana for kun readings, katakana for on readings. To accept romaji, convert it first with a library such as WanaKana.
Load the library on demand#
The library includes about 1.9 MB of dictionary data. In a browser app, a dynamic import() keeps it out of your main bundle so it only downloads when someone needs it. Bundlers such as Vite and webpack split it into a separate file automatically.
let kanjiasync function getKanji () {kanji ??= import('kanji.js').then(mod => mod.default)return kanji}searchInput.addEventListener('focus', () => { getKanji() }, { once: true }) // start earlyasync function onSearch (query) {const Kanji = await getKanji()return Kanji.search({ meaning: query }).slice(0, 20)}
Search in a Web Worker#
A meaning or romaji search can take a few hundred milliseconds. To keep typing smooth, run it in a worker. The example uses Vite's worker syntax. Other bundlers have their own.
import Kanji from 'kanji.js'self.onmessage = ({ data: { id, options, limit } }) => {const results = Kanji.search(options).slice(0, limit)self.postMessage({ id, results })}
const worker = new Worker(new URL('./kanji-worker.js', import.meta.url), { type: 'module' })let nextId = 0function search (options, limit = 50) {const id = nextId++return new Promise(resolve => {worker.addEventListener('message', function onMessage ({ data }) {if (data.id !== id) returnworker.removeEventListener('message', onMessage)resolve(data.results)})worker.postMessage({ id, options, limit })})}const results = await search({ meaning: 'river' }, 10)