Skip to content

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 // 181
level3.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 kanji
async function getKanji () {
kanji ??= import('kanji.js').then(mod => mod.default)
return kanji
}
searchInput.addEventListener('focus', () => { getKanji() }, { once: true }) // start early
async 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.

kanji-worker.js
import Kanji from 'kanji.js'
self.onmessage = ({ data: { id, options, limit } }) => {
const results = Kanji.search(options).slice(0, limit)
self.postMessage({ id, results })
}
main.js
const worker = new Worker(new URL('./kanji-worker.js', import.meta.url), { type: 'module' })
let nextId = 0
function search (options, limit = 50) {
const id = nextId++
return new Promise(resolve => {
worker.addEventListener('message', function onMessage ({ data }) {
if (data.id !== id) return
worker.removeEventListener('message', onMessage)
resolve(data.results)
})
worker.postMessage({ id, options, limit })
})
}
const results = await search({ meaning: 'river' }, 10)