# WM Keyboard — complete documentation > A modern, privacy-first Android keyboard — offline intelligence, 843 languages, themes, tools and an addon ecosystem. Source: https://wmkeyboard.pages.dev/ · Licence: MIT · Generated from the site's own pages. Every page of the documentation follows, in sidebar order, each under its own URL. Cite the URL of the section you use rather than this file. --- # WM Keyboard URL: https://wmkeyboard.pages.dev/ Summary: A modern, privacy-first Android keyboard: offline intelligence, 1,555 languages, themes, tools and an addon ecosystem.
The short version
## Why WM Keyboard Dictionaries, prediction, autocorrect, transliteration and emoji search all run on device. No telemetry, no cloud round-trips. The network is opt-in, one tool at a time. You start in the languages your phone is already set to. Avro-style Bangla phonetic typing, CJK input and downloadable dictionaries for 330+ languages are all here, plus musical notation, braille chording and Morse. Clipboard manager, translator, OCR & QR scanner, voice typing (online and offline Whisper), handwriting, GIFs & stickers, calculator, media controls, even an on-device LLM. Material You themes with a full editor, icon packs, addon repositories you can self-host, and sandboxed Python plugins that add new tools to the keyboard. glide Trace a path over the letters and lift your finger. The glide engine runs on the same dictionaries as prediction, so it works in every language, Bangla included. Cursor swipes, long-press popups and spacebar gestures ride along. tools/whisper-catalog — The Whisper model catalog inside WM Keyboard settings Dictate with Whisper running entirely on device. Pick from 29 models, tiny ones for older phones up to big multilingual ones, and nothing you say leaves the device. There is a lighter online engine too, if you want it. This is the Bangla National (Jatiya) layout, rendered live from the same key data the app ships. Hover any key to peek at its long-press alternates, or tap shift for the second layer. Avro phonetic typing, custom layouts and per-field language detection are all part of the same system. privacy/on-device-learning-toggles — The on-device learning toggles in settings Prediction, autocorrect and learning all happen offline. Every tool that touches the network says so up front and stays off until you switch it on. Learned words can be inspected, exported or cleared at any time.Pick a door
## Jump in Install & set up — Get the APK, enable the keyboard, and pick your languages. — /start/installation/ A two-minute tour — The strip, the toolbar and the panels, in the order you'll meet them. — /start/tour/ Languages & layouts — Add languages, switch between them, and edit or build your own layout. — /languages/overview/ The tool belt — Clipboard, translate, voice, OCR, handwriting and the rest of the panels. — /tools/overview/ Suggestions & autocorrect — Tune prediction, autocorrect confidence, smart chips and your personal dictionary. — /smart/suggestions/ Themes & appearance — Material You themes, the theme editor, fonts, icon packs and backgrounds. — /themes/overview/ Privacy at a glance — What stays on device, what the network is used for, and how to turn it off. — /privacy/overview/ Every setting, explained — The reference section documents every screen, toggle and slider. — /reference/settings/Free and open source
No ads, no tracking, MIT licensed. Get it from Google Play, F-Droid or GitHub and make it yours.
Writing about WM Keyboard? Logos, screenshots and copy are in the media kit.
_phonetic_v1r1.wmpi.gz`, `v1r2` for Bangla and Assamese, 1 to 9 MB). The keyboard fetches it right after the word list and reads it in place from storage, so it costs almost no memory. Only lists you imported yourself are still folded on the phone. Until the file is there (no connection, or the build with no internet), the keyboard folds the downloaded list itself, as it always did. You can import the file instead: it is in the language's language pack, and [Offline imports](https://wmkeyboard.pages.dev/privacy/offline/) installs it.
The rules alone get about half of everyday words exactly right: Tamil 66%, Kannada and Malayalam 60%, Marathi 57%, Punjabi 57%, Gujarati 55%, Telugu 53%, Odia 42%, Persian 67%, Arabic 46%. The rest are lexical facts no rule can know, which is what the first two layers are for.
In the first two layers the words before count too: one spelling can stand for several words, and the one or two you typed just before move up the reading that usually follows them, on the strip and for what space writes. The evidence is your own typing (every word pair you commit is remembered) and the language's word-pair data, which all eleven have as a 1 to 2 MB download in the language's **Dictionaries** group, next to the word list. Without it, it is your own habits only, and those start empty. A known spelling only ever trades places with another known spelling of the same buffer. Languages & layouts / your language / Spellings / Use the words before turns it off.
A spelling none of that recognizes may be a typo. The keys are checked against the romanized spellings people write for the language, as an English typo is checked against English words, so a slip of one key still reaches the word. See [Bangla](https://wmkeyboard.pages.dev/languages/bengali/) for how it decides. It works on any language here with romanized spellings to check against, and it follows the autocorrect switch for that language: with autocorrect off the fix is only offered on the strip.
The layers above are not part of autocorrect, and the autocorrect toggle doesn't control them. The known-spellings list has its own switch, Languages & layouts / your language / Spellings / Use known spellings, and so does a space committing a dictionary word instead of the rules' spelling: **Correct to dictionary words** in the same group.
## Neural suggestions
Languages & layouts / your language / Suggestion strip / Neural suggestions
The three layers above know the words their lists know and the spellings their rules expect. Loose spellings fall between them: `valobashi`, `bhalobasi` and `bhalobaashi` are all ভালোবাসি to anyone who reads Bangla. **Neural suggestions** adds a fourth source for that, IndicXlit, a transliteration model by [AI4Bharat](https://github.com/AI4Bharat/IndicXlit) that learned from millions of romanized words how people write 21 Indic languages in Latin letters. It is off by default, and it is the same switch on every language that has it: Bangla, Hindi, Urdu, Assamese, Marathi, Gujarati, Punjabi, Odia, Tamil, Telugu, Kannada and Malayalam.
Turned on, the strip shows up to three of the model's readings right after the word the space bar writes. Readings that are in the language's word list come first. The word the space bar writes never changes (unless you turn on [Let it choose the word](#let-it-choose-the-word)), and the model never corrects anything by itself: its readings are only offered, and a tap writes one. So it helps most when you scroll or glance past the first word, and costs nothing when the first word was already right.
Measured on held-out words, the right word in the first four places of the strip:
| Language | Without | With |
|---|---:|---:|
| Bangla, chat spellings the known-spellings list doesn't have | 52% | 80% |
| Hindi, chat spellings the known-spellings list doesn't have | 88% | 93% |
| Bangla, Aksharantar test words | 30% | 80% |
| Hindi, Aksharantar test words | 55% | 84% |
| Urdu | 43% | 73% |
| Marathi | 60% | 86% |
| Gujarati | 58% | 87% |
| Punjabi | 55% | 75% |
| Odia | 33% | 66% |
| Tamil | 39% | 81% |
| Telugu | 57% | 90% |
| Kannada | 51% | 89% |
| Malayalam | 32% | 77% |
The other languages were measured on 2,000 words of the Aksharantar test set each, with the language's word list installed. Aksharantar spells long vowels doubled (`aa`, `ee`), which Avro reads differently from most of these layouts, so Bangla's Aksharantar row starts low; the chat rows are the fairer picture for Bangla.
The model is one download of 13 MB, shared by every language, from the data repository's `models` release on GitHub, checked against a SHA-256 built into the app. It also needs ONNX Runtime, the same part [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) runs on, about 33 MB, which the Google Play build gets from Play and the other builds from Maven Central. Turning the setting on starts both downloads under one progress bar, runtime first, and the size the switch quotes counts the runtime until it is on the phone, so a first download reads about 46 MB rather than 13. The row under it shows how far they are and starts them again after a failure; once the model is here, tapping the row or swiping it offers to delete it. Deleting turns neural suggestions off on every language that had them; the model is also a row of its own under **Downloads** on the storage screen. Everything runs on the phone, on a thread of its own, and nothing you type leaves it. A reading that takes longer than the strip can wait for appears a moment later, while you are still on the word.
The last row of the group is the **Romanized dictation model**, the same model family trained the other way round, from the script back to Latin letters. It has nothing to do with typing: it is for dictating this language in Latin letters (Banglish, Hinglish), where it spells the words the spelling lists don't know, and it is a download of its own. See [Hinglish, Banglish and other romanized languages](https://wmkeyboard.pages.dev/tools/voice/#hinglish-banglish-and-other-romanized-languages).
### Let it choose the word
Languages & layouts / Bangla or Hindi / Suggestion strip / Let it choose the word
Bangla and Hindi have a second switch, under Neural suggestions and off by default. With it on, the model may also pick what the space bar writes, but only in one case: the rules spell something that is not a word in the language's word list or among your own words, and the known-spellings list has nothing for what you typed. Then the model's best reading that is in the word list goes first in the strip, and a space writes it. The rules' spelling moves to second place, one tap away. Whenever the rules spell a known word, or the known-spellings list has the spelling, nothing changes.
The text in the field still shows the rules' spelling while you type, as it does when the space bar is about to write a dictionary word that sounds the same. The strip's first word is what a space writes. If you press space before the model has answered, the keyboard waits up to 30 ms for it, so typing fast does not change which word you get.
Measured with the known-spellings list on, how often the space bar writes the right word:
| Words | Without | With | Made worse |
|---|---:|---:|---:|
| Bangla, Aksharantar test words (4,000) | 25.0% | 56.4% | 22 |
| Bangla, chat spellings the known-spellings list doesn't have (2,500) | 41.8% | 52.8% | 31 |
| Hindi, Aksharantar test words (4,000) | 40.9% | 48.6% | 24 |
| Hindi, chat spellings the known-spellings list doesn't have (692) | 78.2% | 80.1% | 0 |
What it gives up is Avro's rule that the space bar writes exactly what you spelled. A name or rare word spelled exactly to the rules, but missing from the word list, can turn into a listed word that looks like it: `ananto` আনান্ত becomes অনন্ত, `bharotpur` ভারতপুর becomes ভরতপুর. On words spelled exactly to the rules and missing from the list, that happened to about one in six in Bangla and one in fifteen in Hindi. For words the list has, it happened to 2 of 305 in Bangla and none of 427 in Hindi. Urdu gained one point, too little for that trade, so it does not have the switch. It does nothing while the strip is fixed to the word as typed, or while desktop Avro's candidate list is shown in its own row, since the first word there would then disagree with the space bar.
## What each language does
### Marathi, Gujarati and Punjabi
These follow Hindi phonetic's model: you write the word the way it sounds, the inherent vowel drops in speech, and a final vowel is long. Each language has its own grammatical endings the rules keep clear of a conjunct.
- **Marathi** reads `z` and `zh` as झ (`maza`, `tuza`), `L` as ळ, and knows its own postpositions and verb endings: `tyala` त्याला, `karnar` करणार, `karun` करून, `gharachya` घराच्या, `mitranno` मित्रांनो.
- **Gujarati** reads `z` as ઝ and `x` as ક્ષ (`Axar`, `Laxmi`), writes the neuter and infinitive endings with their nasal (`hu` હું, `karvu` કરવું, `kartu` કરતું), and the locative too (`gharma` ઘરમાં).
- **Punjabi** is written in Gurmukhi, which has almost no conjuncts: a doubled consonant is the addak (`pakka` ਪੱਕਾ), a nasal is a tippi or a bindi by the vowel before it (`mainu` ਮੈਨੂੰ, `ton` ਤੋਂ), and a y-glide is a vowel carrier rather than ਯ (`pyar` ਪਿਆਰ, `gya` ਗਿਆ, `duniya` ਦੁਨੀਆ).
### Odia, Tamil, Telugu, Kannada and Malayalam
These languages pronounce every vowel, so people type every vowel: `amma`, `nanna`, `avan`. A consonant straight after a consonant is a conjunct, and a word-final consonant takes a virama (Tamil's pulli, Malayalam's chillu).
- **Short and long e and o** are separate letters in the four Dravidian languages. Type `E` or `O` for the long one if you want to be exact; otherwise each language's rules pick the likelier one from context (`nenu` నేను, `beku` ಬೇಕು).
- **Tamil** has no separate voiced or aspirated letters, so `g d b` and `k t p` write the same Tamil letter. `zh` is ழ, `L` ள, `R` or `rr` ற, `ndr` ன்ற (`nandri` நன்றி), and a single stop between vowels is doubled the way Tamil writes it (`iruku` இருக்கு).
- **Malayalam** follows the Mozhi conventions people already use: `t` ട, `th` ത, `d` ഡ, `dh` ദ, `nt` ന്റ (`ente` എന്റെ), `ng` ങ്ങ, `nj` ഞ, `zh` ഴ, and a final `m` is ം.
- **Telugu** reads `th` and `dh` as the plain dentals (`thammudu` తమ్ముడు), and a final `m` as ం (`manam` మనం).
- **Kannada** reads `th` as ತ and knows its plural `-galu` and the long first vowels of its commonest words (`beku` ಬೇಕು, `helu` ಹೇಳು).
- **Odia** writes ଯ where a word opens and ୟ elsewhere, ୱ for `w`, and spells its nasal clusters as conjuncts (`ranga` ରଙ୍ଗ, `pancha` ପଞ୍ଚ).
### Assamese
Assamese uses Avro's rules, because people romanize it the way they romanize Bangla, with three changes: every র is ৰ, an `x` is the Assamese স (`xokolu` সকলো, `axom` অসম), and a `w` gliding into a vowel is ৱ.
### Persian
`kh` is خ, `ch` چ, `zh` ژ, `gh` ق (`Gh` for غ). Short a, e and o aren't written inside a word, a final `e` is the silent ه (`khoone` خونه), and `i` is always the long ی. The verb prefixes می and نمی are written with the zero-width non-joiner the language uses (`mikonam` میکنم, `nemitoonam` نمیتونم).
### Arabic
Arabizi works, digits and all: `2` ق, `3` ع, `3'` غ, `5` خ, `6` ط, `6'` ظ, `7` ح, `8` ق, `9` ص, `9'` ض. A number you type on its own stays a number. `al`, `el` and `il` before a consonant are the article ال (`elyoum` اليوم, `essalam` السلام), `g` is ج, `ch` is ش, and a closing `a` is ا with ة and ى offered beside it. `2` is ق rather than the hamza of most charts, because the people who type `2` most mean the glottal stop their dialect says for ق (`2albi` قلبي); the real hamza words are in the known-spellings list.
### Greek
Greeklish doesn't care which spelling you use: `kalimera` and `kalhmera`, `thelw`, `8elw` and `thelo`, `kserw`, `xerw` and `3erw` all find the same word, accent included, because the word list settles whether an i is ι, η, υ, ει or οι. `b` is μπ, `d` is δ and `h` can be η or χ. A number on its own stays a number. The accent and the right spelling come from the Greek word list, so download it from the language's page first: without it you get the rules' plain reading, `καλημερα`. There's no romanized list yet, so swiping isn't decoded on this layout.
### Tibetan
Wylie is an exact system rather than a guess, so there's nothing to rank: what you type is what you get. The space bar types a tsheg, `/` a shad, and `'` the a-chung. Stacks follow the rules of Tibetan spelling (`bsgrubs` བསྒྲུབས), `.` keeps two letters apart (`g.yag` གཡག) and `+` forces a stack (`pad+ma` པདྨ). Capitals are the Sanskrit letters and long vowels of Extended Wylie (`Ta` ཊ, `A` ཨཱ).
## Languages with rules only
77 more languages have a phonetic layout named after them: `kamal` writes कमल on Maithili phonetic, కమల on Lambadi (Telugu) phonetic and কমল on Chittagonian phonetic.
| Script | Languages |
|---|---|
| Devanagari | Ahirani, Awadhi, Bagheli, Bagri, Bhili, Bhojpuri, Bodo, Bundeli, Chhattisgarhi, Dhundhari, Dogri, Garhwali, Godwari, Gondi, Gujari, Halbi, Harauti, Haryanvi, Kangri, Kannauji, Kashmiri, Konkani, Korku, Kurukh, Lambadi, Magahi, Maharashtrian Konkani, Maithili, Malvi, Mandeali, Marwari, Merwari, Mewari, Nimadi, Rangpuri, Sadri, Shekhawati, Sindhi, Surjapuri, Varhadi, Wagdi |
| Bengali | Bishnupriya Manipuri, Bodo, Chittagonian, Garo, Kok Borok, Manipuri, Mundari, Santali |
| Gujarati, Odia, Kannada, Telugu | Bhili; Halbi; Konkani, Lambadi, Tulu; Gondi, Lambadi |
| Arabic | Bagri, Balti, Brahui, Dakhini, Dogri, Eastern and Southern Balochi, Gujari, Kashmiri, Marwari (Pakistan), Northern and Southern Hindko, Pahari-Potwari, Pashto, Saraiki, Shina, Sindhi |
| Other | Yiddish in Hebrew letters, Mongolian in Cyrillic, Santali in Ol Chiki, Manipuri in Meetei Mayek |
These work from a table of sounds, with none of the grammar rules the languages above have, so treat the first spelling as a guess. Capital letters pick the retroflex sounds, as on the other Indic layouts, and on Sindhi, Saraiki and Pashto they and doubled letters also reach the letters Urdu doesn't have: `dd` is ڏ in Sindhi and ݙ in Saraiki, and `x` is ښ in Pashto. Mongolian follows chat spelling, where `q` is ө and `v` is ү.
All of them rank against a word list when one is installed, so the strip leads with a word the language has rather than the rules' guess: `odor` finds өдөр on Mongolian even though the rules wrote о. Many of these languages have no list to download. Among the Arabic-script ones and the last row, only Pashto, Sindhi, Saraiki, Yiddish, Mongolian, Santali and Manipuri do, and for the others the strip shows what the rules wrote and their other readings, until you [import a list](https://wmkeyboard.pages.dev/languages/dictionaries/). A list in another script than the layout's ranks nothing.
## Glide typing
Swiping over a phonetic layout draws a romanization, so the stroke is decoded against romanized spellings and turned into the script, as on Avro. Marathi, Gujarati, Punjabi, Tamil, Telugu, Kannada, Malayalam and Arabic each have a romanized word list in the language catalogue that widens what a swipe can find. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/).
## Switching mid-word
If you notice halfway through a word that you're still on your Latin keyboard, switch to the phonetic layout: with **Carry the word over** on (the default), what you already typed is converted too. See [Switching languages](https://wmkeyboard.pages.dev/languages/switching/#switching-mid-word-onto-a-phonetic-layout).
## Details & edge cases
**These are new, and every table is a draft.** The rules, the known-spellings lists and the loanword lists were written and measured against each language's word list and a hand-written set of everyday chat spellings, not reviewed by native speakers. If a common word comes out wrong with the word list installed, [open an issue](https://github.com/wasi-master/wmkeyboard/issues) and name the word and the spelling you typed.
**Some word lists are better than others.** Tamil's has no frequencies at all, so the keyboard breaks ties by how close a candidate is to what the rules spell. Malayalam's comes from subtitles and Gujarati's, Marathi's and Kannada's from encyclopedic text, so spoken forms are thinner there, and the known-spellings lists lean on chat forms for that reason.
**The phonetic machinery only loads if the layout is on.** A language with just its script layout enabled builds none of it.
**There are no selection-bar conversion chips for these languages yet.** Bangla and Hindi have chips to convert selected text between roman and native script; these languages don't.
---
# Switching languages
URL: https://wmkeyboard.pages.dev/languages/switching/
Section: Languages
Summary: Four ways to switch: spacebar swipe, hold-drag picker (list or carousel), globe key, system switcher.
Once more than one layout is enabled, there are four ways to move between them on the touchscreen: swipe the spacebar, hold the spacebar and drag through a list, use a dedicated key, or reach for Android's own input-method switcher. All four call the same underlying switch. Pick whichever suits your hands, because nothing about what a switch *does* changes. A physical keyboard adds a fifth: its own language key, or Ctrl+Space. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/#switching-language) for those.
However you make it, the switch lands right away. A half-typed word is committed as it stands (unless the new layout is a phonetic one and **Carry the word over** is on, see below), the keys redraw, and the suggestion strip refills from the new language's dictionary immediately, so you don't have to type a letter first to stop being offered words from the language you just left.
languages/spacebar-resting-label — The resting spacebar shows the current language's name, with switch-direction arrows on either side.
## Swiping the spacebar
space-swipe — Swipe the spacebar to switch language.
The spacebar has two independent swipe slots, each set under Typing / Gestures:
- **Quick swipe on the spacebar**: a swipe that starts moving right away. Defaults to **Language**.
- **Press and hold, then swipe**: hold briefly, *then* swipe. Defaults to **Cursor**.
languages/spacebar-language-preview — Mid-swipe: the language preview shows every enabled layout, current selection highlighted.
Each slot can be set to Nothing, Language, Cursor, Numpad, or Keyboards (Android's list of keyboard apps, see [Spacebar gestures](https://wmkeyboard.pages.dev/typing/gestures/)). Which slot fires depends on *when* the drag starts, not on how far you drag. The reference point is the long-press delay, 300ms by default. A swipe that crosses the touch-slop threshold before that delay uses the quick-swipe slot, and one that starts after it uses the hold + swipe slot. Once the language preview or the picker list is up, any further drag switches language whatever the slot is set to. You are looking at a language chooser by then, so the drag does what it looks like it does. That never overrides your hold + swipe slot, because the chooser only appears on a hold when that slot is set to Language, or to Nothing with the quick swipe on Language (see below).
With exactly two layouts enabled, a swipe toggles to the other one. Dragging further in the same direction doesn't toggle again. Reverse direction to switch back. With three or more layouts, a quick flick moves exactly one layout over, however fast or far it travels, so the one in the middle is always a flick away. To go further, keep the swipe moving: after a short pause (about a fifth of a second) each further 44dp of horizontal drag steps to the next one in your switch order, while reversing direction answers straight away. After a quick flick the preview stays up for a moment after you lift, so even a flick shows which layout it landed on. **Keep the new language on screen**, beside the other spacebar settings in Typing › Gestures, sets how long: half a second by default, up to a full second, or Off to have it go the instant you lift. A slow swipe that rested on its layout for a quarter of a second or more has already shown it, so the preview goes the instant you lift whatever that is set to. Either end of the list resists wrapping around, since a wrap costs 2.5× the travel of a normal step, so the boundary layout "parks" until a deliberate extra pull cycles you back to the start. (Two layouts of the same language still toggle rather than cycle. The count that matters is enabled layouts, not distinct languages.)
While a language swipe is in progress, a preview pops up above the spacebar showing every enabled layout as a row of chips: the current selection bold and highlighted, the rest dimmed. With more than five layouts enabled it shows a five-chip window centered on your selection instead of the whole list. Release to commit the highlighted layout. A tap with no movement always types a space.
The resting spacebar label follows **The spacebar shows** (also under Typing → Gestures), which offers Language (the default), Layout, or Both. In Language mode, a transliterating layout such as Avro always shows its own name instead of the shared language name. Avro, Probhat and Jatiya all display as বাংলা, so the language name alone wouldn't say which method is under your fingers. In Both mode, a transliterating layout shows the combined form instead, such as বাংলা (Avro phonetic). That's the same as any other layout whose name differs from its language. Otherwise, whenever two enabled layouts share a language, the label adds the layout name alongside it so you can tell them apart. The ◀▶ hint arrows need three things at once: **Arrows on the spacebar** on (the default), one of the two swipe slots set to Language, and more than one layout enabled. On a spacebar too short to fit the arrows beside its label, such as the one on the symbols layer, the arrows are left off so the label stays readable.
### Naming a layout yourself
Any switched-on layout can go by a name you choose. Open its language under Languages & layouts, and under the layout cards there is a **Name for (layout)** row for each layout that is on, subtitled with what the keyboard shows for it now. Tap it, type the name, and tap **Save**. A swipe on the row offers the same **Rename**, plus **Use the automatic name** once you've set one.
Your name replaces the automatic one everywhere the keyboard names the current layout: the resting spacebar, the swipe and 🌐 preview, the hold-drag picker, and the Switch order list in settings. **The spacebar shows** and the same-language rule above no longer apply to that layout, so "Work" stays "Work" whichever mode is set. A custom **Spacebar text** still wraps it, with `%s` standing for your name. Clear the field, or tap **Use the automatic name** in the dialog, to go back. The names are saved with your other settings and come back with a backup.
### Typing speed on the spacebar
Turn on **Typing speed on the spacebar** under Typing / Gestures and the spacebar shows how fast you're typing, as "42 wpm" next to its label. It's off by default.
- The number is your speed over the last few words, up to the last 30 seconds, not your lifetime average. It needs two words and two seconds of typing before it shows anything.
- It changes each time a word goes in: a word you type and end with a space or a mark, a suggestion you pick, a glided word, or a two-thumb word. Each new reading moves the number half way, so one fast or slow word doesn't make it jump.
- A pause of more than 5 seconds clears it, and the spacebar goes back to its plain label until you've typed two more words.
- It counts each word's letters and one space after it, the usual five characters to a word. Over 250 wpm is held at 250.
- It never shows in a password field or while incognito is on, and nothing you type there is timed. Going into one of those fields, or coming back out, starts the count again from nothing.
- Each field gets its own count too. Moving to a different field or closing the keyboard clears the number; the same field reloading itself keeps it.
- Nothing is kept: no number, no text. So it works with [Statistics](https://wmkeyboard.pages.dev/reference/settings/statistics/) turned off, and it starts again from nothing when the keyboard restarts.
## Holding to drag through the list
space-hold — Hold the spacebar, then drag to pick a language without lifting.
Two things have to be true first: more than one layout enabled, and **Press and hold, then swipe** set to Language, or set to Nothing while **Quick swipe on the spacebar** is on Language. With neither slot on Language, holding the spacebar never opens the picker, and turning on **Swipe down to hide the keyboard** doesn't change that: the hold repeats the space, as it does with that switch off. That slot is the setting for what a hold *does*, so pointing it at Cursor or Numpad hands it the hold outright and no picker opens. Otherwise the picker swallows the very drag you asked for, which is what used to happen. A fresh install ships that slot on **Cursor**, so this gesture is opt-in: set the slot to Language (or Nothing, if you would rather the hold only ever open the picker and your quick swipe is on Language) to get it.
With that done, press and hold the spacebar. Shortly before the long-press delay would fire (250ms, or your long-press delay if that is shorter), one of two things opens:
- **The inline preview** from the section above, if you have four or fewer layouts enabled and the quick-swipe slot is set to Language.
- **A scrollable picker list**, once you have more than four layouts enabled, or if you've reassigned the quick-swipe slot to something other than Language.
### Keeping the preview past four layouts
Typing / Gestures / Picker for more than four layouts
The switch to the list at five layouts is a setting. **Picker for more than four layouts** ships on. Turn it off and the hold keeps the sideways inline preview however many layouts you have on. The preview shows five layouts at a time and slides along as you drag, so the ones off screen are still in reach, and the hold-drag keeps moving the same way the swipe does. The quick-swipe rule still applies: with the hold slot on Language and the quick swipe on something else, the hold opens the picker at any length. The 🌐 key's long press always opens the picker.
Once the list is open, drag vertically to walk it one row at a time. The first bit of finger movement after the list appears is absorbed as calibration, so drift from before the hold fired never selects a neighbor by accident. Lift your finger after moving and the highlighted row is selected. Lift without moving at all and the list stays open, so you can tap the row you want.
### Keeping the drag sideways: the carousel
Typing / Gestures / Language picker shape
The list has one awkward side. The swipe that switches language moves left and right, but the list walks up and down, so the moment you have enough layouts to need it the same gesture changes direction on you. **Language picker shape** offers a **Carousel** instead of the list. It is the same picker turned sideways: one strip of chips above the spacebar, centred on the layout you are typing in, with the rest scrolling off either end.
With the carousel on, a spacebar hold-drag walks left and right, one chip per 44dp, the same step the plain swipe uses, and the strip slides to keep the highlighted chip in the middle so your finger never has to chase it. Everything else about the hold is unchanged: the first bit of movement is still absorbed as calibration, lifting after a move selects the highlighted chip, and lifting without moving leaves the strip up so you can tap a chip or scroll it by hand. The 🌐 key's long press opens the same carousel.
The setting ships on **List**, so nothing changes until you pick the carousel. It only decides what the picker looks like. The four-layout threshold above (and whether it applies at all), and the quick-swipe slot, still decide whether the picker or the inline preview opens.
A second setting takes this whole gesture away on purpose, the same way the hold + swipe slot does. Put characters into **Spacebar long press keys** under Typing → Gestures and the hold opens [your own popup](https://wmkeyboard.pages.dev/typing/gestures/#your-own-keys-on-the-spacebar-long-press) instead of the picker, which is what you want if you type in one language and would rather have your emoji there. The globe key and the spacebar swipe still switch language. Clear the field to get the picker back.
The last row, **Other keyboards…**, isn't a layout. It opens Android's own "Choose input method" sheet, which is how you hand the field to a different keyboard app entirely. It sits below the scrolling part of the list so it's always in reach however many layouts you have on, and the hold-drag walk skips it: it's a tap-only row. In the carousel it is the keyboard glyph past the divider at the strip's end, and the same rules apply.
### A way out of the switcher
Languages & layouts / Switching language / Extra stop in the switcher
With two languages on, say Bangla and English, holding the spacebar shows just those two. **Extra stop in the switcher** adds one more stop after the last language, so the preview reads Bangla, English, then a gear. Drag onto the gear and lift, and the keyboard opens the Languages screen, where you add, remove and reorder what the switcher holds. Set it to **Other keyboards** instead and the last stop is a keyboard glyph that opens Android's "Choose input method" sheet, the same one the picker's **Other keyboards…** row opens.
It ships on **None**. A hold-drag reaches the extra stop, and so does a swipe along the spacebar when the swipe switches language. A quick flick still moves one language at a time, so the swipe lands on the gear only when it keeps going past your last language. With two languages and the extra stop on, both the hold-drag and the swipe walk three stops in a line rather than toggling between two, so a flick left from the first language no longer jumps to the second. The list and carousel pickers show the gear as a row of their own (a gear icon in the carousel), next to **Other keyboards…**, when it is set to Language settings.
## Using the globe key
A fresh install replaces the bottom-row 🌐 key with an emoji key. Every built-in layout defines that key, but the **Emoji key instead of 🌐** setting is on by default, so it opens the emoji panel instead of switching language until you turn it off. Language switching still works the whole time. It lives on the spacebar (above) and the Languages screen (below) instead.
Layout & size / Bottom row keys / Emoji key instead of 🌐
languages/globe-key-toggle — Layout & size → Bottom-row keys: turning off Emoji key instead of 🌐 restores the language-switch key.
Turn this toggle off in Layout & size / Bottom row keys to get the language key back. With it off, the bottom-row key behaves as a dedicated language switcher:
- **Tap** cycles to the next enabled *layout* (not language) in your switch order, wrapping from the last back to the first. Three layouts of the same language still count as three separate stops.
- **Long-press** opens the same picker the spacebar hold-drag uses (the list, or the carousel if you chose it), regardless of how many languages you have enabled.
### Switching from the toolbar
The **Switch language** tool does what a tap on 🌐 does: one tap moves to the next layout in your switch order. Pin it to the toolbar, or leave it in the toolbox, when you want the switch away from the keys, for example so a stray tap beside the comma never changes language. It's off the keys entirely, so it works whatever the bottom row holds and whether **Emoji key instead of 🌐** is on. Find it under Tools / Switch language.
### Going back to the last language
Layout & size / Bottom row keys / 🌐 goes back to the last language
With many languages enabled, you often type only two or three of them at a time, and a different two or three depending on who you're writing to. Cycling in a fixed order then means tapping past languages you aren't using. Turn on **🌐 goes back to the last language** (off by default) and the key goes by what you used most recently instead, the way Alt+Tab moves between windows:
- **One tap** goes back to the layout you used before this one. Tap again and you're back where you started, so any two layouts toggle, wherever they sit in your switch order.
- **Quick taps**, each within about 0.8 seconds of the last, go further back: the second tap lands on the layout you used before those two, and so on. From the second tap, a list shows the layouts most recently used first, with the one you're on highlighted. Stop tapping, or type anything, and the list closes.
Only the layout you stop on counts as used, so tapping past a language on the way doesn't push it up the list. Every switch you make counts, whether it came from the spacebar, the picker or the key, so the list is ready when you turn the setting on. Layouts you've never switched to come last, in switch order. With two layouts enabled this works the same as the plain tap. A physical keyboard's language key follows this setting too. The spacebar swipe doesn't: it always steps through your switch order.
There's a separate **Comma key opens emoji** toggle (off by default) that replaces the comma key with an emoji key instead. It's independent of the globe-key setting above. Don't confuse the two.
### Ignoring a 🌐 tap right after typing
Layout & size / Bottom row keys / Ignore 🌐 right after typing
The 🌐 key sits between the comma and the spacebar, so a fast thumb aiming for either lands on it now and then, and the language changes in the middle of a word. **Ignore 🌐 right after typing** (off by default) sets a short window, from 50 ms up to a full second: a 🌐 tap that comes sooner than that after a typed key does nothing. Letters, space, backspace and enter count as typing. Shift, the mode keys and the tools don't, and neither does 🌐 itself, so quick taps through the list with **🌐 goes back to the last language** still work. A deliberate switch comes after a pause, which is longer than the window, and a press and hold still opens the picker at any time. The window is greyed out while the key is hidden.
### Sliding from 🌐 onto a shortcut
Key press / Press and hold shortcuts / Slide from 🌐 for shortcuts
With **Slide from 🌐 for shortcuts** on (off by default), putting your finger on 🌐 and sliding it onto a letter runs that letter's editing shortcut when you lift: onto C copies, V pastes, X cuts, A selects all, Z undoes and Y redoes. They're the same six actions on the same keys as the [hold shortcuts](https://wmkeyboard.pages.dev/typing/basics/#hold-a-letter-for-a-quick-edit), including any keys you moved them to, but the slide works whether the hold shortcuts are on or off. A line follows your finger from the key, as with the Shift and Ctrl drags. Lifting anywhere else does nothing, and sliding back onto 🌐 before you lift switches language like a tap. The slide has to start before the hold opens the language picker; once the picker is up, the finger belongs to it.
To take the key off the bottom row altogether, turn off **Show the 🌐 key** in the same group. The spacebar gets its width. Switching still works from the spacebar, so keep a swipe set to Language if you type in more than one.
## Reordering and trimming the ring
Languages & layouts
languages/languages-screen-order — The Languages screen: Your languages, Add language, and Switch order.
Every key and gesture above cycles through the same ordered list: your enabled layouts, in switch order. A fresh install seeds that list from your phone's own language settings, taking one layout per language it recognises, and then adds QWERTY. The whole list is capped at four layouts, so a phone set to four other languages fills up before QWERTY gets a slot. A phone set to Bangla and English starts with Avro and QWERTY. A phone set to English alone starts with QWERTY on its own and has nothing to cycle to yet. Everything else in the catalogue sits inactive until you add it. See [Languages overview](https://wmkeyboard.pages.dev/languages/overview/) for how the full catalogue works and what's in it.
The head of that list is also where the keyboard opens. On a phone set to French, the seeded ring is AZERTY (French) then QWERTY, and the first keyboard you see is AZERTY, with its spacebar label, dictionary and autocorrect. Reordering the ring moves that starting point with it, right up until the first time you switch language yourself; from then on the keyboard opens in whichever layout you last used, and reordering only changes what the next step lands on.
On the **Languages** screen:
- **Your languages** lists each enabled language once. Tap one to enable or disable its individual layouts, or to remove the language entirely (only available once you have more than one language enabled, so you can never empty the list).
- **Add language** searches the whole catalogue and enables an unadded language's first layout. A language with data to fetch asks about the download first (see [Downloads & mobile data](https://wmkeyboard.pages.dev/languages/downloads/)), and its Cancel backs out of the language too.
- **Switch order** appears once you have more than one layout enabled. Drag to reorder the raw layout list, so two English layouts (QWERTY and AZERTY, say) keep independent slots in the ring even though they're both "English."
Any language can stay enabled without a stop in the ring, which suits one you want only for its words: a romanized language such as Banglish on your English keyboard, or English suggestions on a Swedish one. Open it under **Your languages** and turn on **Do not show in the language switcher**. The globe key, the spacebar swipe, the picker and Android's own keyboard picker then skip it, while its words keep showing on every keyboard that lists it under **Also suggest from**. With English, Banglish and a Bangla layout enabled, hiding Banglish leaves two stops: English with Banglish suggestions, and Bangla. With Swedish and English enabled and English listed under Swedish's **Also suggest from**, hiding English leaves one stop: the Swedish layout, with both dictionaries suggesting. If hiding would leave nothing to switch to, every layout stays in the ring.
There's no "maximum visible" setting. (The one recently-used option, [🌐 goes back to the last language](#going-back-to-the-last-language), only changes which layout the key visits next, not which ones are in the ring.) The ring is exactly as long as your enabled-layout list, so reordering or removing is the only way to shorten it. Ring length changes behavior on its own at three thresholds: two layouts swipe as a toggle rather than a cycle, more than four turn the hold-drag gesture from an inline preview into a full list (unless **Picker for more than four layouts** is off), and more than five shrink the swipe preview to a sliding five-chip window.
## Switching from Android's system menu
Two things open Android's keyboard picker straight from the keys: the **Other keyboards…** row at the bottom of the picker list, and any key you set to **Switch keyboard** in the layout editor (it's in the Layers group of the action picker, see [Custom layouts](https://wmkeyboard.pages.dev/languages/custom-layouts/)). Both put up the system's "Choose input method" sheet, which is where you change to a different keyboard app. If Android refuses to show the sheet, the keyboard opens the input-method settings screen instead, which reaches the same place.
To skip the list, bind a key to **Switch to a specific keyboard** (same Layers group) and pick the keyboard app it goes to. One tap then hands the field straight to that keyboard. The choice is remembered by the app's id, so it keeps working when other keyboards come and go. If that keyboard has since been turned off or uninstalled, the key falls back to the system sheet. Before Android 9 there is no direct switch, so it always opens the sheet.
The settings below are about the opposite direction: getting Android's own switcher to list *this* keyboard's languages. The **System language switcher** group on the Languages screen connects your switch ring to Android itself:
languages/system-switcher-toggle — Languages → System language switcher: register every enabled layout as an Android input-method subtype.
- **List languages in the Android switcher** (off by default) registers every enabled layout as an Android input-method subtype, so the system's "Choose input method" sheet lists each one and can switch to it directly. The two stay in sync either way: a switch you make inside the keyboard is mirrored out to Android, and a switch made from the system sheet is followed back into the keyboard. It ships off because the sheet prints a subtype's label where it would otherwise print the keyboard's name, so one language name in that row reads as if the keyboard were called that. Left off, switching stays entirely inside the keyboard. It still tells Android the language you're typing in, under the keyboard's own name, so a system spell checker set to "use system language" checks that language rather than the phone's. A romanized language (Hinglish, Banglish, Arabizi and the other `_rom` entries) is listed under the name it has in the app, "Hinglish · Hindi (Romanized)", rather than the "Hindi (Latin)" Android would derive from its locale, so it can be told apart from plain Hindi in the same sheet. Every other language keeps Android's own translated name.
- **Show app name first** (off by default, shown only while the toggle above is on) puts "WM Keyboard" ahead of the language in the switcher's label instead of after it. Android still controls which part of the label is bold or greyed; this only changes the text order.
- **Select languages in the system settings** (also shown only while the first toggle is on) opens Android's own list of this keyboard's languages, where each one can be ticked or unticked. See the callout below for when you actually need it.
Registering a language with Android and that language being *switchable* are two different things. Android only lists the subtypes it considers enabled, and if nothing has enabled any, it picks them itself from your phone's system languages, which normally means exactly one. That is why the switcher can show a single entry however many layouts you have on.
Android 14 added an API that lets a keyboard enable its own subtypes. On Android 14 and newer, WM Keyboard enables all of them for you and the switcher lists the lot with nothing to configure. Below that there is no such API. Open **Select languages in the system settings**. Tick each language you want in the sheet.
A separate, unrelated toggle, **Remember language per app** (under Per-app language, off by default), has the keyboard remember whichever layout you last explicitly switched to in a given app and restore it the next time you type there. Suggestions and autocorrect switch along with it, to that language's words and its own **Also suggest from** list. Apps you haven't switched languages in yet follow whatever you last used globally, and a field that demands Latin input or advertises its own language still overrides the memory either way. Removing a layout from your list also drops it from every app that remembered it; those apps fall back to your last-used layout.
To decide for one app yourself, press **Set the language for an app** in the same group, pick the app, then pick a language or **Remember last**. The rule wins over the toggle for that app, and **Remember last** works there even with the toggle off. You can still change language while you type in the app; the rule applies again the next time you come back from another app.
**Forget remembered apps**, further down the Languages screen under Language data, clears that whole map after a confirmation. The row only appears once something is in it, and its subtitle counts how many apps have a language pinned. The feature itself stays on, so apps start being remembered again as you switch language inside them.
**Link romanized languages automatically** sits in the same group and is on by default. It pairs a romanized language such as Banglish with the languages that share its script, so suggestions come from both. Adding a language only makes that language's own links, so a link you removed by hand between two languages already on your list stays removed. Turn it off and adding a language links nothing.
## Switching mid-word onto a phonetic layout
Languages & layouts / Switching language / Carry the word over
Phonetic layouts spell their script with roman letters: Avro and Khipro write Bengali, Hindi phonetic writes Devanagari, and Urdu phonetic and the [other phonetic layouts](https://wmkeyboard.pages.dev/languages/phonetic/) write theirs. Which means the letters you type on your Latin keyboard are the very letters those layouts take, and noticing halfway through a word that you never switched no longer costs you the word. **Carry the word over** is on by default. Type `tu`, switch to Avro, and what you already typed becomes তু; the `mi` after it finishes তুমি. Without it the `tu` commits as Latin and only the `mi` is Bengali, so the whole word has to be typed again.
The letters are converted whether they were still being composed or already sitting in the field, and their capitals are dropped on the way, because the phonetic layouts spell retroflexes and aspirates with capitals and the `T` of a sentence-opening `Tumi` was not asked for as ট. A word with a digit, an apostrophe or a hyphen in it is left alone, as is anything longer than 32 letters, and so is a password field.
One case argues the other way, and is why the setting exists: finishing an English word, *not* typing a space, and switching to write the next word in Bengali. That word is carried over too, so `hello` becomes হেল্লো. Nothing distinguishes it from the `tu` above (in both, the caret sits after a run of roman letters), so if that is how you switch, turn this off and switches commit the word as they always did.
Only the phonetic layouts take a carried word. Switching to Probhat, Jatiya, a Latin keyboard or a Chinese or Japanese input method commits the word as it stands, since none of them spells with the letters you just typed.
## Switching from another app or a computer
Automation apps such as Tasker, and `adb` on a computer, can switch too. They send an intent that names a layout or a language, or asks for the next or previous one, and the keyboard switches as if you had pressed 🌐. Only layouts you turned on can be reached this way. It is off until you turn on Privacy / Let other apps control the keyboard. See [Automation: adb and Tasker](https://wmkeyboard.pages.dev/reference/automation/) for the intents and a Tasker example.
## Options
Languages & layouts
| Setting | Where | Default |
|---|---|---|
| Quick swipe on the spacebar | Typing → Gestures | Language |
| Press and hold, then swipe | Typing → Gestures | Cursor |
| Arrows on the spacebar | Typing → Gestures | On |
| The spacebar shows | Typing → Gestures | Language |
| Language picker shape | Typing → Gestures | List |
| Spacebar text | Typing → Gestures | Blank (live name) |
| Typing speed on the spacebar | Typing → Gestures | Off |
| Spacebar long press keys | Typing → Gestures | Blank (the hold opens the picker) |
| Show the 🌐 key | Layout & size → Bottom row keys | On |
| 🌐 goes back to the last language | Layout & size → Bottom row keys | Off |
| Ignore 🌐 right after typing | Layout & size → Bottom row keys | Off (50–1000 ms when on) |
| Slide from 🌐 for shortcuts | Key press → Press and hold shortcuts | Off |
| Emoji key instead of 🌐 | Layout & size → Bottom row keys | On |
| Comma key opens emoji | Layout & size → Bottom row keys | Off |
| Your languages / Switch order | Languages | Seeded from your phone's language list, then QWERTY, capped at 4 layouts |
| Carry the word over | Languages → Switching language | On |
| Extra stop in the switcher | Languages → Switching language | None |
| Remember language per app | Languages → Per-app language | Off |
| Forget remembered apps | Languages → Language data | A button, shown once apps are remembered |
| Link romanized languages automatically | Languages → Language data | On |
| Do not show in the language switcher | Languages → any language | Off |
| List languages in the Android switcher | Languages → System language switcher | Off |
| Show app name first | Languages → System language switcher | Off |
## Details & edge cases
- **A fresh install shows an emoji key where you might expect the globe.** It's the most common point of confusion with switching, and the callout above covers it. Switching itself is never disabled by the setting. Only the dedicated key's icon and behavior change.
- **The globe key's tap cycles layouts, not languages.** If a language has three enabled layouts, tapping the key visits all three as separate stops rather than skipping straight to the next language. The same goes for the recently-used order: each layout is its own entry.
- **With exactly two layouts enabled, swiping toggles instead of wrap-cycling.** Piling on more drag distance in the same direction won't toggle a second time. Reverse direction and you start a fresh toggle. (The count that matters is enabled layouts, so two layouts of the same language toggle too.)
- **A held-but-unmoved spacebar or globe-key long-press leaves the picker open for a direct tap** rather than dismissing it, list and carousel alike. There's no need to drag precisely if you'd rather tap the language you want.
- **Reordering affects layouts, not languages.** If you've added a second layout for the same language (AZERTY alongside QWERTY, say), Switch order treats them as independent stops, and moving one doesn't move the other.
- **A subtype that Android hasn't enabled stays invisible.** Registering is ours to do and enabling is Android's, and before Android 14 only you could bridge the gap. If the system sheet lists one language on an older phone, nothing is broken. Those languages just haven't been ticked in system settings yet.
- **The Android system switcher and per-app memory are independent settings.** You can have Android list every layout as a subtype while per-app memory stays off, or the reverse. Turning one off doesn't touch the other.
---
# Urdu
URL: https://wmkeyboard.pages.dev/languages/urdu/
Section: Languages
Summary: Type Urdu the way you text it: roman letters in, Urdu script out. Plus the Urdu-lettered keyboards for typing the script directly.
Urdu ships five layouts. **اردو** is the phonetic *key* arrangement (Urdu letters sitting where the roman letter that sounds like them would be), and it is what you get when you add Urdu. **Urdu phonetic** is the new one (issue [#496](https://github.com/wasi-master/wmkeyboard/issues/496)): a QWERTY grid that turns roman Urdu into the Urdu script as you type, so `aap kaise ho` commits آپ کیسے ہو. **کلیدی تختہ (NLA)** is the national standard keymap, **اردو (four rows)** spreads the letters over four rows, and **اردو T9** is the keypad. This page is mostly about the phonetic one, because it is the one with moving parts.
## Turning it on
Languages & layouts / اردو · Urdu / Layouts / Urdu phonetic
Add Urdu, open its screen, and tap the **Urdu phonetic** card under **Layouts** to turn it on. Tap the اردو card on the same screen to turn that one off if you don't want both. Then do the one thing that makes the biggest difference: download the Urdu word list from the **Dictionary** row on that screen. Urdu ships no word list inside the app, and the next two sections explain why this layout cares about that more than most.
## How it reads what you type
Every key is where it is on the English layout. What you type shows up in the Urdu script in the composing text straight away, with no separate conversion step, and the space commits it. The keys stay left-to-right while the text runs right to left, which is how every roman-key method for an RTL script works.
| You type | You get | Why |
|---|---|---|
| `din`, `kal`, `tum`, `mard` | دن, کل, تم, مرد | a short vowel is a diacritic nobody types, so it writes nothing |
| `ka`, `ki`, `ke`, `hai`, `to` | کا, کی, کے, ہے, تو | a vowel that ends a word is long, because Urdu ends a word on a letter |
| `ab`, `aap`, `ek`, `aur` | اب, آپ, ایک, اور | a vowel that opens a word takes its carrier: ا, آ, ای, او |
| `koi`, `hua`, `jaao` | کوئی, ہوا, جاؤ | a vowel running into a vowel takes a hamza |
| `yeh`, `woh`, `kya`, `liya` | یہ, وہ, کیا, لیا | …but a ی or و that is a *consonant* carries nothing |
| `dekh`, `likh`, `phir`, `ghar` | دیکھ, لکھ, پھر, گھر | aspiration is do-chashmi he: ھ |
| `khush`, `khabar` | خوش, خبر | `kh` opening a word is خ, not کھ |
| `sunna`, `pakka`, `iss` | سنا, پکا, اس | a doubled consonant is written once: Urdu doubles with a shadda nobody types |
| `accha` | اچھا | …and a doubled `c` is the aspirate, which is the word people mean |
| `Theek`, `kaRa`, `DaDa` | ٹھیک, کڑا, ڈڈا | capitals spell the retroflexes exactly |
| `Khabar`, `GhalaT`, `SubH`, `Zaroor` | خبر, غلٹ, صبح, ضرور | and the Perso-Arabic letters: خ غ ح ص ض ط ظ ق |
| `Kitab` | کتب | a capital that spells nothing reads as its lowercase self |
### Urdu phonetic key map
**Vowels.** Urdu has no vowel signs. A vowel is a letter, a diacritic, or nothing at all, so the form depends on where in the word it falls.
| Key | Opening a word | Inside a word | Closing a word |
|---|---|---|---|
| `a` | ا | nothing | ا |
| `aa`, `A` | آ | ا | ا |
| `i` | ا | nothing | ی |
| `ee`, `ii`, `I` | ای | ی | ی |
| `u` | ا | nothing | و |
| `oo`, `uu`, `U` | او | و | و |
| `e`, `ay`, `ey` | ای | ی | ے |
| `ai` | ای | ی | ے |
| `o` | او | و | و |
| `au`, `ou` | او | و | و |
| `ua` | او ا | وا | وا |
| `ui` | اوئی | وئی | وئی |
**Consonants.**
| | | | | |
|---|---|---|---|---|
| `b` ب | `bh` بھ | `p` پ | `ph` پھ | `f` ف |
| `t` ت | `th` تھ | `T` ٹ | `Th` ٹھ | |
| `j` ج | `jh` جھ | `ch` چ | `chh`, `cch` چھ | |
| `d` د | `dh` دھ | `D` ڈ | `Dh` ڈھ | |
| `r` ر | `R` ڑ | `Rh` ڑھ | `l` ل | |
| `s` س | `S` ص | `sh` ش | `z` ز | `Z` ض |
| `k` ک | `kh` کھ / خ | `q` ق | `g` گ | `gh` گھ |
| `Kh` خ | `Gh` غ | `h` ہ | `H` ح | `zh` ژ |
| `m` م | `n` ن | `N` ں | `v`, `w` و | `y` ی |
| `'` ع | `x` کس | `c` ک | | |
`N` is the nasalisation ں, which the rules never guess at (جان and ہیں are both "n" in roman), so type it when you want it, or let the word list settle it. A capital that isn't in these tables reads as its lowercase letter.
### Why Urdu is harder than Avro or Hindi phonetic
Urdu's script does not write its short vowels. کتاب has no letter for the "i" of `kitab` and دن none for the "i" of `din`, so this transliterator writes *fewer* letters than it is given, the opposite of its Indic cousins, and the letters it does write are only the long vowels. Which leaves a pile of things roman spelling does not carry:
- whether a lone `a` is a long ا or nothing at all (`kitab` is کتاب, `karna` کرنا);
- whether a `t` is ت or ٹ, a `d` د or ڈ, an `r` ر or ڑ;
- which of the Arabic letters a loanword uses (`khas` is خاص, `zaroori` ضروری, `galat` غلط, `subah` صبح);
- whether a final `a` is ا or the ہ of a Perso-Arabic noun (`bacha` is بچہ, `karna` کرنا);
- whether a final `n` is ن or the nasalisation ں (`jan` is جان, `hain` ہیں).
The layout handles that in three layers, in this order.
1. **A list of known spellings**, about 580 of them. The commonest words outright (`hai` is ہے, `hain` ہیں, `mein` میں, `kuch` کچھ, `bahut` بہت, `acha` اچھا, `pani` پانی, `theek` ٹھیک), chat shorthand (`k` is کہ, `h` ہے, `nhi` نہیں, `kr` کر, `bht` بہت), and English loanwords written in the Urdu script (`school` is اسکول, `doctor` ڈاکٹر, `mobile` موبائل). A spelling on the list is settled there, and it shows in the composing text the moment you finish typing it.
2. **The Urdu word list, if you've downloaded it.** What you typed and every word on the list are both reduced to the word's consonant skeleton. That is how the Arabic script spells in the first place, and why it fits. The skeleton forgets every vowel, forgets aspiration (خ and کھ are one key), folds ت ٹ ط together, and د ڈ, ر ڑ, س ص ث, ز ذ ض ظ, ک ق, ہ ح. Words with the same skeleton are candidates, and what you *did* type ranks them: a vowel at the start or end of a word must find one written, a vowel inside it need not, and a capital that asked for ٹ or ق and did not get it counts heavily against a candidate. Habits are forgiven (a closing ہ read as "a", a ں left off, a long vowel typed with one letter), so `hai` finds ہیں as well as ہے, and `bacha` finds بچہ.
3. **The rules alone**, the table above. They write the literal reading: short vowels dropped, nothing retroflex, nothing Arabic.
With the word list installed, layer 3 rarely decides anything. Without it, layer 3 decides every word that isn't on the list in layer 1, so expect `kitab` to read کتب and `pani` پنی until you download the list. That is why the strip also carries the other readings of what you typed: the same spelling with its last inner "a" long, with every one long, and with the first one long. Tap the one you meant.
Every word in the downloadable Urdu list ships with a count of 1, so there is no "commoner word" for the keyboard to prefer when two of them fit what you typed equally well. It falls back on the shorter word, which is right more often than not and sometimes isn't. The spelling map in layer 1 is what covers the words you actually type most, and it is the thing to grow when something common comes out wrong.
## Glide typing
Swiping over this layout draws a romanization, not an Urdu word, so the stroke is decoded against romanized spellings and the result is turned into the Urdu script. The known-spellings list is always there for that, and the **Urdu (Romanized)** word list (about 110,000 romanized spellings, in the language catalogue) widens it a lot if you download it. See [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/).
## Editing
Backspace inside a word you're still typing removes one roman letter per press, and the Urdu redraws. Over text that's already committed it removes one code point, which on an Urdu word is one letter.
## Options
Languages & layouts / اردو · Urdu
- **Layouts**: اردو, Urdu phonetic, کلیدی تختہ (NLA), اردو (four rows) and اردو T9, one card each, turned on and off independently.
- **Key hints**: Languages & layouts / Urdu / Key hints / Show the letter each key types. Off, Added, or Whole letter. It puts the Urdu letter a key is about to write in its corner. The same shared setting Avro and Hindi phonetic use, and it shows only while a transliterating layout is active.
- **Neural suggestions**: Languages & layouts / Urdu / Suggestion strip / Neural suggestions, off by default, shown while Urdu phonetic is on. Up to three readings from IndicXlit, a small transliteration model, right after the word the space bar writes, which never changes. It is the strongest help for Urdu's unwritten short vowels: on test words, the right word reaches the strip's first four places 73% of the time instead of 43%. A 13 MB download shared by every language that has it, plus ONNX Runtime. See [Neural suggestions](https://wmkeyboard.pages.dev/languages/phonetic/#neural-suggestions).
- **Spellings**: Languages & layouts / Urdu / Spellings / Use known spellings, on by default. Switch it off for the letter-for-letter reading, which on Urdu means a lot more wrong guesses than on the other two.
- **Dictionary words**: Languages & layouts / Urdu / Spellings / Correct to dictionary words, on by default. Off, space commits exactly what the rules spell instead of a word from the list that matches its skeleton.
- **The words before**: Languages & layouts / Urdu / Spellings / Use the words before, on by default. One spelling can stand for several words, and the one or two words you typed just before pick between them, on the strip and for what space writes: "kam" is کام after "mera" and کم elsewhere. The evidence is your own typing and, where the language has one, the downloaded word-pair data. A known spelling only trades places with another known spelling of the same buffer, so the composing text is still what space writes. Turn it off if a word comes out differently after certain words than you expect.
- **Also suggest from**: with English picked, the phonetic layout's strip also carries the word as you typed it in roman letters, and an **English words** group appears below it. See [English on Avro and Hindi phonetic](https://wmkeyboard.pages.dev/smart/mixed-language/#english-on-avro-and-hindi-phonetic), which works the same way here.
- **Numerals**: `۰-۹` or `0-9`.
- **Dictionary**: the downloadable Urdu list has about 155,000 words. You can import your own as well, and imported words are found through the same skeleton. The skeleton index over the downloaded list comes ready-made from the data repository (`ur_phonetic_v1r1.wmpi.gz`, about 1.5 MB, fetched right after the list) and is read in place from storage, so the keyboard no longer builds it in memory at every start. Only your imported words are still folded on the phone.
## Details & edge cases
**The اردو layout is also called "phonetic", and is a different thing.** Its keys are Urdu letters placed where the roman letter that sounds like them sits: a key *arrangement*. **Urdu phonetic** has roman letters on the keys and writes Urdu. Both are on Urdu's screen; turn on whichever you meant.
**`kh` is خ at the start of a word and کھ everywhere else.** Both readings are everywhere in Urdu (خوش, خبر, خدا against کھانا, کھیل), but the aspirate is far commoner at the end of a word, where Urdu's own verbs put it (دیکھ, لکھ, رکھ, آنکھ), and the Perso-Arabic letter is commoner at the start. `Kh` is خ wherever you type it. `gh` goes the other way and stays گھ, because گھر alone outweighs the غ words; `Gh` is غ.
**The composing text can change its mind as you type.** `k` is ک, `ka` is کا with the ا that closes a word, and the `r` of `kar` takes that ا straight back out again (کر). What you see at each point is what a space would commit at that point.
**The phonetic machinery only loads if the layout is on.** Urdu with just the اردو keyboard enabled builds none of it.
**There are no selection-bar chips for Urdu yet.** Bangla and Hindi have **To Bangla** / **To Banglish** and **To Hindi** / **To Hinglish** on the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/) for converting text you already have. Urdu has the machinery behind them (both directions are implemented) but no chip of its own; ask on the issue if you want one.
**It's new, and both tables are a draft.** The rules, the spelling map and the English loanword list were written without a native speaker on hand, and measured against the word list rather than reviewed: the rules alone get about 62% of a 176-word test set exactly right, and the skeleton index puts the right word first for 44% of them and in the first three for 73%. If a common word comes out wrong, [say so on the issue](https://github.com/wasi-master/wmkeyboard/issues/496) and name the word and the spelling you typed.
---
# Vietnamese
URL: https://wmkeyboard.pages.dev/languages/vietnamese/
Section: Languages
Summary: Type Vietnamese with Telex or VNI: plain keys in, accented letters and tones out. How the tone lands, how to take one back, and how English words survive.
Vietnamese ships four layouts. **Tiếng Việt Telex** is what you get when you add Vietnamese, and it spells every mark and tone with ordinary letters. **Tiếng Việt VNI** does the same with digits. **Tiếng Việt** is a plain QWERTY with the accented letters on long-press, and **Tiếng Việt T9** is the keypad (see [Ambiguous layouts](https://wmkeyboard.pages.dev/languages/ambiguous/#thai-lao-and-vietnamese)). This page is about Telex and VNI, because they're the two that build letters out of keys.
## Turning it on
Languages & layouts / Tiếng Việt · Vietnamese / Layouts
Add Vietnamese and Telex is on. Tap the VNI card on the same screen if that's the method you learned, and turn Telex off if you don't want both in the switch ring. Then download the Vietnamese word list from the **Dictionary** row. Telex and VNI type fine without it, but the list is what gives you suggestions, lets you go back into a word you already typed, and, if you ask for it, [puts back a mark you missed](#filling-in-what-you-left-out).
## Telex
The word you're typing shows up already composed, marks and all, and the space commits it.
| You type | You get | |
|---|---|---|
| `aa`, `ee`, `oo` | â, ê, ô | circumflex |
| `aw` | ă | breve |
| `ow`, `uw` | ơ, ư | horn |
| `w` on its own | ư | `tw` is tư |
| `uow` + a final consonant | ươ | `nuocsw` is nước, `huowng` is hương |
| `dd` | đ | |
| `s`, `f`, `r`, `x`, `j` | sắc, huyền, hỏi, ngã, nặng | `tieengs` is tiếng |
| `z` | takes the tone off | `toansz` is toan |
The tone key can go anywhere after the vowels, and it doesn't have to be last. `tieengs` and `tieesng` both give tiếng, because the keyboard puts the tone on the right vowel by the usual rule rather than on the letter you typed it after. So `hoafng` is hoàng, and `thuowr` is thuở, since a vowel with a mark of its own takes the tone.
A mark key doesn't have to come straight after its letter either. `dod` is đo, `tono` is tôn, and `nuocsw` is nước: the key looks back for the letter it names, as long as the word it makes could still be Vietnamese. `hello` has no such letter and stays as it is. `banana` does, and comes out as bânna unless [Only tone real syllables](#only-toning-real-syllables) is on.
### Taking a mark back
Press the same key again and the mark comes off and the letter goes in instead. `hass` is has, `aaa` is aa, and `oww` is ow. For a tone there's also `z`, which removes the tone and types nothing. With no tone to remove, `z` is just a z.
A `w` on its own is ư, and a second `w` turns it back into w. Every `w` after that is a w as well, so holding the key down types a run of them instead of flipping between ư and w.
### `uo` and `ươ`
`huơ`, `quơ` and `thuở` are written with a plain `u`, while `hương`, `người` and `đường` horn both letters. They're the same keys until the end of the word, so the keyboard decides once it can see what follows:
- `huow` is huơ, and `huowng` is hương, because a final consonant makes it ươ.
- `nguowif` is người, because ng can't come before an open uơ.
- After `qu` the u belongs to the qu, so `quow` is quơ and `quowng` is quơng.
- `ww` after `uo` horns the u too, if you want ươ and the keyboard picked uơ.
### English on Telex
Telex reads letters as marks, which gets in the way of English typed on the same keyboard. Two things keep the common cases readable:
- A word that starts with `w` keeps its w when what follows can't come after ư. `why`, `when`, `we`, `with` and `web` stay English.
- A doubled key is always an escape hatch: `hass` is has, `oww` is ow.
For anything more, turn on the setting below, or switch to the plain **Tiếng Việt** layout for a stretch of English.
### Only toning real syllables
Languages & layouts / Tiếng Việt · Vietnamese / Tone marks / Only tone real syllables
Off by default. Telex normally puts a tone on anything with a vowel in it, so `fas` becomes fá even though no Vietnamese word starts with f. Turn this on and a tone key only marks a word that Vietnamese could actually spell. Anything else gets the letter instead: `fas` stays fas and `fix` stays fix. A word that picked up a mark but turned out not to be Vietnamese goes back to the keys you typed, so `banana` stays banana. It goes by Vietnamese spelling rather than by guessing at English, so `mix` is still mĩ, because that's a word. Abbreviations with no vowel keep their đ, so `ddc`, `ddt` and `ddh` still give đc, đt and đh. The setting covers VNI too.
## VNI
VNI is the same engine with digits for the marks.
| You type | You get |
|---|---|
| `a6`, `e6`, `o6` | â, ê, ô |
| `a8` | ă |
| `o7`, `u7` | ơ, ư |
| `d9` | đ |
| `1`, `2`, `3`, `4`, `5` | sắc, huyền, hỏi, ngã, nặng |
| `0` | takes the tone off |
A digit with nothing to mark is typed as the digit, so `banana1`, a street number or a model name keeps its numbers. `0` only does something when the word has a tone. Otherwise it's a 0.
## Backspace
Backspace takes off the last letter you can see, not the last key you pressed. `huowngs` is hướng, and one press leaves hướn with the tone still on the ơ. `hif` is hì, and one press takes the whole ì, tone included.
## Going back into a word
Put the cursor at the end of a Vietnamese word you already typed and you can carry on typing it. A tone key typed there marks that word rather than being added as a letter, so coming back to the end of `chao` and typing `f` gives chào. This needs a word list on the device (the same condition as for any other language), and it only kicks in for a word the keys could have typed. A word with marks Telex or VNI can't produce is left alone.
## Filling in what you left out
Languages & layouts / Tiếng Việt · Vietnamese / Tone marks / Fill in left-out marks
Off by default, because lots of people type Vietnamese without marks on purpose. Turn it on, with autocorrect on and the Vietnamese word list downloaded, and a word typed without its marks gets them back when the list has only one way to read it. `tieng` becomes tiếng, `khong` becomes không, `duong` becomes đường, and `ngieeng` becomes nghiêng. One backspace takes the correction back and leaves the word as you typed it.
Two things it never does. A word the list already knows is never changed, so a real word with the wrong tone stays as typed. And a spelling with several readings is left alone: `toan` could be toàn, toán or toản, so the keyboard doesn't pick one.
---
# Autocorrect
URL: https://wmkeyboard.pages.dev/smart/autocorrect/
Section: Suggestions & correction
Summary: Confidence-gated corrections that know when not to fire.
Autocorrect fixes typos as you finish typing a word, but only when one candidate is clearly the right fix. A fat-fingered slip that's obviously "world" gets corrected. A word that sits between two real words is left alone for the suggestion strip to offer instead. This page is about that confidence gate, and about everything built around it: reverting a correction, the words autocorrect never touches, and the auto-space rules that ride along with it.
smart/autocorrect-confidence-fix — A mistyped word corrected inline as the next word begins.
## How a correction is chosen
Space and Enter trigger autocorrect. A full stop or other punctuation does not by default: ending a word with one commits it exactly as typed. Turn on **Correct the word before punctuation** and the marks that end a word (`.` `,` `!` `?` `;` `:` and the Bengali `।`) correct it the way space does, so `teh!` lands as `the!`. A slash, a bracket or any other symbol still leaves the word alone, and one backspace straight after the mark (and the space the auto-space rule put behind it) puts the word back as typed. Enter often sends the text as well, in a search box or a chat, so a correction it makes goes out before you can read it. Turn off **Correct the word before Enter** (see [Options](#options)) and Enter commits the last word as typed, while space keeps correcting. When you press space or Enter, WM Keyboard checks the word against every word one edit away: one letter dropped, added, doubled, swapped with its neighbour, or substituted. A word that is one letter short of a longer word ("hell" for "hello") counts as that same single edit. Two-edit candidates reach the suggestion strip but are never applied silently. Two independent sources score the candidates: the word lists (bundled, downloaded, imported, and any secondary language's) and your own personal dictionary of learned words.
- **Two sources agreeing is enough on its own.** If the dictionary's best guess and your personal dictionary's best guess land on the exact same word, it's applied. The confidence factor below isn't even consulted.
- **Otherwise, the best candidate has to clearly win.** It's applied only if it outscores the runner-up by the confidence factor (4× by default, see [Options](#options)). A close call is left alone rather than guessed at. With no runner-up at all, the candidate still has to be a reasonably common word: an unknown word one slip away from a rare one is left as typed. Every word list is measured on the same scale for this, so a downloaded list, whose word counts run into the millions, is no quicker to correct than the built-in English one.
- **Two letters the wrong way round get the benefit of the doubt.** When no word clearly wins, a word that is what you typed with one neighbouring pair of letters swapped back is applied anyway. It must not be more than twenty times rarer than the best candidate, and no other swap may read about as well. So `wnat` becomes *want* even though *what*, a slipped `h` away, is the commoner word, `adn` becomes *and*, and `soemthing` becomes *something*, while `hwo`, which could be *who* or *how*, stays as typed. A swap keeps every letter you typed, which no other kind of slip can claim. This holds with the learned gate on or off, at every confidence setting but the most careful, 10×.
- **The edit itself is weighted by how it happens.** Cheapest, and so likeliest, are three: a swapped pair of letters, a doubled letter (the hold-repeat slip in "helllo"), and a substitution toward a key right next to the one you meant. A dropped letter, or a stray one typed next to where your finger landed, costs noticeably more. A substitution or an insertion nowhere near the key you hit is the most expensive of all and rarely survives. The neighbour test uses the adjacency map for your active layout, so the same typo resolves differently on QWERTY than on AZERTY or Dvorak, and where the keyboard has your real tap coordinates it uses those instead of the map.
- **A missing accent is not a typo.** Typing `z` where the word has `ż`, `e` for `é`, or `l` for `ł` costs almost nothing and does not count as an edit, because the accented letter is on the same key. So "juz" becomes "już", and a word with every accent left off ("zolw" for "żółw") is found as easily as a word with one. The strip agrees: when the letters you typed spell a word once its accents are back, that word comes first, ahead of commoner words that would need a letter changed or dropped, the way a word you typed in full leads its own slips. Portuguese "chale" offers *chalé* before *chave*, and "caminhao" offers *caminhão* before *caminho*. Turkish's dotless `ı` counts as an accent on `i`, along with `ş`, `ğ`, `ç`, `ö` and `ü`, so "haklisin" becomes "haklısın" and "guzel" becomes "güzel". Since 0.6.4 the same goes for any other letter on the key you pressed, the one its Shift types, the one in its corner or one behind its hold, in any script. On the Arabic layout `أ` and `إ` sit behind `ا`'s hold and `ؤ` behind `و`'s, so "اخبار" finds "أخبار" and "مسوول" finds "مسؤول" as surely as "juz" finds "już", and the same rule decides whether space puts them in. It runs one way only: a letter you reached with Shift or a hold is taken as meant. Marks such as the harakat on Arabic's Shift layer don't count, since a mark never stands in for a letter you tapped.
- **The fix keeps your capitalization.** Typing "hte" mid-sentence corrects to "the". Typing "Hte" at the start of one corrects to "The."
- **A word the list spells with a capital gets its capital.** Some downloaded word lists write words with a capital letter: every German noun ("Haus", "Zeit"), and the names of places and people. Type or swipe one in small letters and it is written with its capital, in the strip and when space commits it. A word the list has both ways ("essen" and "Essen") stays as you typed it. Backspace right after puts back what you typed, and the undo is remembered like any other. This is **Capitals from the word list** (see [Options](#options)). The built-in English list is all small letters, so the keyboard carries the capitals for it separately: days and months ("Monday", "January"), countries, cities and languages ("London", "English"), common first names, brands such as "iPhone", and acronyms such as "DNA" and "USA", about 2,400 in all. The word "I" is among them, so a lone "i" is written "I" even with **Fix missing apostrophes** off. A word English also uses in small letters, such as "may", "will" or "mark", is not among them. A downloaded English list adds its own on top. A capital you keep on a word in your personal dictionary, or wrote into Android's, is applied the same way (see [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#capitals)). A word your personal dictionary learned in small letters, the way you happened to type it, doesn't count as keeping it small, so a learned "i" never outvotes "I". A list you downloaded before this existed is converted on the device the next time the keyboard starts.
The two candidates have to be far apart in score, which keeps autocorrect conservative by design. "helo" is evenly split between "hello" and "help", and "tesr" between "test" and "tear", so both are left for you to resolve yourself. An unambiguous slip like "wprld" resolves immediately.
## The learned gate
The rules above are how the decision used to be made, and still are with **Learned correction gate** (see [Options](#options)) turned off. With it on, which is the default, the same candidates reach a single judgement instead of a chain of rules: a small model fitted offline that weighs everything at once and answers one probability, that the best candidate is the word you meant.
What it weighs is what the rules weighed one at a time, and a few things they could not. The lead over the runner-up. What kind of slip explains the typo: a letter swapped with its neighbour, a tap a little wide of its key, a dropped letter, a letter no finger slip explains. How many of them, and how long the word is. Whether the word list and your own dictionary agree. What the [context model](https://wmkeyboard.pages.dev/smart/suggestions/#the-context-model) thinks, when one is downloaded: of the candidate, of what you typed as it stands, how many words it would put ahead of your spelling here, how sure it is of the next word at all, and how far its favourite among the candidates leads the next. How fast the word was typed, when **Typing rhythm** is on. Whether what you typed is a listed spelling the keyboard reads as a stand-in for another word.
It was fitted on about nineteen thousand words from real sentences, typed by a finger that scatters around each key at three widths from careful to sloppy, with and without a context model in reach, and on words that had been taken out of the dictionary and typed right, which is what a name, a new word or a bit of slang looks like to the keyboard. Those last rows are the point of the exercise. Measured on sentences the fit never saw, the old rule rewrote 22 to 23% of such correctly typed new words; the gate rewrites under 1% of them on careful or fast typing and 2% on the sloppiest. It also fixes more of the real typos: on careful typing it applies 81% of them where the rule applied 60%, and the fixes it applies are right 99.4% of the time, against 99.7%. On fast typing recall goes from 50% to 67% and precision from 95.3% to 98.0%; on sloppy typing from 40% to 51% and 90.3% to 96.2%. With the context model downloaded the gate reads its view too, and that is where it earns its keep: on the same sentences it then applies 84% of the real typos on careful typing at 99.7% precision, 72% at 99.4% on fast typing and 58% at 97.5% on sloppy typing, and it rewrote none of the correctly typed new words at any width (0 of 300, 300 and 94).
The **Autocorrect confidence** slider still rules it. The probability a fix has to reach is the slider's ratio against one: at the default ×4 a fix needs four chances in five, at ×10 ten in eleven, at ×1.5 three in five. A fix that falls short of that bar but clears about six tenths of it is offered as a chip rather than applied, as before. Everything else on this page holds under the gate: a known word is never corrected, a rejected pair stays rejected, a blacklisted word is never a target, and backspace undoes a fix the same way. The one rule that changes is two-source agreement: under the gate it is one of the things weighed rather than a bypass, since the fit found it a strong sign but not an infallible one.
## Words it never touches
A word is never a correction target, whatever the confidence factor is set to, if any of these apply:
smart/autocorrect-skip-allcaps — ALL-CAPS words pass through untouched next to a corrected lowercase one.
- **It's shorter than three letters.** Too little signal to guess at safely.
- **It's already a known word.** Anything in the active dictionary, a downloaded or custom wordlist, a secondary-language dictionary, or your own personal dictionary is left exactly as typed. A known word is never "corrected." There is one exception: a spelling that the word list holds only because people type another word without its accents. The Polish list contains "juz", but "już" is more than fifty times as common, so "juz" is corrected. When both spellings are common words, such as Polish "ze" and "że", the one you typed stays. The French list holds "cest", "jai" and "quil" as words for the same reason (real text is full of elisions typed without their apostrophe), and those are corrected the same way, to "c'est", "j'ai" and "qu'il"; Italian "cera" against "era" goes to "c'era" on the same terms, and so do the other eliding languages. The larger downloaded word lists also hold common typos, because the text they are counted from has typos in it: the Medium English list holds "wheee", "thw" and "teh". A spelling is treated as one of those typos when two things are true. First, it is further down its list than the 50,000 words the Small download keeps. Second, a word one slip away is at least a thousand times as common. So "wheee" is corrected to "where" and "thw" to "the", on any download size. A real word that is only rare, such as "cress" next to "dress", is left as typed. A word you added to Android's personal dictionary is never treated this way. A word nothing recognises doesn't become known just by being typed once: it has to be typed and left alone three times before it joins your dictionary, which is what stops a typo shielding itself from the fix it needed. See [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn) for that, and **Learn a word after** under Typing / Suggestions for a further threshold on top of it.
- **It's an "-s", "-ed" or "-ing" form of an English verb the word list knows.** The bundled English list leaves out some verb forms. It has "corrected" but not "corrects" or "correcting", and "fixed" but not "fixes". So a form like "corrects", "correcting", "fixes", "carried" or "hoping" counts as a word whenever the list holds the verb along with another of its forms. That other form is the proof it is a verb: "thes" ends in "s" too, but no list holds "theed" or "theing", so "thes" is still corrected to "the". For "-ed" and "-ing" the proof has to be the other one of the two, because nouns take "-s" as well. Misspellings of a form the list does have are still corrected: "admited" next to "admitted", "fixs" next to "fixes", "stoped" for "stopped", and "begined", because "begin" has no "-ed". This only applies while English is part of your language mix.
- **It's a contact's name or an installed app's label**, if [contact and app-name suggestions](https://wmkeyboard.pages.dev/smart/contacts/) have indexed it.
- **It's in your [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), or offensive-word filtering catches it.** A blacklisted or filtered word is dropped from the candidate pool before scoring, so it can never be silently forced in as a fix.
- **It's typed in ALL CAPS.** Acronyms and shouted words (ASAP, NOOO) are usually capitals on purpose. This is its own toggle, on by default (see [Options](#options)). A leading capital on an otherwise-lowercase word ("Wprld") is still corrected normally; only a fully-capitalized word is spared.
- **The active language isn't English and has no bundled dictionary of its own.** The bundled English dictionary feeds autocorrect only while your active language is English, or has English configured as a secondary language. Type in French, German, or another language and corrections come from that language's own dictionary and whatever you've personally taught the keyboard.
- **The field is a password field, or any structured field**: email, web address, number pad, phone pad. Autocorrect, like the rest of typing intelligence, is off in all of them.
Missing apostrophes ("thats" → "that's") are fixed by a separate lookup that runs before autocorrect even looks at the word, so it's a known-word fix rather than a confidence-scored guess, and it wins whenever both could apply. The word with its apostrophe also leads the suggestion strip while you type, so what the strip shows is what the space bar is about to commit. Anything that ends a word applies it: the space bar, enter, and the full stop, comma, question mark and their family. English reads a fixed table of contractions. The spellings that are also words of their own, such as "id", "its", "were" and "well", are never rewritten, but the contraction ("I'd", "it's", "we're", "we'll") is offered on the strip right behind the word you typed, one tap away. Where the contraction is the commoner of the two, as "I'd" is next to "id", the word you typed is held up to its standing, so the pair stays on a three-slot strip ahead of completions such as "idea" and "identify". A glided or two-thumb word gets the same offer: swipe "id" and it is typed as "id", with "I'd" right behind it. "ill" is the exception: it is typed for "I'll" far more often than for the word, so it becomes "I'll" like the rest of the table. If you did mean "ill", one backspace right after puts it back, and from then on "I'll" is only offered behind it. German reads a table of its own for the *es* worn down to *'s* on a verb: "Wie gehts" becomes "Wie geht's", and "gibts", "habs" and "wars" take their apostrophe the same way. German words that only look like one, such as "wies" and "nichts", are left alone, and so are "aufs", "ins" and "fürs", which German writes without it. Seven languages that elide get the same fix from their word list instead of a table: French, Italian, Catalan, Occitan, Romansh, Corsican, Sardinian and Piedmontese: "cest" becomes "c'est", "lalbero" becomes "l'albero", "unamica" becomes "un'amica", "lhome" becomes "l'home", "lostal" becomes "l'ostal" and "sistadu" becomes "s'istadu". The keyboard knows which prefixes elide and which short words each one takes, so French "jet" is never split into "j'et" and Italian "sono" never into "s'ono". That table is deliberately thinner than a grammar book would write it, because a one-letter prefix in front of a short word usually spells a word: Occitan "ma" and "ta" are "my" and "your", Corsican "se" and "sa" are words in their own right, and each table was swept against its language's full word list to find them. A real word that happens to split, such as French "lune" or Italian "allora", stays as typed; "l'une" and "all'ora" sit behind it on the strip instead. It has its own toggle, "Fix missing apostrophes," in the same settings group, and no other language is read this way. Every language in the current mix is asked, not only the one in front: with Italian and English set as each other's secondary, "thats" becomes "that's" and "lalbero" becomes "l'albero" whichever of the two you are typing in. The word after an elided prefix still has to be a word of the language whose grammar allowed the split, so an Italian "d'" never reaches across the mix to make "dart" into "d'art". The same goes the other way for the English table: it only knows which spellings are English words, so when the field is being written in another language of your mix and that language has the word you typed, the contraction is offered on the strip instead of committed. With German and English set up together, "im" stays "im" in a German sentence, with "I'm" one tap away behind it, and an English sentence on the same keyboard still gets "I'm". Which language the field is in comes from the language you have in front, or from [language detection](https://wmkeyboard.pages.dev/smart/mixed-language/) when it is on. Offensive-word filtering and the suggestion blacklist also keep those words out of the plain suggestion strip, not only out of autocorrect. See [Suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) for the full mechanism.
Hyphenated words are one word while you type them. A hyphen straight after a letter stays in the word being composed, so "well-pai" completes to "well-paid" and "что-" goes on to "что-то", instead of the strip starting over on the part after the hyphen. A hyphen the word ends on is only punctuation: "well-" then space leaves "well- " as typed, and a second hyphen in a row is a dash that ends the word. Putting the caret back after a compound picks up all of it, not only the last part. A compound whose parts are all known words, such as "hello-world", is never corrected, even when no word list holds it whole. The other way round, a compound typed without its hyphen is offered with it on the strip: "wellpaid" shows "well-paid". That holds even when the word list counts the fused spelling as a word, as the big Russian list does with "чтото", as long as the hyphenated form is at least fifty times as common; "online" is never offered as "on-line". Being on the strip does not mean the space bar will write it: that is still autocorrect's call, made the same way as for any other word.
## Undoing a correction
smart/autocorrect-backspace-undo — Backspace right after a correction restores what you originally typed.
⌫ right after an autocorrected word undoes it, as long as nothing else has touched the text since. The cursor has to be sitting exactly where the correction left it, and the text right before it has to still read as the corrected word plus the space or line break that ended it. Backspace then deletes both and retypes what you originally typed instead. Move the cursor, tap elsewhere, or keep typing first and the undo is gone. Only the immediate next backspace can trigger it.
A missing apostrophe the keyboard filled in ("dont" → "don't") and a lone "i" it made into "I" are undone the same way, and remembered the same way below: after the undo, the next "i" stays lower case instead of being capitalised straight back, and the repair waits on the suggestion strip behind what you typed instead. One undo is enough for an apostrophe, for as long as that undo is remembered: a repair has no confidence to handicap the way a guessed correction does, so where a correction undone once only fights uphill, a repair undone once stops being made by itself at all. A capital from the word list, the lone "I" among them, is the exception: one undo holds it back for the rest of that run of typing, and only a second undo retires it. That way a single stray undo can't leave every "i" in lower case for months, and a capital you let stand afterwards buys the undo back the way a kept correction does. A capital retired by two undos can't fire to earn that back, so if "i" has stopped becoming "I", open [Learned corrections](#learning-from-your-own-fixes) and delete `i → I` under **Corrections you undid**. Set it to **Never** there if you want "i" left alone for good.
The same word won't be "corrected" again next time either way: undoing a correction retires that exact pair as soon as you keep the word you typed, and a second undo of it blocks the pair for good. That part is unconditional, because a rejection has to hold even with learning switched off.
The undo only counts once you keep your word, by typing on after it or leaving the field. Holding backspace to clear the text also starts with that undo, but the next presses delete the restored word too, so nothing is remembered and "i" still becomes "I" next time.
Reverting also counts double towards teaching the original word to your personal dictionary, which is the strongest of the signals that feed [that count](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn). It doesn't save the word outright any more. Undoing a correction is also what you do when the fix simply wasn't the word you wanted, and saving on the first undo turned out to be the shortest path from one stray swipe to a misspelling stuck in your dictionary. This follow-on teaching only happens if [learning from typing](https://wmkeyboard.pages.dev/smart/personal-dictionary/) is on, which it is by default. It's skipped in [incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) (when incognito is set to pause learning) and in a secure field.
One related behavior is easy to mistake for this toggle. Arming Shift live re-cases every chip in the suggestion strip, not only an autocorrect candidate, to match what would actually be committed if you tapped it. Shift is a latch rather than a hold here, so the chips stay re-cased until the shift state clears. That's a strip-wide display rule rather than a way to reject a correction. See [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/) for how it works.
## What counts as rejecting a correction
Backspace is the fastest way to reject a correction, but it is not the only one, and for a long time it was the only one the keyboard could see. Fixing a word three words later, selecting it and retyping, or tidying the sentence before sending all rejected the correction just as clearly and taught the keyboard nothing. Worse, they counted as successes, so the adaptive gate read a better record than autocorrect was earning and loosened where it should have tightened.
A correction is now judged once the text around it has settled, on the same signals the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn) uses: you close the keyboard, send or clear the field, move to another field, or simply type far enough past it.
- **You never went back into that text.** The correction stands, and counts as accepted.
- **You did go back into it.** The keyboard reads the field once, at that point only, and looks for which spelling is there now. Your original word standing where the fix was is a rejection. The corrected word still there is an accept. This reading counts for less than a backspace, and it is thrown away entirely when your word is not a word the keyboard knows. That case is the trap it exists to avoid: you glance back, decide you mistyped, fix it by hand without noticing the keyboard already fixed it, and make the same slip again. The field then looks exactly like a rejection while nothing was rejected at all. A word that really is yours, a name or a nickname, joins your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) after a few sightings, and from then on its undos count in full.
- **Neither is there, or the text is gone.** No verdict at all. Deleting the sentence, or sending a message before anything could be checked, teaches nothing rather than something invented.
The read only ever happens at one of those settling points, never while you are typing.
## Taking a correction back later
Backspace only reaches a correction in the moment it happens. One more letter and it is out of reach, which is awkward, because that is usually when you notice it. The word looks right at a glance, you read the sentence back a beat later, and by then the only way out is to select the word and retype it.
So the keyboard puts the word you typed on a chip at the start of the suggestion strip. Press it and your word goes back, and the keyboard reads that exactly as it reads a backspace: the pair is retired, and your word counts towards your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/). It shows between words rather than in the middle of one, because putting the word back means editing text behind where you are typing.
The chip remembers the last five corrections that earned one, not just the latest. Slips tend to come in runs, so when autocorrect has changed three words in a sentence, three presses put all three back, newest first: after each press the chip shows the next word it can restore. There is no counter on it; the word it names is the next one it will put back.
Before each press rewrites anything, the keyboard finds the corrected word again and checks it is still the one it changed: still spelled the same, still sitting after the same few words it followed when it was corrected. Words you typed after it don't matter, and neither do the ones the chip already put back. If you edited the word yourself, or changed the text just in front of it, that correction is quietly skipped and the press goes to the next one down, so the chip never rewrites a word it can't be sure of. The same word appearing twice is fine as long as the two stand after different words. Each correction also has to stay within about a hundred characters of where you are typing, and moving the cursor back past one drops it along with any newer ones.
The chip and everything it remembers are cleared when you end the sentence, select text, or move to another field. A correction that doesn't earn a chip of its own (see below), or a fix that only adds a capital such as "i" becoming "I", leaves the remembered ones in place. Backspacing a correction away the moment it lands drops it from the chip too, and leaves the older ones alone.
There's a second way back, one that outlasts the sentence. Every autocorrected word also carries a mark the app itself can read, naming the word you typed. In an app that supports it, this draws the same brief underline other keyboards use. Tap the word anywhere in the document, however long after, and it offers your original spelling back. It has no expiry, because it lives in the text rather than on the strip. Not every app reads the mark, so where it doesn't, the chip above is the only way back.
Not every correction gets a chip, and that is the point. A chip after every correction is one more thing to look past, and by the time the one that matters arrives you have stopped reading the strip. So the keyboard scores each correction it applies on two things:
- **How sure it was.** A correction that only just cleared the confidence gate scores low. One that both the dictionary and your own lexicon named scores high.
- **How far it moved the word.** A short word with one neighbouring letter changed scores high, because you can see what happened at a glance. A letter no finger slip explains, or a word split into two, scores low.
A correction that scores under the bar gets a chip. **How often to offer the undo** under Typing / Automatic corrections is that bar: at the lowest setting almost nothing gets a chip, at the highest every correction does. **Undo a correction from the strip**, directly above it, turns the whole thing off.
A listed spelling far rarer than a word one letter away, like `verg` next to `very`, is never rewritten, but it gets a chip as soon as you press space. A real word that only the next word proves wrong, like `Thus` in "Thus bug", can't be caught before that word lands, so **Re-check the previous word** asks then. Each fix is offered once per field: pass over the chip and it doesn't come back.
The chip shares its slot with the join, revision and near-miss chips, and comes last of the four. All four wear the theme's selected-chip colour, the same fill a highlighted suggestion gets. Those three are about the word you are typing now. This one is about a word you have already left behind.
## How long an undo lasts
Undoing a correction does two things. It puts your word back, which is immediate and never in doubt. It also tells the keyboard something about that exact pair of words, and **Remember my undos** under Typing / Automatic corrections sets how long that second part lasts. Only the pair is affected. Other fixes for the same typo carry on as before.
| Level | What an undo costs the correction |
| --- | --- |
| Off | Nothing. The keyboard corrects the word again next time. |
| Light | The pair needs far more confidence from then on. It is never retired. |
| Normal | One undo stops the pair for the rest of that run of typing. Two retire it, and a retired pair is offered as a chip once more after a long quiet spell. |
| Strict | The first undo retires the pair for good, however the keyboard read it, and it is never offered again. |
Normal is the default and the level the rest of this page describes. Under it, a backspace on the correction stops that pair for the next twenty corrections and until you move to another field, whichever comes first. After that the counts on disk take over: a pair undone once fights uphill, a pair undone twice does not fire at all. Corrections you let stand count too, and three of them buy back one undo, so a pair you undid in a hurry is not sentenced for the six months the expiry clock takes.
A retired pair is not gone forever. Once it has sat quiet for about forty saves of the learning file, it is let out on probation: it can reach the suggestion chip again, though it still never rewrites your word on its own. Accept it three times and it is an ordinary correction once more. This is what stops a single bad undo from silently retiring a fix you wanted, with nothing on the screen ever saying why.
Pick **Light** if you find the keyboard gives up on corrections too easily. Pick **Strict** if you want an undo to be final. **Off** keeps the undo itself and forgets everything else.
### When you keep deleting the same fix
Backspace right after a correction is not the only way to say no to it. Often the correction goes unnoticed for a moment, and you delete it letter by letter or with a word delete, type your word again, and get the same correction again. **Stop after I refuse a fix** under Typing / Automatic corrections ends that loop.
Every undo or deletion of a correction counts as one refusal for that word at that place. Once a place has collected the set number of refusals (2 by default, up to 5), the word you type there goes in as you typed it, and the fix is not offered on the strip either. It is a local truce, not a verdict on the word: the same typo anywhere else in the text is corrected as usual, nothing is saved, and the count is gone when you move to another field. Set it to **Never** to turn it off; **Remember my undos** above still applies either way.
## Learning from your own fixes
Most typos autocorrect misses end the same way: you notice the word, go back, and fix it by hand. That fix used to teach the keyboard nothing. Now it teaches two things.
**The pair.** The keyboard remembers what you typed and what you changed it to, once the text around the fix has settled the way learned words do (see [Words wait their turn](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn)). Fix the same typo a second time and autocorrect makes that fix for you from then on, with the undo chip showing the first few times so you can see it happen. After one fix it offers the fix on the strip instead of applying it, and it will not apply a different guess of its own over a word you have already shown it the answer to. If autocorrect had changed the word to the wrong thing and you fixed that by hand, the pair it learns starts from your original typo, and the wrong correction is marked as rejected.
A typo that is itself a real word ("form" for "from") is held to a higher bar: three fixes, and the words before it have to agree that the other word was meant. Otherwise every "form" would turn into "from". A word you have put on **Never suggest** doesn't count as real here: you have already said you never mean it, so two fixes are enough, as for any typo.
**The slip.** Each pair is also read letter by letter, and the difference is counted as a habit of your hands on this layout: a `3` typed for an `e`, a `b` where the space bar was, a doubled letter missed. Once a layout has taught eight of those, autocorrect treats the slips you make as cheaper, so the same slip in a word you never fixed is understood too. A habit never makes a correction fire on its own; it only changes which words are in the running, and the confidence gate decides as before. Habits are learned only from typos you typed yourself, never from a real word swapped for another, from a swiped word, or from a word picked off the strip.
**Where your taps land.** Separately, every word you type and leave standing records where your finger landed on each of its letters, and later taps are read against keys moved to match. This is the tapping twin of the glide typing hand model, and it learns from kept autocorrects as well: a correction you let stand is the plainest statement of which letter a tap was meant for.
The keyboard learns a fix in three situations: you backspace into a word you just committed and retype it, you tap into a word and edit it in place, or you select a word and type over it. It follows only the edits it makes itself, and checks the word against the field once, when you leave it. A learned fix you then undo, by backspace or the undo chip, counts against the pair, and a pair undone twice is retired exactly like any other correction. Everything learned here ages out on the same clock as the rest of the personal dictionary.
**Learned corrections** under Typing / Automatic corrections lists everything above one entry at a time, and each entry can be changed on its own:
- **Fixes you taught** shows every pair with how often you made it. Tap a pair to change either spelling or the count, or use **Add a fix** to write one the keyboard never saw. The count decides what happens, the same as if you had typed it: at 1 the fix is offered on the strip, at 2 it is made for you, and a typed word that is itself a real word needs 3 and the words before it have to agree. A fix you set here doesn't age out, and saving it forgives any undo of it. If you have taught two fixes of one spelling, neither is made until one has at least twice the count of the other, and the row says so.
- **Corrections you undid** shows each correction you backed out of and what the keyboard does about it now. Tap one to choose **Only when very sure**, **Stop for now** (it comes back on the strip after a long quiet spell, never applied) or **Never**, or delete it to forgive it. **Stop a correction** adds one you never had to undo, for a word the keyboard keeps changing into another. Editing this list doesn't change the revert rate behind the confidence gate, and [Remember my undos](#how-long-an-undo-lasts) set to Off ignores the list.
- **Slips the keyboard has noticed** shows each slip with its layout and count, since a slip belongs to where the keys sit under your hand. Tap one to change its count, or delete it. A higher count makes that slip cheaper, and a layout still has to reach eight slips in total before any of them count.
**Forget all learned corrections** clears the fixes and slips at once. **Learn from my fixes** turns the whole thing off. **Learn where my taps land** turns the tap model off on its own, with its own Forget button underneath. Both stores travel with the Dictionary section of an export, and are deleted with the rest of the learned data.
## Corrections it offers instead of applying
Applying a correction and offering one are not the same question. Applying has to be nearly certain, because being wrong rewrites what you wrote and you may not notice. Offering costs you a chip you can ignore. So everything that came close to the bar without clearing it, which used to be discarded silently, is now offered as a chip at the start of the suggestion strip instead. Tap it to apply the fix; carry on typing and it goes away.
The bar for a chip is about a third of the bar for a silent replacement, and it moves with every knob above it: raise the confidence slider and both go up together. A correction you have already rejected once is not offered, because being asked twice is worse than not being helped. The one exception is a retired pair that has sat quiet for a long time: it comes back as a chip rather than staying invisible forever. See [How long an undo lasts](#how-long-an-undo-lasts). Turn the chips off with **Offer corrections it isn't sure about** under Typing / Suggestions.
Tapping the chip is itself evidence that the gate is too tight for that pair, so it feeds back the same way an accepted correction does. Backspace undoes it like any other correction.
## Auto-space and punctuation
Two related rules ride along with autocorrect and share its "sentence in progress" bookkeeping. Each has its own toggle:
- **Double space inserts a period.** Type two spaces in a row within a fraction of a second and the first space becomes `. `. Deleting a space and retyping it "undoes" nothing here; a following [Shift](#shift-cancels-a-pending-auto-space) is what cancels it. A separate, off-by-default setting can make a double space insert a tab instead, which takes priority over the period rule when both are on.
- **Punctuation auto-inserts the space after it.** Off by default. When it's on, a sentence-ending or clause mark inserts a trailing space automatically: period, exclamation point, question mark, comma, semicolon, colon, and the Bengali `।`. You don't need a separate spacebar tap before the next word. A mark typed straight after a digit is part of the number and gets no space, so `5,000`, `3.14` and `5:00` stay whole. A full stop inside an email address gets none either, so `test@pm.me` doesn't come out as `test@pm. Me`; a comma or question mark after the address still does. A run of marks like "..." or "?!" isn't pulled apart into "... !", because the space from the previous mark is taken back before the next one lands. A closing mark does the same thing: type `"hi.` and the closing quote hugs the full stop rather than landing a space past it. This is also gated off in password and structured fields, same as autocorrect itself.
### A space that is already there
Every rule that types a space after something, the space bar after a word, the space after a mark and the space after a glided word, checks first whether the text already goes on with a space. If it does, the cursor steps over that space instead of typing a second one, so it lands at the start of the next word. Put the cursor at `auto| space`, type `matic` and a space, and you get `automatic |space`, ready for the next word. The same goes for a word tapped from the strip. Before, the cursor stayed in front of the old space, so the next word ran into the one after it and every space and mark after that landed in the same wrong place.
Web pages and app screens built on one are the exception: there the space is always typed. A page can show text after the cursor that isn't really there, such as Gmail's grey Smart Compose suggestion, and the keyboard has no way to tell the two apart. Stepping over that suggestion's first space made Gmail close the keyboard.
A word the cursor only landed on is not a word you ended, so a space pressed there with nothing typed into the word is an ordinary space.
### Shift cancels a pending auto-space
Right after either rule above inserts a space and auto-capitalization raises Shift for the next sentence, the very next Shift press does something different from lowering it. It deletes that trailing space, as long as nothing has changed the text since, and cancels that auto-capitalization too. When Shift wasn't raised for you, because the mark was a comma or auto-capitalization is off, the press arms Shift as usual and the space stays, so you can still capitalize the next word by hand. It's a one-shot. Press any other key first and the pending state clears, after which a plain space press only confirms the space already there rather than adding a second one.
### A mark takes the keyboard's space back
Whenever the keyboard types a space for you rather than you typing it, a mark typed straight after it takes that space back so it hugs the word it belongs to. That covers the space after a glided word, the space after a word tapped from the suggestion strip, and the space the punctuation rule above inserts: `word:` and not `word :`. A space you typed yourself is yours, and no mark removes it.
The marks that do this are the ones that end a word or build a longer one: the full stop and its family, the closing brackets, the percent sign, and the slash, backslash, apostrophe, dashes and underscore that spell "and/or", "hello's", "well-known" and "snake_case". `#` and `@` are not among them, because a hashtag and a mention attach to the word after them: `check this #cool` wants the space in front of the hash kept. In a web address field `#` and `@` join the list, along with `&`, `=`, `+` and `~`, since an address has no spaces anywhere in it.
The rule runs before the keyboard decides what else the mark means, which matters for two of them. A colon on a word boundary also opens an emoji search, and an apostrophe goes into the word being corrected so that "hello's" stays one word. Both still hug the word first. If the emoji search does complete, the space comes back, since an emoji stands off the word in front of it: a swiped "party" then `:tada:` gives `party 🎉`.
The straight double quote is the one mark that has to look at what came before it, because it is both ends of a quotation. The keyboard counts the quotes already on the line: an even count means this one opens, so the space in front of it stays, and an odd count means it closes, so the space goes. `he said "hello"` comes out right either way. The apostrophe is left out of this on purpose, since it is a letter inside a word and counting it would say nothing.
Getting to the mark does not spend the space. A colon and a slash live on the symbols page, so reaching either one means pressing `?123` first, and that press changes only what is drawn. The same goes for shift, the Fn and number pages, and the globe key. Any key that puts text in the field does spend it, panels included, because what comes out of a panel is text like any other.
### A mark takes your own space back
The rule above never touches a space you typed. **Remove a space before punctuation** is the setting that does, and it is on by default: with it on, `Hey .` lands as `Hey.` and `yes , no` as `yes, no`, the moment the mark is typed. It is narrow on purpose. The mark has to come after a word, a number or something that closes one (a bracket, a quote), so a mark at the start of a line or after another mark keeps its space, and a line break is never treated as a slip. A space in a password, an address or a number pad is left alone, same as every other rule here. One backspace straight after puts the space back. Since 0.6.6 so does a smiley: when the key after a `;` or `:` turns it into an emoticon, as in `;)`, `:(`, `:D`, `;P` or `:-)`, the space you typed comes back in front of it, so `nice ;)` stays `nice ;)`. If the keyboard put a space after the mark, only a closing mouth such as `)` counts, because a letter or an opening bracket there starts your next word.
Which marks do this is yours to set. The list under the switch takes one character per mark with nothing between them, and starts out as the sentence and clause marks: `.,?!;:`, the Bengali `।`, and the Arabic comma, semicolon, question mark and full stop (`،؛؟۔`), which hug their words exactly as the Latin ones do. You do not have to edit it for French: the languages that keep a space in front of a mark are handled on their own, below, and are never hugged whatever the list says.
### The space in front of a mark
French does the opposite of everything above. `!`, `?`, `;`, `:` and the guillemets are double marks there, and they take a space on both sides: `Bonjour !`, `Quoi ?`, `voici :`. **Space before French punctuation** is the switch for that, on by default, and it types the space for you and keeps every other rule from swallowing it again.
It is a no-break space, so the mark can never wrap onto the next line on its own. That is why the rule exists in print. Type the space yourself and it is replaced with the no-break one; type none and you get one anyway; let the keyboard put one there after a swipe or a strip pick and it stays. One backspace straight after takes it out again, for the times you are quoting code rather than writing a sentence.
The same guards apply as everywhere else on this page. The mark has to follow a word, a number or a closing bracket, so a mark at the start of a line, after a line break, or after another mark is left exactly as you typed it, and passwords, addresses and number pads are untouched. One knock-on: in these languages a colon after a word is punctuation rather than the start of an emoji shortcode, so `:tada:` opens the emoji search at the start of a line but not in the middle of a sentence.
French quotation marks get the same treatment from both ends. `»` takes its space in front like any other mark, and `«` takes one behind, typed with the mark, so `«` then a swiped word gives `« oui` rather than `«oui`. Quoting in French is `« oui »` with no spacebar taps of your own.
Seven languages are written this way and carry the rule: French, Breton, Occitan, Walloon, Picard, Norman and Franco-Provençal. Every other language, Haitian Creole and Catalan included, types exactly as it did before, because the switch has nothing to act on.
### A glided word joins an opening bracket
Glide typing types a space in front of each word as well as after it, so two swiped words do not run together. That space is skipped when the character in front of it is one the word belongs against: an opening bracket, an opening quote, or the start of the line. `he said "` then a glided "hello" gives `he said "hello`. The double quote is read the same way as above, by counting the quotes on the line, so a closing quote still gets its space. In a web address field there is no leading space at all, since an address has no spaces in it.
The joining marks work the same way, with one extra test. A slash, a backslash, a dash, an underscore, `&`, `=`, `+` and `~` hold two words together when they are up against the word behind them, so `The/` then a glided "And" gives `The/And`. The same marks stand between two words when they have a space in front of them, and then the glided word gets its space: `hello -` gives `hello - world`, while `well-` gives `well-known`.
`#` and `@` are openers rather than joiners, because they attach to the word after them wherever they sit. `check this #` then a glided "cool" gives `check this #cool`.
## Brackets close themselves
**Close quotes for me** is off by default and does the same for `"` and `'`, except right after a letter or number, where a quote is closing something or is an apostrophe.
**Close brackets for me** is off by default. Turn it on and an opening bracket types its closing half behind the cursor, with the cursor left between the two, so you carry straight on inside the pair: press `(` and you get `()` with the caret in the middle, ready for what goes in it.
Three presses behave differently once it's on:
- **The opening bracket** types the pair. `(` `[` `{` do it, and so do the CJK and fullwidth brackets: `(` `[` `{` `〈` `《` `「` `『` `「` `【` `〔` `〖` `〘` `〚` `⟨` `⟦`. The pair only goes in where nothing follows the cursor but a space, a line break, the end of the text, a closing bracket or a mark like `.` or `,`. In front of anything else, a word, `@name`, `#tag`, `$5`, an emoji or a quote, you're putting a bracket around that text by hand, so only the opener is typed: `he (was`, not `he ()was`.
- **The closing bracket**, pressed when that very character is already the next one in front of the cursor, steps over it instead of typing a second one. Typing both halves out of habit gives you `()`, not `())`.
- **Backspace** on an empty pair takes both halves out in one press, so undoing the bracket undoes the closer that came with it. A pair with anything inside it deletes one character at a time, as always.
Two kinds of character are deliberately left out. **Quotes** (`"`, `'`, `` ` ``, `«`), because the apostrophe in "don't" is the same key, and a keyboard that closed it would be wrong far more often than right; select some text and press the quote instead, which [wraps the selection](https://wmkeyboard.pages.dev/tools/text-editing/) in the pair. **`<`** for the same sort of reason: outside code it is a less-than sign, and `5 <` is nobody opening a bracket.
The rule also stands down when the bracket lands in front of a word. `(` typed immediately before `world` is you putting brackets around that word by hand, and a closer dropped into the middle of it would only have to be deleted again. A space, a mark, another bracket or the end of the text all leave room for the pair. Passwords, email fields and number pads are untouched. Web addresses are not: a browser's address bar is a search box as often as an address box, and a bracket typed there pairs the same as anywhere else.
The pair works the same in apps and web pages that handle typing their own way, Discord, browser-based editors and the like, where the cursor used to land outside the closing bracket.
With **Automatic space after punctuation** on, a closing `)` gets the space after it too, typed or stepped over: `(see below)` then a word gives `(see below) word`. Only when nothing but a space or the end of the line follows it, so `((a))` and `(a).` don't come apart. `]` and `}` get no space, because `[text](link)` is a Markdown link and the space would break it.
A closing quote gets the same space: `"exact phrase"` then a word gives `"exact phrase" word`. The curly `”` always closes. The straight `"` is counted the way the rule above counts it, so only the quote that closes a quotation gets the space, and the one that opens it stays up against the next word. In an app that turns your opening `"` into a curly `“` as you type, the straight quote that ends the quotation still counts as closing it. A full stop typed after the quote takes the space back, the same as after any other mark.
A web address or a file name typed into a message closes back up. The space after a full stop goes in before the keyboard can tell a sentence from `google.com`, so when the word you type next is a domain ending or a file extension, the space comes back out as the word finishes: `google. com` becomes `google.com`, `notes. txt` becomes `notes.txt`, and `bbc.co. uk` becomes `bbc.co.uk`. The capital the full stop put on the word goes too. Endings that also start sentences (`it`, `in`, `me`, `us`, `go`, `log`) are left alone, and so is a dot that doesn't sit straight on a letter or digit, like `Wait... com`. One backspace puts the space back.
In a browser's address bar the punctuation rule stays out, because a full stop or a colon there is part of an address. Once what you type there has a space in it, it is a search and not an address, and the closing `)` and closing quotes get their space as in a message.
## Lists carry on
**Continue lists** is off by default. Turn it on and Enter on a list line starts the next line with the next marker, in any app, not only the [Notes](https://wmkeyboard.pages.dev/tools/notes/) editor. Type `1. milk`, press Enter, and the new line already reads `2. `. The markers it knows:
- `-`, `*`, `+` and `•` carry on unchanged.
- `1.` and `1)` count up, keeping whichever delimiter you used.
- A task box, `- [ ]` or `- [x]`, carries on as an empty `- [ ]`; `☐` and `☑` carry on as `☐`.
Indentation in front of the marker is kept, so a nested list stays nested. Enter on an item with nothing after its marker ends the list: the marker comes off and no new line is typed, the way a word processor lets you out of one.
A number has to be three digits or fewer, so a line that opens with `2024.` is not read as a list item. The rule only runs where Enter makes a new line. In a chat box whose Enter key sends, Enter still sends; Shift+Enter there makes the line break, and that line break carries the list on. Passwords, email fields, web addresses and number pads are left alone.
Bold, italic and the other inline styles are not converted. A plain text field has nowhere to draw them, and the chat apps that read `*bold*` or `**bold**` already do that themselves; select the text and the [chat formatting](https://wmkeyboard.pages.dev/smart/selection-actions/#chat-formatting) chips wrap it in the markers that app reads.
## Reducing typos before they happen
Autopilot nudges which key a borderline tap resolves to, biasing toward whichever neighboring letter is more likely to continue the word you're typing. Fewer boundary mis-taps happen in the first place, before autocorrect would ever need to fix them. It has its own screen under the Suggestions settings group rather than sitting alongside autocorrect, since it needs suggestions on to have anything to bias toward. Its Strength slider sets how far a favoured letter reaches, and two optional views draw the touch areas it has claimed. See [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/) for the rest of how the suggestion engine ranks candidates.
**Mistype tolerance** (100% by default, 50% to 200%) sets how far into a neighbouring key a tap may land and still be read as a slip of the key you meant. Every tap is scored by where it landed, so a tap near a key's edge can already count as its neighbour; raise this if you type fast and loose and want the keyboard to look further, lower it if you type carefully and want the key you hit trusted. It moves both the strip's candidates and autocorrect's pick, so it stays on the Automatic corrections screen, along with **Learn where my taps land**, when Autocorrect is turned off.
**Fix several missed keys** (Careful by default) applies that reading to every letter of a word at once. Without it, each tap read as the key next door counts as one mistake, and autocorrect fixes one mistake per word. With it, a word typed fast with three keys a little wide, `rgw` with each finger just left of the key meant, is read as `the`. Only keys within reach of where your finger landed qualify (about one and a quarter keys at 100% tolerance, further as you raise it). **Careful** fixes those words, but a fix that also needs a letter added or removed is only offered in the strip. That mix is what most often turns a correctly typed word the keyboard hasn't learned yet, a new name or a bit of slang, into some other word; held back, such rewrites stay about as rare as they were without this setting. A word you keep typing is learned after a few uses, and from then on it's never changed. **Eager** applies it as well, for when your dictionary already holds every word you type. **Off** goes back to one fix per word. A word the keyboard knows is never changed, and typing with no tap positions, from a hardware keyboard or a paste, is corrected as before.
## Spell checking in other apps
Android has a spell checker of its own, separate from the keyboard: it draws the red underline under a misspelt word in apps that ask for one, and offers fixes when you tap it. WM Keyboard can be that spell checker. Open the phone's **Settings**, search for **Spell checker** (usually under System, Languages & input), and pick **WM Keyboard spell checker**. It then checks words against the same word lists the suggestion strip uses: a word list you downloaded for the language, the built-in list for English, and the words the keyboard has learned from you. The fixes it offers are the ones autocorrect and the strip would give. A language with no word list on the phone gets no underlines at all, rather than every word marked wrong. For English it adds Harper's grammar marks on top. Nothing runs until you choose it there, and switching back is the same setting. See [System spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/) for the details.
## Options
Typing / Automatic corrections
smart/autocorrect-options — The Automatic corrections group in Typing settings, including the confidence slider.
| Setting | Default | Shown when |
| --- | --- | --- |
| Autocorrect | On | always |
| Autocorrect this language (per language, under Languages & layouts) | On | Autocorrect on |
| Autocorrect confidence | 4.0× (range 1.0×–10×) | Autocorrect on |
| Adaptive confidence | On | Autocorrect on |
| Typing rhythm | 0% (range 0%–100%) | Autocorrect on |
| Undo autocorrect with backspace | On | Autocorrect on |
| Undo a correction from the strip | On | Autocorrect on |
| How often to offer the undo | 50% (range 0%–100%) | Autocorrect on, and Undo a correction from the strip on |
| Remember my undos | Normal (Off / Light / Normal / Strict) | Autocorrect on |
| Learn from my fixes | On | always |
| Learned corrections | (a screen) | always |
| Learn where my taps land | On | always |
| Learned correction gate | On | Autocorrect on |
| Mistype tolerance | 100% (50–200%) | |
| Fix several missed keys | Careful (Off / Careful / Eager) | |
| Skip words in capital letters | On | Autocorrect on |
| Correct the word before Enter | On | Autocorrect on |
| Correct the word before punctuation | Off | Autocorrect on |
| Capitals from the word list | On | always |
| Block offensive words | On | always |
| Context-aware suggestions | On | always |
| Context model | On | always |
| Re-check the previous word | On | always |
| Phrase suggestions | Off | always |
| Add missing spaces | On | Autocorrect on |
| Detect the language you type | On | always |
| Detection strength | Balanced (Gentle / Balanced / Aggressive) | Detect the language you type on |
| Fix number row slips | On | the number row is shown |
| Fix missing apostrophes | On | always |
| Double space | Full stop (Two spaces / Full stop / Tab) | always |
| Double space speed | 400 ms (range 200–800 ms) | Double space is Full stop or Tab |
| Automatic space after punctuation | Off | always |
| Remove a space before punctuation | On | always |
| Marks that pull the space out | `.,?!;:।،؛؟۔` | Remove a space before punctuation on |
| Space before French punctuation | On | always |
| Close brackets for me | Off | always |
| Close quotes for me | Off | always |
| Continue lists | Off | always |
The table covers the rows this page is about. The same group also carries **Adapt to app tone** and **Space after a suggestion** (both described on [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/)), **Automatic capital letters**, and the two selection rules, wrap-with-a-pair and shift-re-cases-the-selection.
The **Autocorrect** toggle is the master switch for the eighteen rows marked "Autocorrect on" above, from the confidence slider down to Fix number row slips. It is not a switch for the whole group. The apostrophe fix, both double-space rules, the auto-space after punctuation and the space-before-punctuation fix carry on working with autocorrect off, which is the point of them having their own toggles. Autocorrect is also reachable as a one-tap toolbar tool that flips the same switch without leaving the keyboard, showing a brief "Autocorrect on"/"Autocorrect off" confirmation. The tool is lit only when the language you're typing is corrected, so on a language switched off by itself (below) it shows off, and a tap turns autocorrect back on for that language rather than off for all of them.
### One language at a time
Languages & layouts / your language / Autocorrect / Autocorrect this language
Each language has its own switch, on by default. Turn it off for one language and words typed in it are never replaced, while every other language keeps being corrected. This is for typing two languages side by side where only one should be corrected, such as English next to romanized Bangla or a language whose word list doesn't know your spellings. The language follows the keyboard you're on, not the language [detected](https://wmkeyboard.pages.dev/smart/mixed-language/) in the field. Suggestions still show on the strip, and you can tap one. The switch only covers autocorrect itself: a phonetic layout's known spellings and dictionary words have their own switches in the same place (see [Phonetic typing](https://wmkeyboard.pages.dev/languages/phonetic/)). On a phonetic layout it does govern typos: a slip like `vako` for `valo` is corrected only while the switch is on. The main **Autocorrect** switch above still rules every language. While it's off, the per-language rows are greyed out and keep their own setting for when it comes back on.
**Autocorrect confidence** is the ratio the top candidate must beat the runner-up by. It's a continuous slider from 1.0× to 10×, labeled with the exact multiplier (for example "×4.0"). Below 1.5× it also accepts words two letters away from what you typed, and at 1.0× the best candidate wins whatever the runner-up scores. A word already in a dictionary is still left alone, and **Adaptive confidence**, when on, can raise the bar again after corrections you undo. Lower makes autocorrect fire more eagerly, on less-certain calls. Higher restricts it to only the most unambiguous typos. Two-source agreement (see [How a correction is chosen](#how-a-correction-is-chosen)) always bypasses this slider, whatever it's set to, unless the learned gate is on, which weighs it instead. Under the gate the slider sets the probability a fix has to reach, the ratio against one; see [The learned gate](#the-learned-gate).
**Learned correction gate** (on) is that gate. Off, the keyboard decides by the rules in [How a correction is chosen](#how-a-correction-is-chosen) alone. **Context model**, **Re-check the previous word** and **Phrase suggestions** are the neural context model's three rows; they are described on [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/#the-context-model), and the gate reads the model's scores when it is downloaded.
Three more rows change what autocorrect does without you touching the slider:
- **Adaptive confidence** (on) watches how often corrections get rejected and raises the bar when a lot of them do. It stays out of the way until twenty corrections have been judged, and it can move the gate anywhere from 0.85× the slider (a spotless record) up to 2.5× it, with the result always held inside the slider's own 1.5×–10× range. That is the whole of this row: it moves one global gate. The memory of individual corrections you undid is a separate setting, **Remember my undos**, described in [How long an undo lasts](#how-long-an-undo-lasts).
- **Remember my undos** (Normal) sets how long the keyboard keeps an undo. It is the row to reach for if autocorrect either forgets a fix you rejected or refuses to fix a word you want fixed. See [How long an undo lasts](#how-long-an-undo-lasts).
- **Stop after I refuse a fix** (2) leaves a word alone at the place where you have undone or deleted its correction that many times. See [When you keep deleting the same fix](#when-you-keep-deleting-the-same-fix).
- **Learn from my fixes** (on) and **Learn where my taps land** (on) are the two halves of [Learning from your own fixes](#learning-from-your-own-fixes): the first remembers the typos you repair by hand and the slips behind them, the second where your finger lands on each key. **Learned corrections** opens the list of what has been learned, where each entry can be added, changed or removed.
- **Typing rhythm** (0%, so off until you move it) measures how fast you typed the word. A quick sloppy burst makes autocorrect a bit braver. Slow deliberate typing, as with an unusual name or a word from another language, makes it hold back. The slider is how much the rhythm is allowed to sway things.
- **Detection strength** decides how hard the language the keyboard reads the field as leans on ranking, from a light nudge at Gentle through Balanced to a heavy tilt at Aggressive. It reweights the word lists rather than moving the confidence gate, so it changes which word autocorrect reaches for, not how sure it has to be. See [Mixed-language typing](https://wmkeyboard.pages.dev/smart/mixed-language/).
The other new rows are corrections in their own right rather than gate tuning:
- **Add missing spaces** (on) splits a word you typed without its space when both halves are known words, the two are a pair the keyboard has seen together, and nothing else explains it. Off, the suggestion strip stops offering such two-word splits as candidates too. "ofthe" becomes "of the", and a letter next to the spacebar can stand in for the space itself, so "amibtomake" becomes "ami tomake". Two known halves alone are not enough, because the word lists carry every short fragment a corpus produced and an unlisted word ("config", "inbox", a name) that happens to break into two of them is far commoner than a missed space. So the pair has to be in the language's downloaded word pairs, or one you have typed yourself at least twice, and a word in your personal dictionary is never split. The second reading also needs the tap to have landed low on its key, toward the spacebar. Its first half can be a single letter only when that letter is a word at least as common as the one after it, which `i` and `a` are, so `ibwant` with the `b` tapped low becomes "i want". Since 0.6.6 a tap in the lower half of the key that didn't land that far down still counts for something: the split goes on the strip and on a chip you can tap, but is never applied on its own, so `tonthe` with the `n` tapped just below its middle offers "to the" where it used to offer nothing. A tap in the top half of the key is never read as a missed space. Until the pair is known a split stays a suggestion on the strip. Neither reading will ever end on a single letter: a downloaded word list is built from a corpus tokenised at the apostrophe, so it carries a bare "s", "t" and "don" as ordinary words, and without that rule "thats" reads as "that s". The first half may still be one letter, because "alot" really is "a lot". Backspace right after puts your text back, like any other correction. A space that took the place of a letter goes the other way: with `e` before the space and `ough` being typed, the strip offers *enough* as a chip that replaces both. That's a chip only, never applied on its own, and only while what you're typing is at least ten times rarer than the joined word, since a space before a real word was most likely meant: `i to` stays two words. Before 0.6.6 the fragment had to be no word at all, which a downloaded word list (some count `ough`) or a few sightings in your personal dictionary was enough to break. Two missed letters can turn into a full stop: both `b`s of *lobby* land on the space bar, the double space becomes `. `, and the field reads `lo. y`. Since 0.6.6 the word typed after a full stop that a double space put in gets the same chip, here *lobby*, when two of the letters beside the space bar would join the halves into a word. Tapping it takes the full stop out too.
- **Fix number row slips** (on) treats a lone digit inside a word as a tap that landed one row too high, so "as3" can commit as "ase". The row is the one on your screen: a layout with a number row of its own (Eastern Arabic digits, say) has its own keys checked against the letters under them, since 0.6.4. Only a same-length, single-digit swap is ever trusted: a word starting with a digit, or one with two or more digits, is left alone. The letter has to be one the digit sits over or beside, so a `3` is read as `w`, `e` or `r` and nothing else. Before 0.6.6 any letter could stand in, and "ton3" stayed as typed because "tony", four keys from the `3`, was too close behind "tone" for autocorrect to choose. Chinese Pinyin and Jianpin read a digit in the reading the same way since 0.6.6: `niha9` converts as *nihao*. The row only appears while the [number row](https://wmkeyboard.pages.dev/reference/settings/layout/) is on, since that's the only time the slip is possible.
- **Context-aware suggestions** (on) reorders the top candidates using the words before them, drawing on your own word pairs, common English pairs and the words already in this conversation. It only reorders where there's real evidence, and it never changes which word autocorrect applies.
## Details & edge cases
- **Structured and secure fields are a hard gate, not a per-setting one.** Autocorrect, both auto-space rules, and the autopilot bias all share one underlying check: the field must be a plain text field, not a password field or a keypad (email, web address, phone, number). There's no way to enable autocorrect specifically in a password field.
- **An autocorrected word earns no personal-dictionary credit** at the moment it's committed, so the engine's own guess isn't reinforced as if you'd typed it deliberately. Reverting it with backspace teaches the original word instead (see [Undoing a correction](#undoing-a-correction)).
- **The revert window is exact, not fuzzy.** It only fires if the caret is still precisely where the correcting commit left it and the text immediately before it is still `"corrected word" + " "`, unchanged. Any cursor movement, or any edit elsewhere in the field, silently disarms it. Older corrections are reached through the [undo chip](#taking-a-correction-back-later), which keeps the last five.
- **Two-source agreement bypasses the slider.** If your personal dictionary and the standard dictionary happen to name the same fix for a typo, it's applied even below the confidence slider's threshold. The slider only governs cases where the two disagree, or where only one source has an opinion. It can't resurrect a rejected fix, though: a blacklisted, filtered or retired candidate is dropped from the pool before this test ever runs.
- **The confidence check is symmetric with the engine's internal default.** The slider's default position (4.0×) matches the value the underlying suggestion engine falls back to if a setting were ever missing, so a fresh install and an explicitly-set default behave identically.
For the rest of the suggestion strip, including completions, next-word prediction, and how learning ranks your own words over time, see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). To keep a word out of both suggestions and autocorrect entirely, use the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/).
---
# Password manager chips & smart replies
URL: https://wmkeyboard.pages.dev/smart/autofill/
Section: Suggestions & correction
Summary: Saved logins from your password manager, and the system's suggested replies, both shown as tappable chips in the suggestion strip.
Two different things can show up as chips inside the suggestion strip, and they arrive over the same Android API.
The first is your password manager. Tap into a login field and it can offer its saved entries as chips instead of popping up its own dropdown over the keyboard. Tap one and the field fills. The keyboard itself never sees the username or password that went into it.
The second is Android System Intelligence, which reads the conversation on screen and proposes short replies ("On my way!", "Sounds good"). Those show up beside your word suggestions rather than replacing them.
They look similar, but the keyboard keeps them well apart. Each lane has its own toggle, its own limit on how many chips it may show, and its own rules about how much of the strip it gets. Wanting saved logins in the strip says nothing about wanting the system to read your messages, and the other way round.
## Password manager chips
Focus a login field with a password manager set as your system autofill service, and its saved entries (if it has any for that app or site) appear as chips across the suggestion strip. The chips take over the strip completely while they're up, so word suggestions don't share the row with them. That's deliberate: a saved login answers the field directly, which beats anything the dictionary could offer. Tap one to fill the field the same way you would from the manager's own dropdown.
There's a dismiss button (✕) at the end of the row if you'd rather ignore the chips and type or search normally. Tapping it clears the chips until the next response comes in, which in practice usually means the next field you focus.
The keyboard asks for up to six chips and sizes them to the strip's height, and no single chip may be wider than half the screen. A long address is ellipsized rather than taking the whole row, so you can always see that there's another entry beside it. The row scrolls horizontally for the rest.
Chips are also cleared automatically the moment you leave the field, so a login suggestion for one app never lingers into the next field you tap, in this app or another one.
### Which password managers work
There's no allowlist of specific apps. Inline autofill chips use Android's standard inline-suggestions API, so any password manager that supports it and is set as your device's autofill service can offer chips this way: Google's built-in password manager, Bitwarden, 1Password, Dashlane, and others that implement the same platform API. The keyboard requests a chip size and count from whatever service is active. It doesn't know or care which one it's talking to.
The feature needs Android 11 (API 30) or newer. On older versions, the setting doesn't appear and the suggestion strip behaves exactly as it always has. Your password manager's own autofill dropdown still works, but it isn't drawn inline.
Both sides have to opt in. The keyboard declares that it can host inline suggestions, and your password manager has to support them too. If either half is missing, Android falls back to the manager's own dropdown drawn over the keyboard.
### When nothing comes up
Typing / Suggestions / Password manager button
Some apps and sites never ask Android to autofill their login screen, so neither chips nor a dropdown appear. A keyboard can't make that request for them: only the app can. What the keyboard can do is open the manager. In a password field with nothing from the manager on the strip, a button names the password manager set as your system autofill service, **Open Bitwarden** for example. Tap it, unlock the manager and copy the password; back in the field, the [paste chip](https://wmkeyboard.pages.dev/smart/chips/) offers it, masked. The ✕ hides the button for that field. It doesn't appear in a PIN or code box, or while a one-time code is on offer. On by default, Android 9 and later, and only with a password manager chosen in Android's autofill settings.
When the manager is [WM Pass](https://wmkeyboard.pages.dev/tools/wm-pass/), the button stays off. WM Pass answers the field itself, with **Unlock WM Pass** on the strip while its vault is locked, so a second button there would only get in its way.
Typing / Suggestions / WM Pass tool in password fields
Turn this on to get the button back as **Fill from WM Pass**. It opens the keyboard's own WM Pass tool instead of the app: the logins saved for the app in front come first, and a tap types one in without leaving the field. Off by default, and only seen when WM Pass is the autofill service.
## Smart replies
When you tap into a reply box in a messaging app, Android System Intelligence may offer a few one-tap answers to whatever's on screen. Up to three of them appear at a time, and they're drawn by the system, not by the keyboard, the same way password manager chips are.
Where they sit depends on what else the strip is doing:
- **Nothing typed yet.** The replies take the row, with a ✕ to dismiss them. This is the case they're for: the message is on screen, the box is empty, and the reply is the whole answer.
- **You've started typing.** They move to the tail of the strip, after the word candidates and any emoji predictions, behind a thin divider. Word suggestions keep their usual three slots; the reply row is width-capped and scrolls if all three don't fit.
- **A recently-copied paste chip is also up.** The paste chip shrinks to its narrow form instead of stretching, and the replies take the rest of the row.
Tapping a reply commits it through the system, exactly as it would from the reply surface the system draws itself.
Not every app offers replies, and the system decides when it has something worth suggesting. An empty strip in a chat app usually means the system had nothing to offer, not that the setting is off.
Replies aren't the only thing this lane can carry. The system also sends action chips, which are shortcuts into another app rather than something to type. They share the same toggle and the same three slots, and they're sorted behind plain replies, so a reply always gets the room first.
## Options
Typing / Suggestions / Password manager suggestions
Typing / Suggestions / Smart replies
Both toggles are on by default, and both only show up in the Typing screen on Android 11 and newer.
**Password manager suggestions** off stops the keyboard requesting inline suggestions at all. Your password manager's normal autofill dropdown still applies. It draws over the keyboard the way it did before this feature existed, instead of inside the strip. The setting has no effect until a password manager is set as your system autofill service, since without one there's nothing to request chips from either way.
Turning it off also turns off smart replies, whatever that toggle says. Android makes one inline request per field and both kinds of chip come back down it, so asking for replies is also what sends your saved logins into the strip instead of into your manager's dropdown, so there's no way to accept one and decline the other. Off means the keyboard asks for nothing, and your manager draws its own UI again.
**Smart replies** off stops the keyboard asking the system for replies. Nothing else about the strip changes, and login chips are unaffected. (The reverse isn't true, as above: login chips off takes replies with it.)
Turning both off means the keyboard doesn't ask for inline suggestions at all.
## Details & edge cases
- **The keyboard never sees a chip's contents.** Chips are drawn by the password manager or by the system, not by the keyboard. The keyboard only says how much space is available, a size and a chip count, and gets back an opaque view it displays as-is. Whatever username, password or suggested reply sits behind a chip stays inside the other process the whole time.
- **Chips follow the keyboard's theme.** The request names the strip's own chip background and text colors, so a saved login looks like the word suggestions it sits among instead of a system-default pill dropped onto a dark board. Only colors are set (the sender still decides the text, the icon and the layout), and the very first request after the keyboard's process starts goes out without them, because the theme isn't resolved until the board has drawn once. Your password manager can ignore all of it, in which case you get its own styling, which is always legible.
- **Arriving chips wake the strip up.** The bar above the keys rests on the toolbar (tool icons) when there's nothing to suggest, and chips from either lane count as something to suggest, so the strip takes the row when they land. That matters because neither kind of chip arrives while you're typing. A password field has no word candidates, and a smart reply comes before you've written anything.
- **Login chips stay on in incognito.** The saved-login chips keep appearing while [incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) is on, whether you switched it on manually or it engaged automatically because the field asked for no personalized learning. They record nothing, the keyboard never sees what's in them, and a private browsing tab is exactly where you log in. Smart replies are the one kind that incognito does block: a proposed reply means the system read the conversation on screen.
- **Password fields are exactly where these belong.** Secure and password fields aren't specially blocked from autofill, and neither is incognito. They're where you'd expect a password manager to have something to offer. (Password fields do suppress the ordinary word-suggestion strip, which is a separate and unrelated behavior. See [incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/).)
- **Independent of the general suggestions toggle.** Turning off the main **Suggestions** toggle, the one for word predictions above the keyboard, doesn't affect either kind of chip. They're checked separately, so you can have one without the other.
- **Tapping a chip vibrates and clicks like a key**, following your haptics and key sound settings. The chip is drawn by another app, so the keyboard never sees the tap itself; it notices the chips being taken away just before the field fills, and buzzes then. A password manager that asks you to unlock it first opens its unlock screen instead, and that tap doesn't vibrate.
- **The ✕ clears both lanes.** Dismissing chips means "stop offering me things about this field", and both lanes are answering the same field, so both go until the next response.
- **Requires Android 11 or newer.** Login chips also need a password manager currently set as the system autofill service. Anything missing means the strip behaves as if the feature weren't there.
Available identically in every build of the keyboard.
For everything else that shows up in the suggestion strip, see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). For how incognito mode and secure fields affect what the keyboard remembers and suggests, see [Incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/).
---
# Suggestion blacklist
URL: https://wmkeyboard.pages.dev/smart/blacklist/
Section: Suggestions & correction
Summary: Words the keyboard must never suggest, enforced across every suggestion path.
The suggestion blacklist is a list of words you never want offered back to you. A nickname you don't want autocompleted for a stranger, a correction you're tired of seeing, anything at all. Add a word to it and the keyboard keeps it out of the suggestion strip and out of autocorrect for good, while still letting you type and send it yourself whenever you choose to.
smart/blacklist-editor — The suggestion blacklist editor, reached from Typing settings.
## Adding and removing words
Typing / Suggestions / Suggestion blacklist
Tap **Add word** at the top of the editor. A dialog opens with a **Word** field and a **Block in** row of chips: **All languages**, then one chip per language you have enabled. Type the word, pick where it applies, then confirm with **Add**. The **Add** button stays disabled until the field has something in it. The word is stored lowercased and trimmed, so capitalization and stray spaces in what you type don't matter.
The list itself is sorted alphabetically, with a plain trash icon on each row to remove one. A word blocked in one language says so under the word ("Only in French"); a word blocked everywhere has no line under it. Once the list has at least one word in it, a **Clear the blacklist** row appears above it, which empties every list, global and per-language alike, after a confirmation. Past a dozen words a search field appears above the list, the same threshold the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) screen uses, and the list itself draws 100 rows at a time with a **Show N more words** row to reveal the rest, so a list hundreds of words long never composes all at once.
You can also add a word without leaving the keyboard. Press and hold it on the suggestion strip and pick **Never suggest** from the menu that opens. The same menu offers **Suggest again** for a word that is already on the list, so a slip is one hold away from being undone. The rest of that menu, and the word card behind its **Edit** item, are described on [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip).
## Blocking a word in one language only
Typing / Suggestions / Suggestion blacklist / Blocked from the keyboard

A word can be a nuisance in one language and perfectly good in another: a French word the English strip keeps guessing, say. Blocking it for all languages would take it away from French too, so the blacklist keeps two kinds of entry. A word blocked for **All languages** is never suggested, whatever you are typing in. A word blocked for one language is kept out of the strip only while that language's keyboard is active, and offered as usual everywhere else.
The Add dialog asks which you want each time. The keyboard's own **Never suggest** cannot ask, since it is one item on a hold menu, so **Blocked from the keyboard** at the top of the editor decides for it: **All languages** (the default, and how the menu has always behaved) or **Current language**, which blocks the word for the language you were typing when you held it. Deleting a word that only a downloaded word list still knows follows the same setting, since that deletion works by blacklisting.
**Suggest again**, and adding a word by hand from the strip's menu or the word card, take the word off every list it is on. There is no per-language undo on the keyboard, because the opinion being reversed is "never", not "never in French".
A word blocked in one language is not forgotten from your personal dictionary, unlike a word blocked everywhere: the dictionary is shared between your languages, and the word is still wanted in the others.
Before you add anything, the screen shows "No words in this list yet. Add a word above." The row back on the Typing screen reads "Words to never suggest or autocorrect to." Once the list has entries, that row's subtitle switches to a running count instead, like "3 words never suggested."
## What gets filtered
A blacklisted word is matched case-insensitively and kept out of every place the keyboard offers a word back to you, well beyond the ordinary English suggestion strip:
- **Completions**: dictionary, custom dictionary, secondary-language, personal-dictionary, contact-name, contact-email, and app-name completions, plus missing-space split suggestions, are all filtered before they reach the strip.
- **Bangla (Avro) suggestions**: loanword-map hits, phonetic siblings, and the phonetic transliteration itself are filtered the same way, so the blacklist reaches transliterated composing too, not only plain-English typing. The composing preview that shows a listed spelling mid-word skips blacklisted spellings too.
- **Next-word predictions**: the empty-composing bigram predictions that appear before you've typed anything are filtered as well.
- **Glide typing**: a decoded stroke's candidates go through the same filter, so a blacklisted word can't arrive by swipe either.
- **Autocorrect targets**: a blacklisted word is never chosen as the word autocorrect replaces your typo with, even if it would otherwise be the best-scoring candidate. The same holds for the two halves of a missing-space split correction.
The blacklist never touches the word itself as text. Typing it, editing it, and sending it all work exactly as normal. Blacklisting only removes a word from being offered, which makes it a suggestion filter rather than a content filter.
"Block offensive words," a separate toggle under Typing / Automatic corrections / Block offensive words, uses this exact same filtering mechanism. It runs against a bundled list of profanity and slurs instead of words you typed in yourself. Each of your languages has its own list, and one language's list doesn't hide a plain word of another: English's list flags `wel`, `nog` and `ging`, which are everyday Dutch, so they stay on the strip when Dutch is in your mix. A word flagged by a language whose list the keyboard can't check against, or one no language of your mix knows, stays hidden. A word you added to your personal dictionary by hand is never hidden by this filter. It's on by default, but it only shows up in the Typing screen while autocorrect is turned on, even though (like the blacklist) it also affects plain suggestions. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) for the rest of the automatic-corrections settings.
## Details & edge cases
- **Nothing is blacklisted by default.** The list starts empty and you build it entry by entry.
- **A word can be on more than one list.** Blocked in French by hand and later blocked everywhere from the keyboard, it shows twice in the editor, once per list, and the trash icon on each row removes only that entry. The row back on the Typing screen counts entries, so that word counts as two.
- **The count and the filter are per language being typed.** Switching keyboards switches which per-language list applies; the global list applies throughout.
- **It's mostly independent of your personal dictionary, with one automatic link.** The blacklist and the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) of learned words are two separate stores, and neither settings screen offers a shortcut to push a word from one into the other. But blocking a word everywhere does purge it from the personal dictionary and from the words still waiting to be learned, the moment the keyboard picks up the change, and it stays out of both while it remains blocked; blocking it in one language only filters the suggestion, leaving what the keyboard learned alone. There's also a link on the keyboard itself: **Delete** on a held suggestion forgets the word from the personal dictionary and, when the word is still in a downloaded or imported list, puts it on the blacklist as well, since that is the only way to unlist one word from a read-only list. As a candidate, meaning something offered to complete another word or something autocorrect would replace a typo with, the blacklist always wins, so the word stays suppressed either way. But if you type that exact word yourself, autocorrect leaves it alone regardless of the blacklist, the same as it would for any word already known to the keyboard.
- **Removing a word un-blocks it immediately.** If a global block already purged it from your personal dictionary, taking it off the blacklist starts the word over rather than restoring what it had learned before. You don't need to touch the personal dictionary at all.
- **A word doesn't need history to be blacklisted.** You can blacklist any word, learned or not. The blacklist doesn't check whether the word exists in any dictionary first.
- **Backed up like any other setting.** The list lives in the same settings store as everything else in the app, so it's included automatically in a [settings backup](https://wmkeyboard.pages.dev/privacy/backup/). There's no separate export step for it.
- **An email field is the one gap.** In a field the app marked as an email address, the strip is driven straight from your contacts' addresses rather than through the ordinary suggestion pass, and that shortcut doesn't consult the blacklist. Blacklisting an address keeps it out of the strip everywhere else, but not there. See [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/) for what that path does.
For the rest of the suggestion strip (completions, corrections, and next-word prediction), see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). For teaching the keyboard new words instead of blocking them, see [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/).
---
# Smart chips
URL: https://wmkeyboard.pages.dev/smart/chips/
Section: Suggestions & correction
Summary: Inline answers above the keys: calculations, conversions, and useful bits pulled from your clipboard.
Smart chips watch the last 48 characters before your cursor. The moment that text looks like something a tool can answer, the answer lands on the suggestion strip in place of word candidates. Tap the chip to type the answer, or its gear button to open the full tool with the same numbers already loaded. A few chips have nothing to type and only ever open a tool. Those carry no gear, and the whole chip is the door. The gear also stays hidden while its tool is switched off under Tools, since there would be nothing for it to open.
The gear takes the text it answered out of your message on its way into the tool, because the tool types its own result in that spot. If you change your mind, close the tool without inserting anything and press backspace once: the first backspace puts `150 usd`, or `calculator`, back where it was. Typing anything first, or moving the cursor, gives up that undo. To keep the text every time, turn on **Keep the text when a chip opens its tool**; the tool's result then lands after your text.
## Doing the math
Type an expression like `12*8` and the strip offers `96` as a tappable chip. It recognizes `+ - * / × ÷ − ^ % ( )`, `√`, `π`, and `e`, along with implicit multiplication (`2π`, `3(4+1)`). A plain division counts on its own, so `1/2` offers `0.5`. Tap the chip and the result replaces what you typed. Type `=` yourself and the answer is appended instead, leaving your sum in place, so `12*4=` becomes `12*4=48`.
Permutations and combinations use the shorthand a calculator keypad uses: `5p3` offers `60` and `4c2` offers `6`. Both letters have to sit tight between two digits, so `page 5 c 2` stays a sentence.
A few things deliberately don't trigger a chip. A bare number on its own isn't a sum, and `100%` on its own reads as "percent" rather than a sum to evaluate. A `%` with arithmetic around it does count, and reads as it does in the [Calculator](https://wmkeyboard.pages.dev/tools/utilities/#doing-the-math): `100+20%` offers `120`. Named functions and word-operators like `sin`, `sqrt`, `log`, or `mod` never show up as an inline chip either. Apart from `p` and `c`, the symbol scanner only looks for digits, operators, and the two symbols `π`/`e`. Anything with more letters than that works only by opening the Calculator tool itself.
Some arithmetic gets written as a sentence, though, and a second scanner catches three shapes of it: a share of an amount (`15% of 200`, `15% on 200`, `15% tip on 4500`), a discount (`20% off 1500`, `20% off of 1500`), and a bill split (`split 4500 between 3`, `split 4500 3 ways`, `4500 split 3 ways`; `among` and `by` work as well as `between`, and `people`, `persons` or `friends` as well as `ways`). Each one produces an ordinary calc chip, and the gear opens the Calculator on the equivalent sum.
### Sums in number fields
An amount, quantity or price box works too. Those fields only take digits, so a `×` typed into one would be dropped before the chip could read it. Instead the keyboard holds the sum itself. Type the first number, then an operator from the hold on the `-` key (or the minus itself): the number stays in the field, and the rest of the sum appears on the chip as you type it, `120 × 3 → 360`. Tap the chip, or press Enter or `=`, and the answer replaces the number in the field. Backspace edits the sum, and taking out its operator leaves the field as it was. Moving the cursor or leaving the field drops the sum unchanged. A field that only takes whole numbers gets the answer rounded, shown as `~3`, rather than losing its decimal point. PIN fields and phone fields are left alone.
The chip's gear icon opens **Calculator** with your expression prefilled. Result precision (decimal places) is shared with the unit converter, and there's a separate degrees/radians switch for trig, set below in [Options](#options).
## Converting currencies and units
smart/currency-chip — A currency chip: amount and source on the left, the converted amount on the right, gear button to open the full converter.
Type an amount next to a currency (`150 usd`, `150$`, `$150`, or `150 dollars`) and the strip converts it into your other currency. Symbols, spelled-out words and 3-letter codes all work. Big amounts can be written the short way or the long way: `1.5k usd`, `2m dollars`, `3 crore taka`, `2 million dollars`, even `1 thousand 500 dollars`. `k`, `m` and `b` are thousand, million and billion, and `lakh` and `crore` work as well. If what you typed is already in your target currency, the chip flips and converts the other way, so either side of your currency pair gets an answer. The result shows a currency's name rather than its code (`Taka`, not `BDT`), unless you pick the symbol (`৳18,000.00`) or the code under **Write the result as** in the [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) options. If exchange rates haven't loaded yet, the chip shows a brief spinner while they fetch. In the F-Droid edition it reads **Get the rate** instead and fetches only when you tap it, unless **Fetch rates as you type** is on in the [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) options.
Coins work the same way, and they're on out of the box. `1 btc`, `0.5 ETH`, `2 bitcoin`, `100 doge` and `100000 sats` all convert into your currency. Point your pair at a coin and the conversion runs the other way, so `1000000 bdt` becomes a fraction of a bitcoin, with enough digits to read. Tickers that double as ordinary English words, such as `DOT`, `LINK` and `NEAR`, only count in capitals, so "meet me in 1 near future" stays a sentence. Coin prices load the first time you type a coin amount, not before.
Units follow the same shape. Type `1 ft` (or `1ft`) and the chip offers the same length in meters, across 14 categories: length, mass, temperature, area, volume, speed, time, data, energy, power, pressure, angle, frequency, and fuel economy. Spelled-out magnitudes work here too, so `1 thousand miles` converts as 1000 miles and `2 lakh km` as 200,000 km. Only the words, though: `5m` is five meters and `1.5k` stays out of unit territory, since the single letters already mean something metric. Short, easy-to-confuse abbreviations (`c`, `f`, `in`, `s`, and a couple dozen others) only count glued directly to the number, so `30c` triggers and `30 c` doesn't, and ordinary sentences don't turn into unwanted chips. The unit it converts into is whichever one you paired it with last time you used the converter. The first time, it falls back to a sensible default: feet to meters, kilograms to pounds, and so on.
To pick the unit it answers in, name it after `in`, `to`, `into` or `as`: `1 mile in cm` gives `160934.4 cm`, and `30c to f` gives `86 °F`. The unit you name can be any spelling the converter knows, short ones included. One that measures something else, like `1 mile in kg`, gets no chip.
Anything landing in feet is read as feet plus inches, since that's how people say it. `1m` becomes `3 ft 3.37 in`, not `3.2808399 ft`. The chip answers in the same style you asked in, so `1m` gets symbols back while `1 meter` gets `3 feet 3.37 inches`. **Feet and inches** under Tools / Unit converter / Options turns this off if you'd rather have the decimal.
### The chip fits itself to the strip
A conversion chip prefers to echo what you wrote: type `100 dollar` and the left side reads `100 Dollar`, not `100 USD`. When the full answer doesn't fit the strip, the chip gives things up one at a time instead of cutting the number off mid-digit. First to go is a `.00` tail on the result, then exact decimals (a rounded preview wears a `~`, as in `~৳122,001`), then names shrink to codes and codes to symbols, then the font steps down a size. Only as a last resort does a spelled-out amount flatten to digits, so `1 thousand` becomes `1000`.
Unit chips walk the same ladder. `1609.344 km` rounds to `1609.34 km` and then `~1609 km`, and a spelled-out unit shrinks to its symbol (`miles` to `mi`) before the amount gives up its words. A feet-and-inches answer walks its own version: it drops the spelled-out names first (`3 feet 3.37 inches` to `3 ft 3.37 in`), then the fraction of an inch (`~3 ft 3 in`), and finally the units themselves, for the prime marks everyone reads as feet and inches anyway (`~3'3"`). None of this changes what a tap types. The inserted amount is always the exact, full-precision result.
Both chip types share the same wide look (`[icon] query → result [gear]`) and take the full width of the strip, since a recognized calculation or conversion is a better use of that space than word candidates. The date and weather chips further down this page are drawn the same way.
## Grouping long numbers
Type a long run of digits on its own, like `1234567`, and the strip offers it back with separators in place. Tap the chip and the grouped number replaces what you typed.
Which grouping you get follows the language you're typing in. English and most other languages get thousands, so `1234567` reads as `1,234,567`. Bangla, Hindi, Urdu, Nepali, Tamil and the other South Asian languages get the lakh and crore grouping, where only the last group holds three digits: `1,00,000`, `10,00,000`, `1,00,00,000`. **Grouping style** in [Options](#options) fixes one of the two if you'd rather not have it follow the language.
A number typed in Bangla digits comes back in Bangla digits, and a decimal point you've already typed carries its fraction through untouched, so `1234567.89` offers `1,234,567.89`. The chip is drawn like the calculation and conversion chips, and its gear opens the Calculator with the number loaded.
This chip is deliberately hard to trigger, since most long digit runs are not amounts. See [Why a chip stays quiet](#why-a-chip-stays-quiet) for the rules.
## Keyword chips open a tool
Type a tool's keyword on its own (`wiki`, `calc`, and so on) and a narrower chip offers to open that tool, dropping the word you typed. The text-editing tool is the one exception: `edit` stays in the field, since that tool works on the text that's already there. This chip stays narrow instead of filling the strip, because the word you're typing might be meant as that word, and word candidates keep the rest of the row.
smart/keyword-chip — Typing wiki offers a narrow chip to open Wikipedia, leaving room for word candidates.
Each tool's own settings list the words that open it, and let you change or clear them. Matching ignores capitals by default, and a per-tool **Match capitals** switch makes that tool compare literally instead, which is how `AI` can open the AI tool without every auto-capitalised "Ai" doing the same.
## Chips that read the sentence
Five more families watch for text that isn't a sum or a keyword but still sounds like a job a tool does. They ride the same master switch and each has its own toggle in [Options](#options).
- **Resolve dates.** A forward-pointing phrase (`tomorrow`, `next friday`, `aug 14`, `the 15th`) gets a wide chip showing the date it lands on. A tap keeps your phrase and appends the date in brackets, so "next friday" becomes "next friday (21 Aug)". The gear opens the Calendar on that day. Only forward dates count, because "last friday" needs no annotating. **Date chip formats** (advanced) writes the date your own way: type one or more [date patterns](https://developer.android.com/reference/java/text/SimpleDateFormat), separated by `|`, such as `EEE d MMM | yyyy-MM-dd`. The chip shows the first and a tap writes it, so "tomorrow" becomes "tomorrow (2026-11-04)" with `yyyy-MM-dd`. With more than one, hold the chip to switch to the next. Month and day names follow the phone's language. A pattern that doesn't read leaves the chip's own wording.
- **Answer weather questions.** `will it rain`, `how's the weather`, `how hot is it` and similar get a wide chip with the conditions on it. This one never types into your field, since the question was probably meant for the person you're writing to. Tapping it opens the Weather tool instead. It needs a weather location set, and shows a spinner while the forecast fetches. In the F-Droid edition it reads **Get the forecast** instead, and the first tap fetches the forecast rather than opening the tool, unless **Fetch the forecast as you type** is on in the Weather tool's options.
- **Offer lookups.** `define serendipity` or `what does X mean` offers the Dictionary; `who is Tesla`, `what is X` or `tell me about X` offers Wikipedia. Narrow chip, and it leaves your question alone.
- **Translate hints.** `how do you say`, `translate this`, or `in spanish` (sixteen language names are recognised) offers the Translate tool. Tap it after a sentence, as in `good morning in spanish`, and the hint words go from the field, the words before them on that line are selected, and Translate opens on them, already translated into the language you named, which also becomes its target. **Replace** then puts the translation where those words were. With nothing before the hint, it opens the tool and leaves your text alone. Turn on **Translate "in …" where it stands** under Tools / Translate to skip the panel: the tap puts the translation in place of the words and the hint, and the chip's Translate button still opens the tool.
- **Ask home.** With a Home Assistant on the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/)'s list, a sentence that reads like a command for the house (`turn off the lights`, `dim the bedroom to 30%`, `lock the front door`, `open the blinds`) gets a chip that sends it to Home Assistant's Assist instead of leaving it in your message. Off with **Suggest commands as you type** on the tool's page.
- **Location hints**, part of **Translate hints**. `I'm at`, `my location`, `where I am` or `here is my location` offers the [Location tool](https://wmkeyboard.pages.dev/tools/location/), so the place goes in right after your words. Narrow, and it leaves your text alone.
- **GIFs for celebrations.** `happy birthday`, `congrats`, `eid mubarak`, `শুভ জন্মদিন` and a handful more offer a GIF search already pointed at the occasion. It falls back to the sticker panel when the GIF tool is off. Your own stickers work differently: they turn up by themselves when you type their title, a keyword or an emoji, and you send one with a single tap. See [Stickers as you type](https://wmkeyboard.pages.dev/emoji/stickers/#stickers-as-you-type).
Each only fires when the tool it would open is switched on under Tools. If one of these switches is on while its tool is off, a note under the switch names the tool, with a **Turn on** button that switches it back on. The narrow ones are hints rather than answers, so each gets one shot per field: once you type past a lookup or intent chip, that tool won't hint again until you move to another field. One wiring quirk is worth knowing. **GIFs for celebrations** sits inside **Translate hints** rather than beside it, so turning the translate hints off takes the celebration GIFs with them, while turning the GIFs off leaves translate alone.
## Calling or texting a contact
Typing / Smart chips / Call or text a contact
Type the name of someone in your contacts and a narrow chip with their name and a phone icon comes up beside the word candidates. Tap the name and your phone app opens with their number already in the dialer, ready for you to press call. Tap the message button on the chip's right and your messages app opens addressed to them. The keyboard never places the call or sends anything itself, and the name you typed stays in your text, since you may have been writing it for the person you're chatting with.
What counts as a name:
- **The whole name, in any case.** `jane doe`, `Jane Doe` and `Mom` all work, so long as that's how the contact is saved.
- **The first name alone, with its capital**, and only when no one else in your contacts shares it. `Jane` finds Jane Doe, but `jane` doesn't, and with two Marks saved `Mark` finds neither: a chip that has to guess who to ring would be worse than none. This is also what keeps `Will` the contact apart from `will` the word.
- **The name right before the cursor**, or followed by one space. Type on and the chip moves on; punctuation straight after the name (`Mom,`) ends it too.
A contact with several numbers is offered on the one you've marked as default in your contacts, and otherwise on the first one Android lists. Like the lookup and intent hints, each name gets one offer per field: once you type past it, that contact won't come up again until you're in another text box.
The switch is **off** by default because it reads your address book. Turning it on asks for the contacts permission, after a screen explaining what's read. Names and numbers are held in memory only while the keyboard runs, are never written to a file, and never leave the phone. The chip doesn't show in password fields, or anywhere the strip is switched off.
## Repeated text
Typing / Smart chips / Repeated text
Type the same thing a few times in a row, `no no no`, `ok` on three lines, `hahaha`, and a narrow chip reads **Repeat "no"** with a small **×3** beside it. Keep typing the same word and the number ticks up instead of the chip reappearing. Tap the chip and the [Text repeat](https://wmkeyboard.pages.dev/tools/utilities/#repeating-text) tool opens on that text, with the same separator you used and a count ready that at least doubles what you typed (three becomes ten). The copies you typed stay in the field until you press **Insert** there. What Insert does with them is the tool's own setting: by default it takes them out and writes the whole count in their place, so the field ends with exactly that many; **Add after them** leaves them and writes the count after, with the separator between.
What counts as a run:
- **The same text, three times or more, ending at the cursor.** The number is a setting, 2 to 6. One trailing space or line break is fine, since a finished word usually has one; type anything else and the chip moves on.
- **With a space, a comma or a new line between** any text counts, a single letter or emoji included: `a a a`, `5, 5, 5`, `😂 😂 😂`.
- **With nothing between** the text has to be at least two characters and not itself a repetition: `hahaha` and `lollollol` fire, `!!!!!!`, `aaaaaa` and `😂😂😂` don't. A stretched letter or a doubled emoji is a message, not a request, and the tool's Stretch mode is a tap away.
- **The shortest reading wins**, so `no no no no` is four of `no`, never two of `no no`.
- **It has to start at the beginning of the text or after a space**: `xhahaha` is not a run.
Like the other hints, each text gets one chip per field: type past it and `no` won't come up again until you're in another box. A keyword chip beats it (`wiki wiki wiki` still opens Wikipedia), and it beats a contact's name typed three times. The chip stays off in password fields and while the Text repeat tool is disabled.
## Pulling fragments out of your clipboard
Open the [Clipboard](https://wmkeyboard.pages.dev/tools/clipboard/) tool and a row of dashed-outline chips sits above the grid of copied items. It surfaces one-time codes, phone numbers, and links found buried inside your clipboard history, not only in your latest copy. The scan covers your recent clips, sorts codes before phone numbers before links, and shows up to a dozen chips at once. Long-press a chip to see the fragment highlighted inside the clip it came from. Tap it to paste only that fragment, leaving the full clip in history untouched.
smart/clipboard-entity-strip — The Clipboard panel: dashed entity chips above the history, pulled out of several different clips.
One fast path doesn't need the panel at all. Copy a bare one-time code, then tap into a box that asks for a code, and the strip offers it as a dashed **Code** chip in place of the usual "paste your last copy" chip. It's deliberately that narrow. A copied code counts as a secret, so it stays off the strip in an ordinary message field, and a code quoted inside a longer message never becomes a chip there at all: pulling a fragment out of a message is what the Clipboard panel above is for. The fast path is also OTP-only, so a freshly copied phone number or link shows as the ordinary full-clip paste chip until you open the panel for the fragment version.
smart/otp-fresh-copy-chip — Copying a verification text offers only the code, not the whole message, right on the suggestion strip.
Both surfaces are covered by the same setting (see [Options](#options)), and neither one scans a clip you've marked sensitive, or an image or file clip.
A code you never copied has its own route to the strip. If you let the keyboard watch notifications, a verification code that arrives in one raises its own chip above the keys with no copy-paste step at all. That's a separate, off-by-default feature with its own page: see [One-time codes](https://wmkeyboard.pages.dev/smart/one-time-codes/).
## AI suggestions
An optional chip, off by default, that comes from your [AI provider](https://wmkeyboard.pages.dev/tools/ai/#suggestions-in-the-strip) rather than from the text before the cursor. It offers one short continuation of what you're typing, in front of the word suggestions, and is marked with a sparkle so you can tell it from a word. A smart chip takes the strip over it. Tap it to insert the continuation, or hold it to hide AI suggestions in this field. It sends text without a tap, which is why it lives with the AI settings and says there exactly what each request carries.
## Why a chip stays quiet
Each detector has guards against the shapes of text that look like a trigger but usually aren't:
- **Dates** (`12/04`, `12/04/2025`, `2024-07`) never read as arithmetic. A zero-padded pair reads as a date. A lone `-` between two plain numbers is a sum (`12-4` offers `8`) unless either side has four digits or a leading zero, since `555-1234`, `2024-25` and `2024-07` are a phone number, a season and a month. Either way, an explicit `=` forces the answer.
- **Order numbers and prices** don't turn into phone numbers. A leading `#`, `$`, `£`, or `€` rules a run of digits out.
- **Years** don't turn into codes. A bare 4-digit value between 1900 and 2099 is rejected even next to a code-sounding word.
- Google's `G-123456` SMS prefix is stripped before the code check, since it's branding rather than part of the code.
- A code needs a nearby keyword (`otp`, `verification`, `code`, `pin`, `2fa`, and similar) within 40 characters. A deny-list (`zip`, `postal`, `hex`, `swift`, `ifsc`, and others) stops things like "zip code 90210" from qualifying anyway.
- **`try`** in lowercase reads as the English verb, not Turkish lira. Type `TRY` in capitals to convert it.
- Short unit abbreviations (`c`, `f`, `in`, `s`, `k`, and others) only count glued to the number, so "meet me in 5" or "about 30 c out" stay quiet.
- **Long numbers** are only offered grouped when they read as a quantity. The run needs five digits at least and fifteen at most, and a leading zero rules it out, since that marks a phone number, an account or a PIN. It also has to stand on its own: anything joined to a letter, a `.`, `-`, `/`, `:`, `#` or `+` on its left is a version, a date, a ratio or an identifier, and a number that already carries separators is left as it is. Currency, unit and calculation chips all win over it, because a number that means something is worth more than a number that is merely long. And it stays out of fields that ask for a number at all, such as an amount box, a card form or a PIN, since those want the digits they asked for.
- **Repeated text** needs three copies (or whatever the setting says) ending right at the cursor, and never reads a single stretched letter or a doubled emoji as a run. See [Repeated text](#repeated-text).
- Unpunctuated digit runs longer than 11 digits aren't treated as phone numbers unless they start with `+`. That shape rule is what stands in for knowing where you live, so it steps aside once you've listed your own [phone formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/) and the masks decide the length instead.
## Options
Typing / Smart chips
smart/options — Smart chips settings under Typing: master switch and the four per-family toggles.
- **Smart chips**: the master switch, default **on**. Turn it off and all twelve chip families below go at once.
- **Calculate as you type**: default **on**.
- **Convert currencies**: default **on**.
- **Convert units**: default **on**.
- **Tool keywords**: default **on**.
- **Resolve dates**: default **on**.
- **Answer weather questions**: default **on**.
- **Offer lookups**: default **on**.
- **Translate hints**: default **on**.
- **GIFs for celebrations**: default **on**.
- **Group long numbers**: default **on**.
- **Grouping style**: **Automatic**, **Thousands** or **Lakh and crore**, default **Automatic**, which follows the language you're typing in.
- **Call or text a contact**: default **off**. Needs the contacts permission. See [Calling or texting a contact](#calling-or-texting-a-contact).
- **Repeated text**: default **on**. See [Repeated text](#repeated-text).
- **Chip after this many repeats**: 2 to 6, default **3**. Shown while Repeated text is on; the same row lives on the Text repeat tool's page.
- **Keep the text when a chip opens its tool**: default **off**. Leaves the typed amount, sum or tool keyword in place when the gear opens the tool.
Each tool's own page (Calculator, Unit converter, Currency converter) links back here with a **Suggestion chip** row that shows whether its chip is on. The switches themselves live only under Smart chips.
A few related settings live under each tool instead:
- **Degrees** (Calculator, default **on**) and **Result precision** (0–12 decimal places, default **8**). Precision also shapes the unit converter's output.
- **Decimal places** for currency (0–6, default **2**), **Write the result as** (Name, Symbol or Code, default **Name**) and **Refresh the rates every** (1–48 hours, default **6**), under Tools / Currency converter / Options.
- **Convert coins** (default **on**), **Coin decimal places** (Auto to 12, default **Auto**), **Refresh coin prices every** (1–60 minutes, default **5**) and the **Coins** chip grid, under Tools / Currency converter / Cryptocurrency. The same screen picks which source each kind of rate is fetched from.
- **Detect codes, numbers and links** (default **on**), under Clipboard, governs the Clipboard panel's entity strip. The fresh-copy code chip is a separate pair of settings on that same screen: **Suggest the last copy** (default **on**), since the code chip is a variant of that chip, and **Suggest copied codes** (default **Any field**), which is what allows a copied code onto the strip at all.
## Details & edge cases
- A chip won't reconsider text it just inserted. After you tap one, the strip mutes until you edit the field again, so converting 150 USD to Taka doesn't immediately offer to convert the Taka amount right back.
- Smart chips step aside for secure fields, fields that ask for no suggestions, any open panel, and CJK-style conversion composing, since a chip would otherwise steal the space a pinyin reading needs for its own candidates. A reading that spells a tool's keyword still offers that tool, as a chip behind all the candidates with an arrow after its name, unless **Shortcuts while converting** is off.
- **Chips on the far side**, under Typing › Automatic corrections, moves the chips that share the strip with the words to the end of the strip, where a right thumb reaches them: a one-time code, a copied clip, a keyword, and the chip that joins two words or fixes an earlier one. The words stay in their order beside them. A sum or a date fills the strip either way.
- On a conversion layout, every chip comes back once the reading is committed, so `12*4` answers on Pinyin as it does on English. A wide chip (a sum, a date, a conversion) covers the next words until you carry on typing. Every tool's keywords include Chinese, Japanese and Korean words, and a Chinese or Japanese keyword counts at the end of a longer run of text, so 我的笔记 offers Notes.
- They're independent of word prediction. Turning off suggestions elsewhere doesn't turn off any of the smart chips.
- [Power saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) can flip the master switch off (its own **Smart chips** toggle lives under Power saving, off by default), so every chip family goes at once, but it leaves clipboard entity detection alone. Codes, numbers, and links keep getting pulled out of your clips even while chips elsewhere are dimmed.
- Exchange rates come from open.er-api.com (about 160 currencies, updated daily), with frankfurter.dev (European Central Bank, about 30 currencies) as a fallback. Coin prices come from Coinbase with the jsDelivr currency-api behind it. The F-Droid edition starts both from currency-api, which is open data, with Frankfurter behind the currency rates. None of them needs an account or a key, and any of them can be swapped for another under the tool's settings. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what else touches the network.
- A coin chip shows the ticker (`BTC`) where a currency chip shows a name (`Taka`), because coin names shorten badly. It also keeps five meaningful digits rather than two decimals, so a small fraction of a coin still reads as a number instead of `0.00`.
- The currency and unit pair a chip converts into isn't set from a Settings screen, unless you name the unit in the text (`5 kg to lb`). It's remembered from whichever pair you last used inside the [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) or [Unit converter](https://wmkeyboard.pages.dev/tools/utilities/) panel itself.
- Chips answer what you typed. [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) answer what you selected: a different bar, a different trigger, and its own switch under Advanced. The two never share the strip, because nothing is being typed while text is selected.
---
# Contact-aware typing
URL: https://wmkeyboard.pages.dev/smart/contacts/
Section: Suggestions & correction
Summary: Your contacts' names and emails, suggested where they help, never uploaded.
WM Keyboard can complete names and email addresses straight out of your contacts, and app names straight out of your launcher, so `"was"` completes to Wasi and `"john"` completes to `john.doe@gmail.com`. All three sources are off by default, and everything they read stays in memory on the device. Nothing is written to disk and nothing is sent anywhere.
smart/contact-name-suggestion — A contact name completing in the suggestion strip, mid-word.
## What gets suggested
Three independent sources feed the suggestion strip, each with its own toggle in Typing settings:
- **Contact names.** Every contact's display name is split into words (first name, last name, and any middle parts) and offered as a completion, the same way a dictionary word would be. After you finish typing one name, the next part of that same contact's name is offered as the next word: type "Wasi" and "Mollik" appears next, the way a learned bigram would.
- **Contact emails.** Type the first couple of characters of an address, from the very start, the way you'd begin typing the name before the `@`. The full address is offered to complete. This works both in ordinary text and, with a second toggle, inside email input fields.
- **Call or text.** Separate from completion: with **Call or text a contact** on under Smart chips, a finished contact name offers to open the dialer or your messages app for that person. See [Calling or texting a contact](https://wmkeyboard.pages.dev/smart/chips/#calling-or-texting-a-contact).
- **App names.** The labels of apps that appear in your launcher complete the same way ("sign" → Signal), so you don't have to type an app's full name out to search or reference it. This needs no permission. It comes from the same package list Android uses to build your home screen and app drawer.
A word that matches a known contact name or a known app name is never "corrected away" by autocorrect. Contact-email addresses aren't typed out character by character the way autocorrect operates, since you complete one by tapping it from the strip, so this exemption doesn't separately cover them and doesn't need to.
## Using it
### Contact names in the strip
Turn on **Suggest contact names** and start typing a contact's first or last name. It completes like any other word, ranked above ordinary dictionary and learned words. It works in almost every language mode, not only English, since all it does is match against words extracted from your address book.
The Bangla phonetic (Avro) layout is the exception. While you're composing a romanized buffer there, the strip is the Bengali candidate list and nothing else, so contact names, contact emails and app names aren't offered as completions. The name chaining still works: finish a name and the next part of it is still offered as the next word, because that path runs before the Bengali one takes over.
### Contact email completion
Turn on **Suggest contact emails** and type the start of an address anywhere in ordinary text. Two characters are enough to trigger it, and a single letter deliberately isn't, or one keystroke would dump the whole address book onto the strip. The strip offers matching full addresses from your contacts, ranked above everything else in the strip.
A second setting, **Contact emails in email fields too**, extends this into fields the keyboard recognizes as email inputs specifically. Those fields keep no composing buffer the way an ordinary text field does, because letters you type there commit straight to the field one at a time. That leaves the everyday word-completion engine with nothing to work with, independently of Typing / Suggestions / Suggestions in every field. This setting makes an exception for contact-email completions only. It reads the address-in-progress straight from the field instead of from a composing buffer, so the one place you're most likely to want an email address offered is the one place ordinary suggestions can't reach. It only has an effect while **Suggest contact emails** itself is on, and its row only appears in the settings list once that parent toggle is on too. It also needs the master **Suggestions** switch on, and it stands down in a password field like everything else.
Tapping a suggested email in an email field commits the whole address with no trailing space, since an address is usually the entire field. It also stays out of your personal dictionary, because an address isn't treated as a word to learn.
### Addresses you type yourself
**Remember email addresses you type** (off by default, no permission needed) covers the addresses that aren't in your contacts: your own, and the ones you sign in with. Each time you leave a field that asks for an email address, the keyboard keeps what it holds, as long as that's a whole address (an `@` with a name before it and a domain with a dot after it). The next time an email field opens, the addresses you've used most show on the suggestion strip before you type anything, and typing the start of one narrows the list. With **Suggest contact emails** on too, your own addresses come first and your contacts' follow once you've typed two characters.
It learns only from fields that call themselves email fields, never from ordinary text, and it keeps your 50 most recently used addresses. Unlike everything else on this page, these are written to the phone's storage, in the learning data, so they survive a restart. Delete them from the Storage screen in the settings, which lists them as **Email addresses you typed**.
### App names in the strip
Turn on **Suggest app names** and installed-app labels complete the same way contact names do, but ranked lower. Ordinary words that happen to also be app names (Files, Photos, Clock) shouldn't outrank the dictionary, and you type a contact's name far more often than you reference an app by name. App names complete in the suggestion strip only. They don't extend into the system-wide spell checker below.
## Options
Typing / Suggestions / Suggest contact names
| Setting | Default | Needs |
| --- | --- | --- |
| Suggest contact names | Off | Contacts permission |
| Suggest contact emails | Off | Contacts permission |
| Contact emails in email fields too | On (only shown while contact emails are on) | Nothing |
| Remember email addresses you type | Off | Nothing |
| Suggest app names | Off | Nothing |
Turning on either of the two contact toggles requests the Contacts permission the first time, if it isn't already granted. Turning a toggle back off never prompts for anything. Both toggles use the same Android permission, so granting it for one covers the other automatically. App-name suggestions need no permission at all. They're covered by the app's existing ability to list installed launcher activities.
Turning any of these three off clears its in-memory index immediately. There's no lingering suggestion from data the keyboard is no longer supposed to be reading.
## Contact names in the system spell checker
WM Keyboard can also act as Android's system-wide spell checker, the service other apps' text fields use for the red squiggly underline. It's a separate component that you select yourself, under Android's own **Settings → System → Languages & input → Spell checker**, where it's listed as "WM Keyboard spell checker". Installing the app doesn't switch it on automatically.
When selected, it reads the same **Suggest contact names** setting described above (there's no separate toggle for it) to decide whether to load your contacts. If that's on, the spell checker treats known contact names and emails as correctly spelled, so no red squiggle appears under a name it recognizes. It also puts a matching contact name first in the correction menu when you've mistyped one, ahead of the word-list fixes. Because it runs in its own process, it works this way in any app, even while WM Keyboard isn't the active keyboard. See the [system spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/) page for the rest of what it catches.
## Details & edge cases
- **What's read.** A one-shot query against `ContactsContract` for contact display names, and a separate one for contact email addresses, each covering the whole address book in a single pass rather than filtering or searching incrementally. App names come from querying installed launcher activities, the same list your home screen's app drawer is built from.
- **What's stored.** Nothing on disk for contacts or app names. Contact names, contact emails, and app names are held as plain in-memory lookup tables, rebuilt from scratch whenever the keyboard process starts with the relevant setting on, and dropped the instant you turn that setting off. If a query fails for any reason, such as a permission revoked mid-session or an unavailable provider, the source silently falls back to empty rather than crashing or retrying in a loop.
- **What never leaves the device.** There's no network code anywhere in this path. Contacts, contact emails, and app names are read, indexed, and matched entirely locally.
- **Typing a name is still "learning."** The memory-only guarantee covers the index this feature builds. If you type a contact's name as ordinary text, that word goes through the keyboard's general on-device learning path like any other typed word. That's a separate, on-by-default setting, unrelated to the toggles on this page. There's also a distinct, off-by-default setting that would also copy a learned word into Android's own shared personal dictionary, which other apps can read. Leave it off and this doesn't apply. See [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) for both.
- **Before you unlock the phone.** Right after a reboot, before you've entered your device credential the first time, all three toggles behave as off regardless of their saved state. Android's contacts and app-label providers are themselves locked at that point and can't be read no matter what the setting says. Nothing about your saved preference changes. The moment you unlock, contact and app suggestions resume on their own.
- **Name splitting is punctuation-aware.** A contact's display name is broken on spaces, commas, and similar separators, with stray punctuation trimmed from each piece. A name with a middle initial, or a bracketed label like "(work)", still indexes its meaningful parts rather than the punctuation around them. Very short fragments (a single letter) aren't indexed, which keeps initials from cluttering the strip. Matching is case-insensitive, and the name is still shown with its original capitalization when suggested.
- **Only real-looking addresses are indexed.** A contact's stored email is only used if it has an `@` with something on both sides and no embedded spaces. Malformed entries in your address book are silently skipped rather than offered as junk completions.
---
# Grammar check
URL: https://wmkeyboard.pages.dev/smart/grammar/
Section: Suggestions & correction
Summary: Grammar checking with Harper on the phone, GiellaLT's checkers for the Sámi languages and Faroese, or LanguageTool on a server for more than 30 languages.
Grammar check runs your writing past Harper, the same open-source engine behind `harper-ls`, and surfaces fixable issues right above the keys as you type. Harper runs on the phone, so by default nothing you type leaves it to get checked. For other languages than English you can switch on [LanguageTool](#checkers-harper-and-languagetool), which checks on a server instead, and for North, Lule, South and Inari Sámi and Faroese you can download [GiellaLT's own checker](#giellalt-grammar-checkers), which runs on the phone.
## What it catches
Harper checks English spelling, grammar, and style: agreement errors, punctuation and capitalization slips, redundant or repeated words, malapropisms, and a long tail of style nits. It reports twenty fine-grained kinds of issue, which the panel groups into four color-coded categories so an issue reads at a glance:
- **Correctness**: spelling, typo, grammar, agreement, capitalization, punctuation, word boundaries, confused words, misheard phrases, usage
- **Clarity**: readability, redundancy, repetition, word choice
- **Engagement**: enhancement, style, miscellaneous
- **Delivery**: formatting, regionalism, nonstandard
Any of those twenty can be switched off, either one at a time or a whole category at once. See [Filtering what you see](#filtering-what-you-see).
Each issue also lints against a specific English dialect: **American** (the default), **British**, **Canadian**, or **Australian**. You pick that independently of whatever language or layout you're typing with, since Harper only understands English. See [Options](#options) below for where to change it.
## Checkers: Harper and LanguageTool
Tools / Grammar check / Checkers
The **Checkers** list at the top of the settings page says who checks your writing. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/), so rows can be dragged into another order, switched off, set up or removed. It starts with two rows:
- **Harper**, on the phone and switched on. English only.
- **LanguageTool**, switched off. It checks grammar, spelling and style in more than 30 languages, among them German, French, Spanish, Portuguese, Dutch, Polish, Ukrainian and Russian, but it runs on a server. Its row says where your text goes: **Sends your text to api.languagetool.org**.
A check goes to the first checker that is on and knows the language of the layout you're typing with. Harper knows English; LanguageTool knows every language it has, and a language it lacks goes as `auto`, so the server works out what the text is. With the list as it starts, then, English stays on the phone and the other languages go to LanguageTool once you switch it on. Drag LanguageTool above Harper to have it check English too. When the first checker cannot answer (Harper isn't in this build, the server is down), the next one that knows the language answers instead. With no checker that knows the language, Harper checks anyway, as Grammar check always did.
While LanguageTool answers, the panel's title reads **Grammar · LanguageTool**, or the nickname you gave the server. Its issues use the same cards, the same four categories and the same filter as Harper's: each LanguageTool category maps onto one of the twenty kinds (its typos onto Spelling or Typo, its casing onto Capitalization, its confused words onto Usage, and so on). English checks follow the dialect chip: American, British, Canadian or Australian.
### What goes to the server
Every check sends the text in the field, its language code, your Premium username and key if you set them, and the two options below. Nothing else. The panel checks after each pause in typing, but never sooner than 1.2 seconds after the last key while LanguageTool is first in line. **Fix all**, a fix chip and the **Grammar** [selection action](https://wmkeyboard.pages.dev/smart/selection-actions/) re-check the result the same way.
These are never sent:
- a **password field**, or any field an app marks as one
- anything typed in **[incognito](https://wmkeyboard.pages.dev/privacy/incognito/)**, whether you turned it on or the field asked for it
- anything while [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds **Online grammar check**
In each of those cases Harper still checks English, and for other languages the panel says why nothing is checked. Every request appears in [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/) as **LanguageTool**.
### Servers and accounts
Tap the LanguageTool row to set it up:
| Field | What it does |
| --- | --- |
| Server address | Empty for LanguageTool's public server, `api.languagetool.org`. Or the address of a server you run yourself, for instance `http://192.168.1.20:8081` |
| Username and API key | A [LanguageTool Premium](https://languagetool.org/premium) account. Give both or neither. With both and no address, checks go to the Premium server, `api.languagetoolplus.com` |
| Picky mode | Also report the style and typography issues the server leaves out by default |
| Your first language | A code such as `de` or `fr`. LanguageTool then also flags false friends from that language |
**Test** asks the server for its list of languages, so a typo in an address shows up there rather than in the panel.
LanguageTool's server is free software (LGPL), so you can run your own on a computer at home, or anywhere else, from the [jar](https://dev.languagetool.org/http-server) or one of the Docker images. Your text then never reaches a third party. Add it with **Add a provider / LanguageTool**: each server is its own row, with its own address and nickname. This is the same in every edition, F-Droid's included.
The public server takes 20 checks a minute, 75 KB of text a minute and 20 KB a check. Grammar check keeps to that: a long note is cut into pieces of at most 18 KB at paragraph ends, then sentence ends, then spaces, and a piece already checked isn't sent again until it changes. If a minute's allowance is used up, the next check waits for it when that takes seconds, and the panel says the server is busy when it would take longer. Edit the text and it tries again. A Premium account or your own server is sent bigger pieces, up to 60 KB, with no pacing.
## GiellaLT grammar checkers
Tools / Grammar check
[GiellaLT](https://giellalt.github.io/), the language technology group at UiT The Arctic University of Norway, writes grammar checkers for the languages it works on. Five have a published release, and the grammar settings list them under **GiellaLT grammar checkers** at the Advanced level, each with a **Download** button:
| Language | Download |
|---|---|
| North Sámi | 1.5 GB |
| Lule Sámi | 375 MB |
| Faroese | 353 MB |
| South Sámi | 290 MB |
| Inari Sámi | 186 MB |
Each is one file holding the whole checker: its tokeniser, its rules, the speller it suggests spellings from and its messages. It is kept as downloaded and read in place, so its size is what it takes on the phone. The download is checked against the release's SHA-256 before it replaces anything.
With a checker downloaded, it checks first whenever the keyboard is in its language, before anything on the **Checkers** list, and it needs no row there. It runs on the phone, so nothing leaves it. The panel's title then reads **Grammar · GiellaLT**, and each card shows GiellaLT's title for the error, its explanation (in your phone's language where the checker has one, else English) and its suggestions. The first check after the keyboard starts loads the checker, which takes a few seconds; later checks do not. A loaded checker also takes memory, roughly half its download: North Sámi's holds about 650 MB, so on a phone with 4 GB or less, Android may close the keyboard while it is loaded. The keyboard unloads it with its other models once it has been hidden for 2 minutes, and [Resources](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-keyboard-holds-in-memory) lists it while it is loaded, with a button to unload it sooner.
The checkers run on GiellaLT's divvun-runtime, which is a download of its own the first time you download a checker. Unlike the rest of the app it is GPL-3.0, because its VISL CG-3 rule engine is.
## Using the panel
smart/grammar-toolbox-entry — Grammar check in the toolbox, with the dialect chip and a live issue count.
Open **Grammar check** from the toolbox to check whatever's in the field you're writing in. It isn't pinned to the toolbar by default, but two built-in [keyboard modes](https://wmkeyboard.pages.dev/typing/modes/) (**Chat** and **Writing**) promote it to the front of the toolbox, since it's one of the first things worth reaching for in either.
The panel sits in a fixed strip above the key rows rather than covering them, so you can fix an issue by editing the text directly as easily as by tapping a fix chip. Open the panel and it checks the field's current text immediately, with no wait. After that, edits re-check after a short pause (see [Fully offline](#fully-offline-in-process) below).
Each issue is a card: a colored dot and category label, the flagged text struck through, an arrow to up to five suggested fixes, and Harper's explanation underneath. An issue Harper has no fix for shows the explanation in the chips' place instead. From there:
- **Tap a fix chip** to apply that one fix. The field re-checks straight after.
- **Tap the card itself**, meaning neither a chip nor the dismiss icon, to jump the field's cursor to the issue. A multi-word span parks the cursor at its start. A single word with a real replacement gets selected, so you can type over it. Anything else parks the cursor right after the issue.
- **Tap the copy icon** to copy Harper's explanation.
- **Tap the sparkle** beside the dismiss icon, shown while the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) is on, to ask the AI about the issue. The AI panel opens a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#asking-about-another-tools-text) with the issue, Harper's explanation, its fixes and the sentence around it attached, so you can ask why, or whether the fix is right.
- **Tap the dismiss icon** (**×**) to hide a card. It stays hidden only until the text changes and re-lints. Dismissing isn't remembered past that.
- **Tap "Fix all"**, shown whenever at least one issue has a suggestion, to apply every issue's top fix in one go. Fixes go in back to front, so earlier issues don't shift the ones after them. An issue whose span overlaps one already fixed is skipped rather than risk mangling the text.
- **Tap the dialect chip** in the header to switch American/British/Canadian/Australian on the fly. The same text re-checks immediately under the new dialect.
- **Tap the funnel** beside the dialect chip to choose which kinds of issue are worth showing at all. See below.
If nothing's flagged, the panel says so. If the field is empty, it invites you to type or to open a field with text in it. If issues were found but your filter hides every one of them, the panel says that instead, so a quiet panel is never ambiguous.
## Filtering what you see
Harper is thorough, and thoroughness is not always what you want. If you don't care to be told about regionalisms, or you want spelling but not style suggestions, the funnel in the panel header turns individual kinds off.
The filter is two levels deep:
- **Tap a category row** (Correctness, Clarity, Engagement, Delivery) to switch every kind inside it at once.
- **Tap the chevron** on a category to expand it onto its own kinds, then tap one to switch only that kind.
- A category shows a **tick** when all of its kinds are on, a **dash** when only some are, and nothing when they're all off. Turning one kind of an off category back on leaves the category dashed rather than quietly re-enabling the rest.
- Every row carries the number of issues of that kind in the current text, filter or no filter, so you can see what you're hiding before you hide it.
- **Show every kind** at the bottom clears the filter in one tap. It only appears while something is hidden.
The funnel turns accent-colored while any filter is active, so a panel that's hiding things says so from the header.
Filtering is not the same as dismissing. A dismissed card comes back on the next re-check; a filtered kind stays gone until you turn it back on, and the choice is remembered across sessions and carried in your [backup](https://wmkeyboard.pages.dev/reference/settings/backup/).
A hidden kind is hidden from everything the panel does, not only the cards: it's left out of the issue count in the header, and "Fix all" will not touch it. Turning off a kind of suggestion you disagree with means "Fix all" stops applying it, which is usually what you want.
The same filter lives in settings, where the four categories are folds you can open onto their kinds. Changing it in either place changes it in both.
## Fully offline (in-process)
Harper is a Rust engine compiled into `libharper_jni.so` and loaded directly into the keyboard's process. There's no server call anywhere in this path, checked or unchecked. All linting runs on one dedicated background thread, since Harper's internal linter state isn't safe to share across threads.
Re-checks are debounced. After you stop typing, the panel waits before re-linting rather than running on every keystroke (see the **Re-check delay** setting below). Opening the panel or switching dialects skips the wait and checks right away.
The very first check after opening the panel can take a beat longer, since building Harper's linter costs roughly 100 ms the first time. That cost is already paid by the time you open the panel if [Harper is also set as your system spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/). Its own service warms the linter up on its own. Opening the Grammar tool alone doesn't pre-warm it.
## DeepL Write
With a DeepL API Pro key set up under [Tools / Translate](https://wmkeyboard.pages.dev/tools/translate/#deepl) and **DeepL Write** turned on there, the panel gets one more button beside the filter. Tap it and DeepL rewrites the text in the field: grammar, wording, and the style you picked. Unlike Harper, DeepL Write reads German, French, Spanish, Italian, Portuguese, Japanese, Korean and Chinese as well as English.
The answer takes the place of the issue list until you tap **Replace** or close it with its X. **Replace** swaps the text for DeepL's version. On a field longer than 2,500 characters, only the first 2,500 go to DeepL and only those are replaced. If you edit the text before the answer arrives, the answer is dropped, because it no longer matches what is in the field.
Apart from [LanguageTool](#checkers-harper-and-languagetool), when you switch it on, this is the one part of Grammar check that goes online. The text in the field and your DeepL key go to DeepL, and only when you tap the button. Without a DeepL key, or with DeepL Write off, the button is not there and the panel works as described above. It's never there in a password field either, so a password can't be sent to DeepL. The same rewrite is available on selected text as the **DeepL Write** [selection action](https://wmkeyboard.pages.dev/smart/selection-actions/).
## Options
Tools / Grammar check
smart/grammar-settings — The Grammar check settings screen: dialect, re-check delay, and the system-wide group.
| Setting | Default | Range/options |
| --- | --- | --- |
| Checkers | Harper on, LanguageTool off | Harper, LanguageTool servers; see [Checkers](#checkers-harper-and-languagetool) |
| English dialect | American | American, British, Canadian, Australian |
| Re-check delay | 350 ms | 100–1500 ms |
| Show Correctness issues | On | Per kind: spelling, typo, grammar, agreement, capitalization, punctuation, word boundaries, confused words, misheard phrases, usage |
| Show Clarity issues | On | Per kind: readability, redundancy, repetition, word choice |
| Show Engagement issues | On | Per kind: enhancement, style, miscellaneous |
| Show Delivery issues | On | Per kind: formatting, regionalism, nonstandard |
Each category is a fold. The heading's switch turns the whole category on or off; open it to reach the individual kinds, and the closed fold names the kinds still showing. These are the same switches as the panel's funnel.
A **System-wide** group sits below these two, with a **Use Harper everywhere** button and (on Android 12 and up) an **Underline only, no fix popup** toggle. Those set up WM Keyboard as Android's system spell checker, which adds Harper's grammar marks in other apps on top of word-list spelling. See [System spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/) for what that covers and how it differs from this in-keyboard tool.
## Details & edge cases
- **Harper is English only.** It doesn't check any other language, and the dialect setting is global: it applies no matter which typing language or layout is currently active. For other languages switch on [LanguageTool](#checkers-harper-and-languagetool), or use the optional [DeepL Write](#deepl-write) button.
- **The system spell checker is always Harper.** The Checkers list only decides who checks in the Grammar panel. What Android's spell checker marks in other apps comes from Harper on the phone, whatever the list says.
- **Every build.** Grammar check and the spell checker's grammar marks are in every build. F-Droid compiles Harper from source. A build made without Harper's native library shows the tool as not in this build, and its spell checker checks spelling only.
- **A missing engine fails safely.** If `libharper_jni.so` didn't load, the panel opens normally and says the grammar engine isn't in this build, rather than crashing or hanging on "checking."
- **A fix only touches the words it flagged.** Applying a chip (or "Fix all") replaces the flagged spans and nothing else, so the rest of what you wrote isn't re-typed into the field. That's what keeps formatting alive in apps that have any: bold, colour, a checklist item in Google Keep. The one exception is an editor that won't tell the keyboard where its text starts. There's no way to address a span in one of those, so the fix falls back to rewriting the whole field, which flattens any styling it was carrying.
- **Dismissing an issue is temporary.** A dismissed card comes back if you edit the text again and it re-lints to the same issue. There's no permanent "ignore this" list.
- **"Fix all" only uses each issue's first suggestion.** For an issue with several possible fixes, use its individual fix chips instead if you want a different one applied.
- **A filter hides issues, it doesn't stop the checking.** Harper still lints the whole text; the filter decides what's worth showing you. Turning kinds off doesn't make checking faster.
- **An unfamiliar kind is always shown.** If a future Harper adds a kind of issue this version of the app doesn't know, it appears in the panel (under Engagement's color) rather than being silently swallowed by a filter that can't name it.
- **A LanguageTool error takes the issues' place.** No connection, a refused key or a busy server shows as a line in the panel, unless the next checker on the list could answer. The next edit checks again.
- **Re-check delay only paces this panel.** It has no effect on [the system spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/), which always answers synchronously when another app's text field asks it to.
For contact-aware, dictionary-based red squiggles in every other app on your phone, not only WM Keyboard's own writing surface, see [System spell checker](https://wmkeyboard.pages.dev/smart/spell-checker/). For the everyday typo-fixing that runs on every language and every build, see [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/).
---
# Mixed-language typing
URL: https://wmkeyboard.pages.dev/smart/mixed-language/
Section: Suggestions & correction
Summary: The keyboard reads which language the message in front of you is written in, and leans suggestions and autocorrect that way until you switch back.
Plenty of people type two languages into the same box, on the same keyboard, in the same message. Banglish is the obvious case: "ami tomake" and "how are you" are both typed on a Latin keyboard, and both are ordinary sentences to the person writing them. Left alone, the suggestion engine treats one of those languages as the real one and the other as a stream of typos.
**Detect the language you type** fixes that by reading the words already in the field. Type "ami tomake" on the English keyboard and it starts behaving like the Banglish one. Type "how are you" and it swings straight back, usually within two or three words.
smart/language-detection-switch — The suggestion strip following a message mid-switch: English words, then Banglish ones.
## What it needs to work at all
Detection is on by default, but it is inert unless the language you're typing has other languages to lean toward. That means **Also suggest from** has to name at least one other language on the language's own screen (see [Languages overview](https://wmkeyboard.pages.dev/languages/overview/)).
Romanised languages get that link for free. Enable Banglish (`bn_rom`) alongside English and the two are paired both ways automatically, because a romanised language and its Latin-script neighbour are exactly the pair this feature exists for. A hand-made link you added yourself is never overwritten or duplicated by that pairing.
With only one language in the mix, or with the field still empty, nothing shifts. The keyboard behaves exactly as if the feature didn't exist.
## How the field is read
Every word committed in the field is credited to each language whose dictionary knows it. A word both languages know pushes neither ahead, which is what keeps names and shared loanwords from voting.
Older words fade fast. Each new word decays the running tally to 70% of what it was, so roughly the last three words are in charge and a mid-sentence switch takes effect almost immediately. When you focus a field that already has text in it, the words already there are read in oldest-first, so the same decay leaves the words nearest your cursor in charge.
A run of words nobody recognises (typos, names, a third language entirely) still decays the tally without adding to it, so the mix drifts back to neutral rather than hardening into a guess. Nothing here is written down: the reading is per field, held in memory, and thrown away when you move on.
## Where you are typing
Detection starts every field from nothing, and nothing is exactly what an empty message box has. So the keyboard also keeps a tally, per app, of which of your languages the words you type there belong to, and starts detection in that app's fields from the tally: a new message in a chat app you write Banglish in leans Banglish before you have typed a word, and a new email starts out English.
The lean is deliberately small. It is worth about one typed word of evidence at most, so the first word you type outweighs it and the second settles the matter. A chat app you also write English in is never fought: type an English word and the field is English. Apps with only a few dozen words of history lean less, the tally forgets slowly (roughly the last hundred words), and a word both languages know is credited to both and moves it nowhere, the same rule the field itself uses.
**Remember each app's language** under Typing / Automatic corrections turns this off. The tally is private storage, wiped with the rest of the learned data, and never leaves the device.
## The three strengths
Typing / Automatic corrections / Detection strength
Once the field has clearly gone one way, the detected language gets a ranking boost and its rival takes the same size handicap. **Detection strength** is how big that swing is allowed to get:
| Strength | What it does |
| --- | --- |
| Gentle | Reorders the strip only. The detected language's typo targets never overtake the on-screen language's common words, so autocorrect stays with the keyboard you're on. |
| Balanced (default) | Lets a clearly detected language take the lead in ranking, and autocorrect follows for everything but the most common rival words. The ×4 confidence gate absorbs the contested middle. |
| Aggressive | A full role swap. Autocorrect fixes typos in the detected language even against top-frequency words of the language the keyboard is set to. |
Balanced is the shipped default and the one to leave alone unless something specific is annoying you. Go Gentle if autocorrect keeps reaching into the other language when all you wanted was better suggestions. Go Aggressive if you write whole messages in the language that isn't your keyboard's and you want it treated as the real one.
Whichever strength is set, the swing scales with the evidence. One classified word moves the mix about halfway, three make it certain, and a field split evenly between the two languages moves nothing at all.
## English on Avro and Hindi phonetic
A phonetic layout is the one place the two languages cannot share a keyboard the ordinary way. You type Latin letters and the layout turns them into Bangla, Devanagari or the Urdu script, so an English word has nowhere to go: `hello` comes out হ্যালো, and one English word in a Bangla message means switching layouts twice.
Add English under **Also suggest from** on Bangla's, Hindi's or [Urdu's](https://wmkeyboard.pages.dev/languages/urdu/) own screen (or that of any language with a [phonetic layout](https://wmkeyboard.pages.dev/languages/phonetic/)), and that changes in two steps.
**The strip always carries what you typed.** The word exactly as you typed it, in Latin letters, sits on the last visible chip of the strip. The Bangla or Hindi suggestions keep their usual places in front of it, so nothing you reach for moves. Tap the chip and the word is written in English. (If you would rather the typed word always sat on the left with the phonetic reading beside it, turn on **Keep the first two words in place** on the language's screen; see [Bangla](https://wmkeyboard.pages.dev/languages/bengali/#options).) Once the field has turned English (you tapped that chip, or the text already there is English) the strip leans the same way: the Bangla word stays first, because that is still what the space bar writes, and English completions take the rest.
**Type English words as English** goes one step further, and is off until you turn it on. It sits on the language's own screen, in an **English words** group that appears under Also suggest from once English is picked there (Languages & layouts / Bangla / English words / Type English words as English), and it is set per language. Khipro has a switch of its own in the same group, also off by default, so this one only changes Avro (see [Typing with Khipro](https://wmkeyboard.pages.dev/languages/bengali/#typing-with-khipro)). With it on, the space bar itself writes an English word in English:
| You type | You get | Why |
| --- | --- | --- |
| `kemon` | কেমন | only Bangla knows it |
| `because` | because | only English knows it |
| `hello`, `keyboard` | hello, keyboard | listed as English loanwords, so the spelling is English |
| `to`, `are`, `man` | তো, আরে, মান in a Bangla or empty field; to, are, man once the field is English | both languages have the word, so the field decides |
| `am` after `I`, `are` after `how` | am, are | the word before counts most: a pair English writes settles a close call, and so does a pair Bangla writes |
| `I` on its own | I | on Avro a lone capital I spells the letter ঈ, which is never a word |
| a name neither list has | the layout's own script | unless the last couple of words were English |
The text in the field shows the same answer while you are still typing, so `hell` already reads hell and nothing changes under you at the space. An English word at the start of a sentence gets its capital if the field asks for one, since a script with no capitals never arms shift for you. A capital letter anywhere after the first always means Avro, because `T`, `D`, `N` and `O` are letters of their own there.
What counts as "the field is English" is the same reading described at the top of this page, with one difference: a listed loanword like `ok` or `phone` counts for both languages, since it is as much Banglish as English. Writing `ok` does not turn the তো after it into "to".
When the keyboard picks the wrong script for a word both languages have:
- Press backspace straight after the word and it flips to the other script, the same way backspace takes back an autocorrection. It follows the same **Undo autocorrect with backspace** setting.
- Or tap the other script's word on the strip, before or after the space. The undo chip offers it after the space.
- Either way the keyboard remembers the spelling. After two flips it stops weighing that spelling at all and writes it your way, until you flip it back.
The **English words** tool puts the switch on the toolbar, lit while it is on. It is there for the moment a Bangla word keeps coming out in English: tap it mid-word and the word in the field turns back before you press space. It switches the language you are typing at that moment. Tapped on a layout that is not phonetic, or with English missing from Also suggest from, it says so and changes nothing.
The same switch also sits on the suggestion strip itself, as a small button beside the candidates, a size down from the toolbar's buttons and lit the same way. That is where it is needed: a word coming out in the wrong script is something you notice while it is being typed, which is when the suggestions have taken the row and the toolbar is behind the chevron. It shows on a phonetic layout with English picked under **Also suggest from** for that language, and goes away on every other layout. Put the **English words** tool on a toolbar row of its own and the strip drops its copy, rather than showing you the same button twice.
The strip's button is pinned, so it cannot be dragged off the way a tool can. **Switch on the suggestion strip**, the second row of the same group, takes it away instead. English words then stay the way you set them, with nothing on the keyboard to turn them off by accident.
After an English word, the next-word predictions come from English too (your own pairs, the bundled ones, and the English word-pair pack if you downloaded it). Which language each app's fields start out leaning towards is the same per-app tally described above. Glide typing on these layouts still decodes Bangla or Hindi only.
## Bangla, Hindi or Marathi on the English keyboard
The other way round works too. Pick a language that has a [phonetic layout](https://wmkeyboard.pages.dev/languages/phonetic/) (Bangla, Hindi, Marathi, Urdu, Tamil and the rest) under **Also suggest from** on English's own screen, and the English keyboard reads each word you type through that language's phonetic rules as well. Its readings show up on the strip in its own script: type `marathi` and मराठी is one tap away, with no layout switch.
Where a reading lands depends on who knows the word:
| You type | The strip | Why |
| --- | --- | --- |
| `kemon` | কেমন right after the first chip | only Bangla knows it |
| `because` | English only | only English knows it |
| `to`, `hello` | English first, তো or হ্যালো on the last visible chip | both languages have it |
| a word neither list has | its reading on the last visible chip | unless English still has words that start with it |
The first chip never moves, and the space bar still writes what you typed in English letters; a reading goes in only when you tap it. A word you pick this way counts for its own language when the keyboard reads which language the field is in, so after a few Marathi words the readings of a word both languages know get weighed toward Marathi too.
How good the readings are depends on that language's word list. With the list downloaded (from the language's own screen) the keyboard offers real words; without it only the phonetic rules and the bundled spellings are there to go on. A language only picked under Also suggest from still gets its word list and spellings read in, whether or not its phonetic layout is on.
## Options
Typing / Automatic corrections
| Setting | Default | Shown when |
| --- | --- | --- |
| Detect the language you type | On | Autocorrect on |
| Detection strength | Balanced | Autocorrect on, and Detect the language you type on |
| Remember each app's language | On | Autocorrect on, and Detect the language you type on |
Both rows sit under Automatic corrections rather than Suggestions, because detection moves autocorrect's targets as well as the strip's order. That also means both are hidden by the **Autocorrect** master switch: turn autocorrect off and the two rows disappear from the screen. The switch you set stays set, though, so detection carries on reordering the strip. It's only autocorrect's half of the effect that goes away, and that goes away with autocorrect anyway. See [Typing settings](https://wmkeyboard.pages.dev/reference/settings/typing/#automatic-corrections) for the rest of that group.
## Details & edge cases
- **It is per field, and per field only.** Switching apps, or tapping into a different box, starts the reading from scratch. There is no memory of "you write Banglish to this person". That's a different feature, the long-run per-language weighting described under [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/#learning-from-what-you-type).
- **Detection never changes the keyboard.** The layout, the language name on the spacebar and the script you're typing all stay exactly where you put them. Only ranking and autocorrect targets move. To switch language, see [Switching languages](https://wmkeyboard.pages.dev/languages/switching/).
- **A language has to clearly lead before it counts as the field's language.** For the learned-word weighting in particular, the detected language needs about a third more share than the language you're on before it takes over. A field with no clear leader stays with the keyboard's own.
- **It's a different question from "which languages do you use".** The long-run mix moves over weeks and would never notice that this message switched to Banglish three words ago. This one is the fast signal, and the two run side by side.
---
# One-time codes
URL: https://wmkeyboard.pages.dev/smart/one-time-codes/
Section: Suggestions & correction
Summary: A verification code arrives in a notification, and a chip above the keys types it. Off by default, and it needs notification access.
Typing a verification code normally means pulling down the shade, reading six digits, going back to the app and hoping you remembered them. WM Keyboard can skip that: when a code arrives in a notification, a chip appears above the keys with the code and the name of the app that sent it, and one tap types it.
This is off by default and it needs notification access, which is a broad grant. The rest of this page is what the keyboard does with it, and what it deliberately doesn't.
## Turning it on
Typing / One-time codes / Codes from notifications
Turn the switch on. A plain-language disclosure explains what the feature reads and why. The system's notification-listener screen opens next, where you grant the access. If you back out without granting it, an **Allow notification access** row appears under the switch as the way back. That row disappears once access is there.
The grant is the same notification-listener permission the [media control tool](https://wmkeyboard.pages.dev/tools/media/) uses, but the two features are asked for separately, because they read very different things. Media control reads no notification content at all; the code chip reads notification text to find a code. Granting one doesn't turn the other on.
Turning **Codes from notifications** off stops the keyboard reading notifications immediately, and drops any code it was holding at the same time.
## What gets read, and what's kept
While the feature is on, the keyboard looks at each incoming notification's text: title, body, big text, the newest message of a conversation, inbox lines, sub-text, summary and ticker text, up to the first 1,000 characters. It skips its own notifications and anything ongoing (a media session, a download, a foreground service), since none of those carry codes.
A code can arrive while the keyboard isn't listening, for instance when Android has closed it to save memory. So when notification access connects, and when you open a field with no code held, it also looks through notifications posted in the last 10 minutes, newest first, and stops at the first code.
If a code is found, it goes into a single in-memory slot: the code, the posting app's name, the notification's key, and a capped copy of the message for context. Nothing is written to disk, nothing is sent anywhere, and a newer code replaces an older one rather than queueing behind it. Two codes in flight means the first one was abandoned, and offering a stale code is worse than offering none.
## What counts as a code
The detector is deliberately hard to trigger. There is no "any six digits" rule, because delivery slots, order numbers and flight times all arrive as notifications too. Every pattern is anchored to a word that promises a code is next: `otp`, `passcode`, `verification`, `code`, `pin` on their own, and `verification code`, `security code`, `login code`, `sign-in code`, `2fa code` and similar pairings.
Four anchored shapes run in precision order, so a message with several numbers gives up the right one:
- `482913 is your Amazon OTP`
- `Your OTP is 482913`, `code: 482913`, `PIN - 4829`
- `OTP for account 2310990533 is 4279`, where the code follows the "is" rather than the account number
- `use 482913 to sign in`, `enter 4829 to verify`
Chinese, Japanese and Korean messages have their own code words: 验证码, 校验码, 动态码, 动态密码 and 口令 among others (traditional forms too), 認証コード and 確認コード, 인증번호 and 인증코드. A code right after one of them counts, with a few joining characters allowed in between, so `当前验证码为 467321`, `認証コードは123456です` and `인증번호 [482913]` all give up the code.
### Your own code words
Typing / One-time codes / Your own code words
When a sender's wording isn't on any list, type it here, commas between: `取件码, 暗号`. A number of 4 to 8 characters within about 40 characters of one of your words then counts as a code, through the same proximity pass and guards as the built-in words, except the deny list, since you chose the word yourself. Your words also apply to the codes the clipboard picks out of copied text. Empty by default.
If none of those bites, the [clipboard detector](https://wmkeyboard.pages.dev/tools/clipboard/)'s keyword-proximity pass runs as a recall net, which is also what catches alphanumeric codes like `WX8Q2P`.
A few guards apply throughout. A code has to be 4 to 8 characters. A bare four-digit value between 1900 and 2099 is rejected, because that's a year to a person and a code to a regex. The proximity pass leaves amounts alone, so `1500.00` in `OTP for Rs 1500.00 is 482913` isn't offered. Google's `G-` prefix is stripped as branding rather than treated as part of the code. And a deny list on the keyword itself stops "zip code 90210" and friends from ever qualifying.
## The chip
The chip is drawn with a dashed outline, the same visual language the clipboard fragment chips use: this is something lifted out of somewhere else, not text you typed. It shows a small **Code** badge, the code itself, and the posting app's name, so you can see whose code you're about to trust before you trust it. The ✕ on the right dismisses it, which drops the code everywhere rather than only hiding the chip.
It ranks high in the strip, above the clipboard and snippet chips, because the code is usually the whole reason you're in that field and it expires while everything else can wait. Only the [password-manager lane](https://wmkeyboard.pages.dev/smart/autofill/) beats it, since the platform may be offering the same code with more context.
The chip hides itself, without dropping the code, when the current field doesn't qualify. One case does that: **Only in code boxes** is on and this field doesn't read as one. [Incognito mode](https://wmkeyboard.pages.dev/privacy/incognito/) doesn't hide it: reading a code out of a notification records nothing, and a login in a private tab is where the code is wanted. It hides rather than drops, because you're often one tap away from the box the code belongs in.
A field counts as a code box when it asks for a number keypad, or when the app named it one: the hint it shows while empty, its accessibility label or its resource id saying `OTP`, `PIN`, `passcode` or `verification code`. The names that only look like codes don't count: a ZIP code, a country code, a promo code and a coupon code are all left alone.
## Typing into a row of boxes
Plenty of apps and websites split a code across a row of single-character boxes that advance the focus for you. A code committed whole lands entirely in the first of them, which keeps one character and drops the rest.
**Type codes one character at a time** (on by default) handles that. The keyboard types the first character and watches what the field does with it. If the focus moves on to a next box, each later character waits for its box to take over before it is sent, however long the form takes, so none lands in a box that is already closing. If the focus stays put, it is one ordinary field, and the rest of the code goes in at once. While a code is going in, the keyboard doesn't treat the cursor landing after each character as you going back to edit a word, so a later character can't replace the ones before it.
This setting sits outside the rest of the group on purpose. It governs every path that types a code, including a code tapped from a [clipboard chip](https://wmkeyboard.pages.dev/smart/chips/#pulling-fragments-out-of-your-clipboard), which needs no notification access at all.
## Options
Typing / One-time codes
| Setting | Default | Shown when |
| --- | --- | --- |
| Codes from notifications | Off | always |
| Allow notification access | Action row | Codes from notifications on, and access not granted yet |
| Only in code boxes | Off | Codes from notifications on |
| Offer a code for | 3 min (range 1–10 min) | Codes from notifications on |
| Clear the notification | Off | Codes from notifications on |
| Your own code words | Empty | Codes from notifications on |
| Type codes one character at a time | On | always |
**Only in code boxes** narrows the chip to the fields described above. It's off by default because some apps give their code box neither a number keypad nor a telling name, and there is no way to recognise those. A code that never appears where you need it is worse than one offered in a field you weren't going to use it in. Turn it on if you'd rather never see a code offered mid-sentence.
**Offer a code for** is measured from when the notification was posted, not from when you saw it. Past that, the code is dropped rather than hidden.
**Clear the notification** dismisses the code's notification once the chip has typed it, using the same access the capture does. A few apps re-post their code after a dismissal, which is the reason it's off by default.
## When no chip comes
Check these in order:
1. **Codes from notifications** is off by default, so turn it on, and allow notification access when asked.
2. The notification has to carry the code. An app that says only "you have a new message", or a phone set to hide notification content, leaves nothing to read.
3. Android 15 and later can hide codes in notifications from apps like this keyboard, showing it "Sensitive notification content hidden" instead. When that happens the settings row says **Android hid a code from the keyboard**. The keyboard can't get around it. Some phones have a switch for it in Developer options. Otherwise copy the code from the notification, and the strip offers it from the clipboard.
## Details & edge cases
- **The code never leaves the device and is never stored.** It lives in memory until it's used, dismissed, replaced by a newer code, or ages out. There's no history of past codes anywhere in the app.
- **A code you used or closed doesn't come back.** Some apps re-post their notification when another message arrives or the group re-sorts, which looks like a new notification. The keyboard remembers which codes from which app it has already offered, so the same code from the same app stays gone, and the expiry counts from when the code first arrived rather than from the latest re-post. That memory goes when Android closes the keyboard, so a used code whose notification is still showing can come back until it expires. **Clear the notification** prevents that.
- **The clipboard has its own, separate code path.** Copying a verification text offers a **Code** chip too, covered under [Smart chips](https://wmkeyboard.pages.dev/smart/chips/#pulling-fragments-out-of-your-clipboard). It needs no notification access, and it has its own setting under the Clipboard tool. Pasting a whole clip that is nothing but a code goes in character by character as well, on the same reasoning.
- **Turning the feature off is immediate, not on next launch.** The flag the notification listener reads is flipped at the same moment as the setting, and any captured code is dropped with it.
- **Nothing runs before your first unlock.** Between a restart and the first time you enter your device credential, the feature behaves as off whatever the switch says. The lock screen still redacts notification content at that point, and a chip printing a code out of one would put exactly that on the keyboard.
---
# Personal dictionary
URL: https://wmkeyboard.pages.dev/smart/personal-dictionary/
Section: Suggestions & correction
Summary: Words the keyboard learns from you, and full control over them.
WM Keyboard quietly builds a private, on-device vocabulary out of what you type: names, slang, abbreviations, anything the bundled dictionary doesn't know. Every word in it is safe from autocorrect. A word nothing recognises has to be typed a few times before it gets in, so one bad swipe doesn't put a misspelling in your dictionary for good. You can review that vocabulary, add to it or remove from it by hand, and wipe it entirely, all from Settings.
smart/personal-dictionary-list — The Personal dictionary screen: learned and manually-added words, most-used first.
## What gets learned, and how strongly
A word only joins your personal dictionary if every one of these holds at the moment you commit it:
- **"Learn from typing" is on.** It is by default.
- **Incognito isn't currently pausing learning.** Incognito goes active for one of two reasons. Either you switched it on yourself, or the focused field asked not to be learned from (Chrome's incognito tabs, private-browsing fields elsewhere) and "Follow private browsing" let that request through, which it does by default. Whether active incognito pauses learning is a separate switch, "Pause learning," and it's on by default too. That one doesn't live on the Privacy screen with the rest of these. It's under the Incognito tool's own settings (Tools / Incognito / While incognito).
- **The field allows it.** Password fields and PIN entry are excluded, and so is every structured field (email, web address, number, phone, date, time, datetime), deliberately independent of whether the app is also hiding the suggestion strip. Only plain text fields qualify.
Within a plain text field, how hard a word is taught depends on how it was committed:
- **Tapped from the suggestion strip.** Twice the weight of a plain commit, since choosing a candidate deliberately counts for more than typing it out.
- **Typed and committed normally.** The default weight.
- **Autocorrected into place.** The corrected word itself is not added, because an autocorrect target earns no credit for being typed. The word pair around it is still recorded for next-word prediction either way.
- **Backspaced right after an autocorrect fires.** Restoring your original spelling counts double towards learning that spelling, because deleting a fix is a clear "I meant what I typed." It no longer saves the word outright, since that was the fastest way for a stray swipe to get nailed into the dictionary forever. Nothing is lost by waiting: that exact correction is already blocked from firing again the moment you undo it. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/#undoing-a-correction) for the rest of that flow.
A word needs at least two letters to be auto-learned this way. Adding one yourself (see below) has no such floor.
### Languages written without spaces
Thai, Lao, Khmer and Burmese put no space between words, so what you type between two spaces is usually a phrase. The keyboard finds the words inside it and learns each one, with the word pairs between them, the same as it would for words you had spaced out. A run the keyboard already knows as a single word stays whole. The words before the cursor are read the same way, so next-word suggestions follow the last word you wrote rather than the whole line. Finding words in a run takes a moment longer than splitting at spaces, so for context the keyboard reads only the last 32 characters of a long run.
## Words wait their turn
Nothing you type is written to your dictionary while you're still typing it. Every committed word is held first, and only counted once the text it landed in has stopped moving. That's what gives you a chance to read back what you wrote: a word you fix, delete or re-pick was never learned in the first place. Only what you do to a word counts against it. Backspacing through it, sweeping it away with a delete swipe or typing a fix over it drops it; putting the cursor on it, or walking the cursor back through it with a spacebar swipe, leaves it waiting, and it is counted with the rest when you move on.
What differs between words is how many settled uses they need.
A word something already recognises, whether that's the bundled dictionary, a wordlist you imported, a contact name, an app name, or your own dictionary, needs **one**. There was never anything in doubt about how it's spelled.
A word nothing recognises has to be typed **three times** before it joins your dictionary, and each of those has to settle. Change that count with **Remember a new word after** under Typing / Suggestions, anywhere from 1 up to 10. A sighting that's undoing an autocorrect counts double, so one undo plus one ordinary use is enough at the default.
Settling is what stops your slips being counted at all. A word you've just typed isn't evidence of anything yet, because half the time you're about to fix it, and a glide gives you the wrong real word often enough that counting it on the spot slowly turned the personal dictionary into a copy of the wordlist. So a word is held, unwritten, until one of these happens:
- You close the keyboard.
- You send or clear the field.
- You move to a different field.
- You keep typing far enough past it (about 500 words) that you're clearly not coming back.
If you go back into that text before any of that, the word you change is dropped and counts for nothing: backspacing over it, deleting it with a delete swipe, or typing a fix on top of it. The word you merely land in is not decided by that alone. Tapping into it, or walking the cursor back through it with the spacebar, leaves it waiting, and it settles with the rest once you move on, so proofreading a sentence does not unlearn it and a word the cursor happens to sit on when you close the keyboard is still counted. The rest of the text you moved past is text you left alone, so tapping up to fix the first line of a note settles everything below it rather than throwing the whole session away. Re-picking a swiped word from the strip drops it too, because you just overruled the guess. So does deleting the word from your dictionary while it's still waiting.
Two things skip the queue, because in both you've told the keyboard directly rather than by leaving text alone: tapping the `Add "yourword"?` chip, and **Add word** from the word menu or the Personal dictionary screen. Either way the word goes in once: the space you type straight afterwards finishes the same word you just added, so it isn't counted a second time on top.
A word nothing recognises is invisible while it waits. It's never suggested, never shields itself from autocorrect, and never shows up on the Personal dictionary screen until it graduates. Sightings also go stale on their own, so the same slip made in March and again in September never adds up to a word. (A recognised word waiting its one turn is a different matter: it was already in a dictionary, so it's suggested and safe from autocorrect the whole time. All that's waiting is your own count of it.)
If you'd like to see which words are still waiting, set **New word color** under Appearance / Toolbar. The strip then paints a word that nothing recognises in that colour while you type it, and stops the moment it's learned or you add it, so a word the dictionary still has to learn is told apart from one it already knows.
### Being asked instead
If you'd rather decide each one yourself, turn on **Ask before saving new words** (Typing / Suggestions). Nothing is counted and nothing is saved. Instead, the moment you commit a word nothing recognises, the suggestion strip offers a chip reading `Add "yourword"?`.
- **Tap the chip** and the word goes in right away, exactly as though you'd added it by hand.
- **Tap the ✕** and the keyboard stops asking about that word, permanently.
- **Ignore it** and it goes away by itself as soon as you type the next word or press backspace. Nothing is saved.
The chip shares the strip with your word candidates rather than covering them, so it never gets in the way of finishing the sentence. Only one ask-first chip shows at a time: a snippet offer takes priority over this one.
## From the suggestion strip
Press and hold any word on the suggestion strip and a small menu opens about it. Its items depend on the word and on what you are typing:
- **Never suggest** puts the word on the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/). For a word already on it, the item reads **Suggest again** instead.
- **Add "yourword"** appears while the word you are working on is not in your personal dictionary. It is about the word under the cursor, not the word you held: the suggestions are known words already, and the one you want in is the one you are writing. That covers the word you are typing right now and, with nothing being typed, the word the cursor is parked inside, which is when you usually spot the spelling your dictionary is missing. The word goes in spelled exactly as you typed it, counted as one use, and its capitals are kept (see [capitals](#capitals) below). If it was on the blacklist, it comes off.
- **Delete "word"** appears for a word the keyboard can forget: one in your personal dictionary, one still waiting to be learned, one in Android's own dictionary, one you adjusted the rank of, or one in a word list you imported. It is forgotten from all of those. A word in an imported list is taken out of the list's file itself, so it does not come back with "Suggest again". If the word is also in a downloaded word list, which the keyboard cannot edit, it goes on the blacklist too, so it is gone from the strip either way. **Delete edits imported lists** under Typing / Suggestions is the switch: turn it off and imported lists are left as you imported them, and the word is blacklisted instead.
- **Forget what you learned about "word"** appears beside Delete for a word that is in a word list and that the keyboard has also learned something about: how often you use it, its capitals, a rank you set, a swipe shape. That learning goes and the word stays, offered the way the lists offer it, so a word that picked up a capital it shouldn't have ("Toolbar") comes back in lower case without being blacklisted. It shows under the same **Delete** chip of **Word menu**.
- **Search all words for "word"** appears when the cursor sits inside a word a swipe wrote and that swipe's path is still kept. Like **Add**, it is about the word under the cursor rather than the one you held. It reads the stroke again against every word list you have, past whatever limit glide typing normally works under, and fills the strip with the answers, which is the only way back to a word the decoder was never allowed to offer. See [Going back to a misread swipe](https://wmkeyboard.pages.dev/typing/glide-typing/#going-back-to-a-misread-swipe).
- **Synonyms for "word"** lists words with the same meaning as the word you held, grouped by part of speech and, where the source gives one, by meaning. Tap one and it goes in place of the word, exactly as if you had picked it off the strip: it replaces the word you are typing, the word the cursor is in, or the word a swipe just wrote, and it takes that word's capitals, so "Happy" at the start of a sentence becomes "Glad". Held on a next-word prediction, it types the synonym as the next word. It appears for English words, on a keyboard with English among its languages. See [Synonyms](#synonyms) below for where the words come from.
- **Suggest "word" higher** and **Suggest "word" lower** move the word one rank step up or down, the same steps the [slide](#sliding-a-held-word) and the word card set, without the gesture. Not offered for a word on the blacklist.
- **Edit** is always there. It opens the word card.
### Sliding a held word
Keep your finger down after the menu opens and slide it instead of letting go. The menu closes and a badge over the strip says what will happen when you lift:
- **Up** lifts the word one rank step for every 24 dp (about 4 mm) you slide, and **down** sinks it the same way. The steps are the word card's own rank, from −10 to +10, counted from wherever the word already sits, so the badge reads `"form" rank +2`. Slide back to where you started and it reads **unchanged**, and lifting does nothing.
- **Six steps down** (about 2.5 cm) is the boundary. The badge turns red and reads **Never suggest "form"**, and lifting there puts the word on the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), exactly as the menu item does. **Suggest again** in the menu, or the blacklist screen, takes it back off.
A word that is already on the blacklist doesn't slide: its menu stays up, and **Suggest again** there is how it comes back.
The rank is stored in the same place the word card keeps it, so the card and the [personal dictionary screen](#adding-editing-and-removing-words) show it, and setting the card back to 0 undoes a slide. Nothing changes unless the finger moved a full step after the hold, so a hold you let go of in place still just opens the menu, and a quick swipe along the strip is still a swipe.
Put the cursor inside a word and that word is itself the top candidate on the strip, so the menu for it is one hold away rather than a hunt for a chip that happens to be showing. Tapping that chip does nothing but put the strip away, since it would only replace the word with itself. See [When the cursor lands in a word](https://wmkeyboard.pages.dev/smart/suggestions/#when-the-cursor-lands-in-a-word).
### Synonyms
The synonyms come from a list of sources, which **Synonym sources** under Typing / Suggestions puts in order. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag a source to move it, switch it off, remove it, or add it back. The keyboard asks the first source that is on. When that source has no synonyms for the word, or cannot be reached, it asks the next one, so one service being down or not knowing a word does not leave you with nothing. The panel says at the bottom which source answered.
| Source | Where | What it knows |
|---|---|---|
| Vocabulary packs | on the phone | Wiktionary's synonyms for the words in the [vocabulary packs](https://wmkeyboard.pages.dev/tools/vocabulary/) you downloaded, grouped by meaning. Nothing is sent anywhere |
| Datamuse thesaurus | `api.datamuse.com` | most English words, ranked by how well each one stands in for yours |
| Wiktionary | `kaikki.org` | Wiktionary's full entry for the word, grouped by meaning |
| Free Dictionary API | `api.dictionaryapi.dev` | the source the [Dictionary](https://wmkeyboard.pages.dev/tools/search/) tool uses |
| Similar words | `api.datamuse.com` | words with a related meaning, for a word no thesaurus lists synonyms for. The panel says so when these are what you get |
All five are on by default, in that order. Drag a source to move it and turn its switch off to never ask it. An online source receives the word you held and nothing else. A look-up happens only when you tap **Synonyms**, and [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) does not hold it back, the same as a vocabulary look-up. The keyboard remembers the last few answers, so opening the same word again asks nobody.
The word card is a panel over the keyboard, and it is everything the dictionary screen can do to one word without leaving the keyboard. It lists everywhere the word was found: your personal dictionary with how often you typed it, the waiting room, each word list that has it with its place in that list ("#4,512 of 300,000"), an imported list, Android's dictionary, a contact, an app, or the blacklist. Under that come the word's own controls, and along the bottom the same actions as the menu, so a menu trimmed down in settings still reaches all of them.
The card is also one tap away from the text itself. With [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) on, select a word and tap **Edit word** on the bar. The card opens for the selected word, and its **Add** adds that word while your dictionary does not have it yet. See [Look up](https://wmkeyboard.pages.dev/smart/selection-actions/#look-up).
**Spelling** is the first control: the word as it is spelled now, a **Change** button, and a **Keep these capitals** switch.
Pressing **Change** hands the keys to a bar in the strip's own row, since the card itself covers the keyboard and there is nothing under it to type with. The bar starts from the word as it stands and edits like a small text box:
- **Tap** in it to put the caret there, **drag** across letters to select them, or **hold** to select the whole word.
- **Swipe along the spacebar** to move the caret. With shift up or selection mode on, the swipe drags out a selection instead. A hardware keyboard's arrows, Home and End work too.
- **Press shift over a selection** to cycle it through lower case, Title case and UPPER case, the same as in a text field.
- **Glide a word** and it goes in at the caret, replacing any selection, with no space after it.
Backspace deletes, the ✓ (or the Enter key) applies it, and the ✕ (or the back gesture) leaves the word alone. Nothing you type there reaches the app you are writing in.
Applying a respelling moves everything the keyboard keeps about the word onto the new spelling: how often you typed it, the language it was last typed in, the word pairs it takes part in, and its rank adjustment. Change nothing but a capital and it is not a move at all, because every list keys the word the same way. It settles the spelling instead and pins it. A word your personal dictionary does not have, one from a downloaded list, has nothing to move: the new spelling is added by hand, which is what puts it in front of the list's own.
**Keep these capitals** is the same switch as the dictionary screen's, for a word the keyboard has already learned; see [capitals](#capitals) below.
Then the rank controls. Which of them the card shows is a setting, **Adjust rank changes** under Typing / Suggestions, set to **Both** by default:
- **Learned weight** edits the same count the dictionary screen shows, with the same minus and plus buttons stepping a decade at a time. Lowering it to "Not learned" forgets the word. Raising it on a word the keyboard has not learned adds the word at that weight. It cannot push a word below a word list's own order, since the count only ever adds.
- **Rank adjustment** keeps a separate up-or-down setting for the word, from −10 to +10, that the keyboard applies on top of every list. Any word can move either way, a word from a downloaded list included, and what the keyboard learned is untouched. Adjustments show up in a **Rank adjustments** group at the bottom of the dictionary screen. Tap one to type or step its value, or use its button to reset it. They never affect autocorrect, only the order of the strip.
Which items the menu shows is up to you. **Word menu** under Typing / Suggestions is a row of chips for **Never suggest**, **Add**, **Delete**, **Synonyms** and **Suggest higher or lower**; turn one off and it stays off the menu. **Edit** cannot be turned off, because the card carries the other three anyway, and neither can **Search all words**, which only appears where there is a stroke to search at all.
## Capitals
The dictionary keeps one spelling per word, and normally votes on it: type "Boston" with your own capital a few times and the word is offered as "Boston" from then on; type it in lower case often enough and the capital wears off again. The vote runs both ways. A word you have written in lower case for a long time has that standing behind it, up to the same cap the capital gets, so one "Keyboard" typed with Shift does not turn every later suggestion into "Keyboard"; it takes as many capitalised uses as the lower case had, at most eight. A word from one of your wordlists counts as written in lower case from the start, even before your personal dictionary has it: gliding "The" through Shift once adds "the", not "The", and it takes eight capitalised uses to turn it. Only a word the keyboard has never seen anywhere takes its capital on the first sighting, since there is nothing to outvote. A capital at the start of a sentence never counts, whether auto-capitalise put it there or you pressed Shift for it yourself, and neither does caps lock. So writing with automatic capitals off doesn't teach every word you open a sentence with as a name.
Only a capital you typed is a vote. A swipe types no capital at all: your finger draws letters and the keyboard decides how to spell what it read. So a swiped word never votes on its own spelling, however it comes out. Neither does tapping a suggestion that is capitalised when what you typed was not: the chip is spelled the way the dictionary spells it, and choosing it is choosing the word, not the capital. To teach a capital, type it: press Shift, or glide through the Shift key (see [Glide typing](https://wmkeyboard.pages.dev/typing/glide-typing/#capitalizing-without-leaving-the-stroke)).
This is what stops a name you write both ways from getting stuck. Before, a swipe that came back as "Mark" was learned as "Mark", which made the next swipe come back as "Mark" more strongly still, and "mark" could never be written plainly again.
A word you add yourself is different. Add it by hand, from the dictionary screen or from the strip's **Add** item, or respell it in the dictionary screen's edit dialog or on the word card. Either way its capitals are kept: no amount of later typing changes them. The row reads "capitals kept", and both the edit dialog and the word card have a **Keep these capitals** switch to turn that off, or to turn it on for a word the keyboard learned on its own. Keeping "you" in lower case this way is how to stop it ever being offered as "You" after a sentence start. It also wins over a capital from a word list or Android's personal dictionary, so respelling "Ok" as "ok" keeps it small everywhere, glide included.
A kept capital is also how the word is written when you type it in small letters and press space, with autocorrect on: add "Wasi" and typing "wasi" writes "Wasi". A single-word entry with capitals in Android's dictionary works the same way. The one exception is a word your word lists also write in small letters: adding the name "Will" does not capitalise every "will" you type, so for such a word the capital stays a suggestion. Backspace right after puts back what you typed, like any correction.
## Why a learned word matters
Once a word is in your dictionary, it's exempt from autocorrect. The correction engine treats anything in your personal dictionary the same as a word from the bundled dictionary, and never "fixes" it away. It also carries a heavy weight in suggestion ranking, so it competes with common words in the strip almost immediately rather than needing to be typed dozens of times first. See [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) and [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/) for how those two mechanisms work in full.
That protection is why a new word waits. It also means a second threshold sits behind it. **Learn a word after** under Typing / Suggestions runs from Once up to 5, and asks for that many reinforcements of a word already in your dictionary before it's shielded. Ranking is unaffected either way, so a word below that threshold is still offered on the strip, but it isn't shielded from correction. With **Remember a new word after** doing most of the work now, most people can leave this one at Once.
Alongside single words, the same learning pass builds word-to-word pairs, so typing "good" after "have a" can predict "morning". It builds three-word contexts as well, which win over the two-word ones when they match. It builds word-to-emoji pairs too, so a phrase that's usually followed by 🎉 can offer it. Pairs wait in the word cache with the words they are made of, and are learned from the text as it settled, so a word you fixed or re-picked never leaves a pair behind. Every word that ended up in the text counts toward them, one-letter words like "I" and "a" included, and so does a word autocorrect wrote for you: it is not counted as a word you typed, but the pairs on either side of it are real, and leaving it out would join its two neighbours into a pair you never wrote. They are only built out of words the keyboard recognises, at both ends, so a habit is never hung off a possible typo and a misspelling can't come back as a next-word suggestion. A separate, bundled set of generic English word pairs gives next-word prediction something to work with before you have any typing history of your own. None of those pairs are part of your personal dictionary. They aren't stored in it, don't show up on this screen, and can't be edited or cleared, and your own learned pairs always outrank the bundled ones once you have some.
## Adding, editing and removing words
Typing / Suggestions / Personal dictionary
Every word you've typed or added sits in one list, with the highest weight first until you select another order. Once the list passes a dozen entries, a search field appears above it, and the list itself draws 100 rows at a time with a **Show N more words** row to reveal the rest, so a dictionary of thousands of words never composes all at once. Each row starts with the language the word is filed under, then reads "Seen N times"; a word you put in yourself reads "You added this word · Seen N times", so it shows its count like any other.
**Sort by** above the list sets the order: **Weight**, **Alphabetical** or **Date added**. The button under it says which way the list runs, and pressing it reverses that: highest or lowest weight first, A to Z or Z to A, newest or oldest first. Both alphabetical orders keep words that start with a capital letter at the top, so names sit together. Words with the same weight or the same date fall back to A to Z. The screen remembers the order for your next visit.
**Date added** is when a word joined your dictionary, not when you last typed it, so a word you type every day stays where it joined. That makes **Newest first** the quick way to review what the keyboard picked up recently. Words saved by a version from before this order existed have no date of their own, so they sort by when you last typed them instead. Words learned in the same typing session share a date and sit A to Z among themselves.
### Sorted by language
Every word the keyboard learns is filed under the language you were typing when it learned it, and moves to another language if you later type it there more recently. Once your words span more than one language, a row of chips appears above the list: **All words**, then one chip per language with how many words it holds, then **All languages** for words that belong to none in particular. Pick a chip to see only that language's words; search and the sort order work inside it. Languages you have turned on come first, in the order you set them; any other language the list still holds follows.
**All languages** is where a word you added by hand goes unless you choose otherwise, and where words saved by an older version without a language end up. Nothing guesses a language for them, because a name like "Wasimaster" is equally at home in English and Banglish.
To move a word, press its row and pick a language under **Language** in the edit dialog, or **All languages** to file it under none. A word you move stays where you put it: typing it later in another language no longer moves it, as it would a word you never filed. The language matters to suggestions in one way. A word filed under one language, that no dictionary knows, is offered a little less eagerly while you type another, and a word under **All languages** is offered the same everywhere. Moving a word changes only that; it does not delete the word, its weight or its word pairs.

**Add word** opens a dialog with the word, an optional **Shortcut** and its **Language**, **All languages** unless you pick one of yours. Confirm and the word goes in at a weight of one, or one more than it already had, marked as added by hand. Adding a word says it is a word, not that it is a frequent one, so it earns weight the way every other word does, by being typed. What the mark buys is that the word is immediately safe from autocorrect whatever the **Learn a word after** threshold says, and that it is the last thing compaction (see below) would ever remove. A trash icon on each row removes that word, and drops it out of any word pairs it was part of. A shortcut works the way it does in Android's own dictionary: type `asaik` and "as far as I know" is offered on the strip, which also helps with a word that is hard to glide, such as one with an apostrophe. It's kept as a [snippet](https://wmkeyboard.pages.dev/tools/snippets/) in a folder called **Personal dictionary**, where you can change it, turn the whole folder off, or delete it.
**Press any row to edit it.** The dialog that opens holds the spelling, the weight, a **Keep these capitals** switch and the word's **Language**, and you can change any of them. Fix the spelling and the word keeps everything it had: its weight, the language it was learned under, and every word pair and triple it was part of, so "have a good" still predicts the corrected word afterwards. Respell a word onto one that's already in the list and the two merge into one entry. The weight is the same number the row reads out as "Seen N times", typed straight in or nudged with the minus and plus buttons beside it, which step by one below 10, by ten below 100, and so on up a decade at a time. Anything from 1 to 1,000,000 is accepted. Raising a weight makes the word win the strip sooner; lowering it does the reverse, and a word taken down to 1 is back in reach of **Remove one-off words** unless you added it by hand. Weight has no effect on autocorrect protection: every word on this list is shielded whatever its number, subject only to the **Learn a word after** threshold.
**Remove one-off words** appears next to it whenever the list holds anything that's only been seen once, and clears the lot in one go after a confirmation that tells you how many there are. Older versions saved every new word the first time it was committed, so on an upgraded install this is where the stray swipes and mistyped words are. Words you added by hand are never in there, whatever their weight.
**Remove words the lists know** appears beside it when some of your words are already in a word list: the downloaded or bundled list of the word's language, or one you imported. A word with no language counts when any of your languages' lists hold it, and a language set to read your own lists alone is checked against those only. After a confirmation with the count, those words go in one step. The keyboard still knows them from the list, but it forgets how often you typed each one, so they rank as the list ranks them. A word whose capitals you pinned goes only if the list writes it the same way, so `DNA` goes where the list says DNA and stays where it says dna. The rank changes you made from the word card (the list at the foot of this screen) are not touched.
**Pick words to delete** opens a checklist of the words the list shows right now, so a search or a language chip above narrows it first. Nothing is ticked to start with: tick the words that should go, or tap **Select all** and untick the ones to keep. **Only words no list has** keeps to the words the keyboard knows only from your dictionary, which is where learned slips and misread swipes sit. The **Delete** button says how many words go, and pressing it is the confirmation.
To add a lot of words at once from something you already wrote, open the [Learn from text](https://wmkeyboard.pages.dev/tools/learn-from-text/) tool in that note or draft. It lists the words the keyboard doesn't know yet, and adds the ones you keep checked exactly as **Add word** would.
Words are capped at 32 characters, counted after the word is lowercased. Anything longer is dropped on the floor by both paths: typing it teaches nothing, and **Add word** closes the dialog as though it worked while the list stays exactly as it was. There's no error and no warning, so if a long entry never turns up in the list, count the characters.
Every add, edit or remove here updates a version marker the running keyboard watches. If the keyboard is open in another app while you edit the list, it reloads your change instead of silently overwriting it the next time it saves. Before you've typed or added anything, the screen reads "Nothing here yet. Words appear as you type, if 'Learn from typing' is on in Privacy. You can also add a word above."
Two related screens are easy to confuse with this one but do something different. The [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) is a separate never-suggest list. Blacklisting a word does purge it from this one, though neither screen offers a shortcut to push a word the other way. **Custom dictionaries** is for importing a bulk word list per language rather than words you've typed (see [Downloadable dictionaries](https://wmkeyboard.pages.dev/languages/dictionaries/) for that).
## Android's dictionary
Typing / Suggestions / Personal dictionary
Android keeps a personal dictionary of its own, shared by every keyboard and the spell checker. Two rows above the word list move words between it and yours, whenever you ask:
- **Import from Android's dictionary** lists the words in Android's dictionary that aren't in yours yet, all checked. Uncheck any you don't want and tap **Add**. They go in as words you added by hand, and a word written there with capitals keeps them. An entry with spaces in it, like "on my way", comes in as separate words, the same way the keyboard already reads it.
- **Export to Android's dictionary** lists your words that Android's dictionary doesn't have yet, most used first, and **Copy** writes the checked ones there. A message says how many went.
An entry with spaces in it is read as its separate words everywhere, but its capitals are left on the phrase: "User Dictionary Manager" in Android's dictionary teaches the keyboard "user", "dictionary" and "manager" and nothing about how you capitalise them, so they are still suggested in lower case. A single-word entry written with capitals does keep them, and that includes a one-letter entry such as "I". Importing is the exception, since you pick the words yourself: each part comes in as you see it in the list, capitals and all, and unchecking the ones you do not want is the point of the list.
Words match whatever their capitals, so "Boston" and "boston" count as one word and are never offered twice. Android only lets the keyboard currently in use touch its dictionary, so both rows need WM Keyboard to be your current keyboard. Otherwise they say so and do nothing. Neither runs by itself: to copy each new word as the keyboard learns it, turn on **Add words to the system dictionary** under Privacy.
A third row, **Import a Gboard dictionary**, takes the `PersonalDictionary.zip` from Gboard's own Export (in Gboard: Settings, Dictionary, Personal dictionary, then Export in the menu). A dialog counts what is in it first. Its words come into this list as words you added by hand, capitals kept, each filed under the language Gboard had it under when WM Keyboard has that language (under **All languages** otherwise), and its shortcuts become [snippets](https://wmkeyboard.pages.dev/tools/snippets/) in a folder called **Gboard** that show their word as a suggestion, as Gboard does. See [Gboard's personal dictionary](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#gboards-personal-dictionary).
## Clearing everything at once
Privacy / Your data / Learned words
The row is called **Learned words** and its button reads "Delete the learned words". It asks first: a dialog spells out what goes and waits for you to confirm.
It reaches wider than this word list. Seven files are cleared together, because each learning store lives on its own: your learned words, the new words still waiting to be counted, your rank adjustments, your emoji-usage history, autocorrect's memory of the corrections you undid, your CJK candidate-pick history, and the language-mix signal behind mixed-language typing.
Like an add or a remove, the delete bumps the version marker a running keyboard watches, so a session open in another app re-reads your vocabulary from disk instead of saving the old copy back over the wipe. Typing statistics live under their own folder and aren't touched. Clear those from the Statistics screen instead.
smart/personal-dictionary-clear — The 'Your data' group on the Privacy screen, with the one-tap clear button.
The learned-word file this screen deletes is your personal dictionary, so this button empties the word list on this page along with everything else. The confirmation dialog says so: a word you add by hand goes into the same list as a word the keyboard picked up. What survives is your snippets and your imported word lists.
## Where it lives, and who else can see it
Your personal dictionary is a single JSON file in the app's private storage, never uploaded anywhere on its own. Two things are true by default, and two are opt-in:
smart/personal-dictionary-backup-toggle — The Dictionary section of a manual export, off by default.
- **It's inert during direct boot.** Between a restart and your first unlock, the file is unreachable, since it lives in credential-encrypted storage. The keyboard runs with an empty, in-memory-only vocabulary: nothing is read, and nothing you type in that state is written anywhere. Once you unlock, the real file is reattached and the temporary in-memory copy is discarded rather than merged in.
- **Nothing mirrors outside the app by default.** An opt-in toggle can also copy newly-learned words (not autocorrect targets) into Android's own shared personal dictionary, so other keyboards and spell checkers can see them too. It's off by default, one-directional, and forced off automatically during direct boot however you've set it, since nothing typed on a lock screen should be learned anywhere. A separate toggle, on by default, does the reverse for a different field of that same system list. It reads back any shortcut entries you've defined there (like "omw" → "on my way") and offers the full phrase as you type the shortcut. Glide the shortcut instead and the full phrase is typed straight away; while you swipe, the floating word, the suggestion strip and the words offered when you hold still show it as `omw = on my way`, and one backspace puts the shortcut back. A shortcut can also stand for a gap, such as a tab or an em space: its suggestion looks blank, and picking it types that gap with no extra space after it. To move words across by hand in either direction, see [Android's dictionary](#androids-dictionary).
- **Android's own backup is opt-in.** Turning on "Back up with Android" includes your whole keyboard, learned words included, in Android's standard app-data backup and phone-to-phone transfer.
- **A manual export leaves it out by default.** The Backup & restore screen's "Dictionary" section covers your personal vocabulary and the language each word is filed under, next-word patterns, rank adjustments, the fixes you have taught autocorrect and where your taps land, and it starts unchecked. A settings file you export and share out of habit carries no typing data along unless you deliberately opt in. Restoring that section does signal a running keyboard to reload, the same as adding or removing a word does.
See [Incognito & secure fields](https://wmkeyboard.pages.dev/privacy/incognito/) for the rest of how incognito and secure fields work, and [Backup & restore](https://wmkeyboard.pages.dev/privacy/backup/) for the full export/restore and Android-backup picture.
## Options
Typing / Suggestions / Personal dictionary
| Setting | Default | Where |
| --- | --- | --- |
| Learn from typing | On | Privacy / Learning on this device |
| Add words to the system dictionary | Off | Privacy / Learning on this device |
| Use the Android dictionary | On | Privacy / Learning on this device |
| Expand Android dictionary shortcuts | On | Privacy / Learning on this device |
| Incognito mode | Off | Privacy / Learning on this device |
| Follow private browsing | On | Privacy / Learning on this device |
| Pause learning (while incognito) | On | Tools / Incognito / While incognito is on |
| Learn a word after | Once | Typing / Suggestions |
| Remember a new word after | 3 | Typing / Suggestions |
| Ask before saving new words | Off | Typing / Suggestions |
| Word menu | Never suggest, Add, Delete, Synonyms, Suggest higher or lower | Typing / Suggestions |
| Adjust rank changes | Both | Typing / Suggestions |
| Back up with Android | Off | Privacy / Android backup |
| Include "Dictionary" in export | Off | Backup & restore / What goes in an export |
The dictionary screen itself has no settings beyond the word list, its sort order, its language chips, its two buttons, the two Android rows and the per-word edit dialog. Every toggle that controls whether something gets learned lives on the Privacy screen rather than here, with two exceptions: pausing learning during incognito is under the Incognito tool's own settings, and the three thresholds that decide when a word gets in and when it's shielded sit with the rest of the suggestion sliders.
## Details & edge cases
- **The in-app description undersells the gate.** "Learn from typing" describes itself as skipping "password fields," but the real gate is broader. Every structured field (email, web address, number, phone, date, time, datetime) opts out too, not only the secured ones.
- **The backspace-revert path checks slightly different conditions than ordinary learning.** Restoring a word by backspacing right after an autocorrect checks only the setting, incognito, and that the field isn't a secure one. It doesn't ask for a plain text field the way normal learning does. In practice that costs you nothing, because the autocorrect it undoes could only have fired in a plain text field in the first place. It's also counted immediately rather than waiting to settle, since you've told the keyboard directly rather than by leaving the text alone.
- **A blacklisted word is never learned while it stays blacklisted.** The personal dictionary and the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) are independent stores, but adding a word to the blacklist purges it from the personal dictionary and the words still waiting to be learned. Typing it afterward teaches the keyboard nothing while it remains on the list. The blacklist also wins as a candidate, so the word is never offered or autocorrected to either way. Take it off the blacklist and ordinary learning resumes.
- **Words added before this version keep their old weight.** Earlier versions handed an added word a weight of 200 instead of marking it, so on an upgraded install those words read "Seen 200 times" without the added-by-hand note. Lower the weight from the edit dialog if it crowds the strip.
- **Respelling a word you moved keeps its language.** A word you filed by hand takes that language with it to the new spelling, even when the spelling merges into a word filed elsewhere. A word you never filed fills in its language only where the other word has none.
- **Android's dictionary has no languages here.** Words imported from Android's dictionary go under **All languages**, and an export writes every word for all languages, because Android files most entries under the phone's language whatever they are.
- **Editing only the capitals of a word keeps everything else.** The respelling is compared after lowercasing and normalising, so changing only capitalisation is not a rename: the word keeps its weight, its pairs and its age, and only its kept spelling changes.
- **Respelling a word keeps its date.** A word you respell, or merge into another, keeps the older of the two dates, so fixing a typo does not move it to the top of **Newest first**.
- **A weight edit resets the word's age.** Compaction (see below) scores a word by its weight and how long ago it was last touched. Editing a weight counts as touching it, so a word you deliberately adjusted starts fresh on that clock.
- **The list is capped, and stale words go first.** It holds 10,000 words before it starts trimming, and words you added by hand outlast the ones the keyboard picked up on its own. See [Dictionaries & words](https://wmkeyboard.pages.dev/reference/settings/dictionaries/#the-list-is-capped-at-10000-words) for how it chooses.
- **A word arrives with the count it earned.** A new word that took three sightings to get in reads "Seen 3 times" on the list, not "Seen once", because the sightings it collected on the way in come with it.
- **Turning learning off empties the waiting room too.** Switching off "Learn from typing", or switching on incognito while it's set to pause learning, throws away the words that were queued but not yet counted. They were typed while it was allowed, but none of them had been written anywhere.
- **A language with no dictionary of its own is slower to learn from.** Nothing recognises any of those words, so every one of them waits its three sightings. Set **Remember a new word after** to 1 if you type mostly in a language the keyboard has no wordlist for.
For the rest of the suggestion strip (completions, corrections and next-word prediction), see [Suggestions & prediction](https://wmkeyboard.pages.dev/smart/suggestions/). To keep a word out of suggestions entirely instead of teaching it, see the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/). For how contact names interact with this same learning path, see [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/).
---
# Search by meaning
URL: https://wmkeyboard.pages.dev/smart/search-by-meaning/
Section: Suggestions & correction
Summary: Find clips, notes, snippets, emoji, your own stickers and your photos by what your words mean, in any language, with a model that runs on the phone.
Search by meaning gives the keyboard's search boxes a second opinion. You still get the usual matches for the letters you typed, and under them come the things that mean what you typed: "groceries" finds the note "Buy milk, eggs and bread", "kitten" finds the clip "My cat sleeps on the sofa all day", "skiing" finds your sticker of a skier, and "বিড়াল" or "gato" finds 🐈 with no keyword pack for either language. It works in more than 100 languages, and a query in one language finds things written in another.
It is off by default, because the model behind it is a large download.
## Turning it on
AI / Search by meaning
Pick one of three:
- **Off** (the default). Search works by letters only, as it always has, and nothing is downloaded.
- **Text**, about 155 MB. Clips, notes, snippets and emoji are found by meaning.
- **Text and pictures**, about 375 MB in all. Your own stickers and saved GIFs, pictures in your clipboard history, and the photos in the [gallery](https://wmkeyboard.pages.dev/tools/gallery/#searching-your-photos), are found by what they show as well.
Picking Text or Text and pictures starts the download. On mobile data it asks first, the same way every other model download does (see [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/)). A banner under the choice shows the progress, says when the model is ready, and offers **Try again** if the download stopped. **Delete the model** removes the files and turns the feature off. The **Search by meaning** row under About / Storage shows the same files with their sizes, and the indexes built from your things beside them; deleting the text model or the indexes there turns the feature off too, and deleting only the pictures model leaves it on Text.
On the GitHub edition the first download also fetches the LiteRT interpreter the model runs on, unless offline voice typing or the background remover already did. The Play Store edition gets the interpreter from Play. The F-Droid edition has no LiteRT interpreter, so it has no search by meaning and the choice is not shown.
## Where it works
Every match by meaning is listed after the matches by letters, best first, and only while the box still holds the words it was found for.
| Box | What is searched |
|---|---|
| [Clipboard](https://wmkeyboard.pages.dev/tools/clipboard/#finding-a-clip-again) | the text of each clip, a link's page title, a file's name, and with pictures on, what a copied image or screenshot shows. Clips marked as passwords are left out. |
| [Notes](https://wmkeyboard.pages.dev/tools/notes/#the-shelf-and-the-tags) | each note's title, tags and text. Locked notes are never searched by meaning. |
| [Snippets](https://wmkeyboard.pages.dev/tools/snippets/#opening-the-panel) | each snippet's label, text and trigger, from every folder |
| [Emoji](https://wmkeyboard.pages.dev/emoji/picker/#searching-for-an-emoji) | each emoji's English name and keywords, so a query in any language reaches them |
| [My stickers](https://wmkeyboard.pages.dev/emoji/stickers/#searching-and-sending) (Text and pictures) | what each of your own stickers shows, in the pack chip you are on |
| [Gallery](https://wmkeyboard.pages.dev/tools/gallery/#searching-your-photos) (Text and pictures) | what each photo in your library shows. This box has no letter matches: a photo has no words to match. |
| [AI tools](https://wmkeyboard.pages.dev/tools/ai/#the-keyboards-own-tools) | the model's own question: **Search your clipboard** finds clips, never passwords or codes, **Read your notes** adds notes that say the same thing in other words, never locked ones, and **Read your snippets** does the same for snippets |
With **Text and pictures**, **Tag my stickers** (on by default) also gives each of your stickers that has no title and no keywords a few tags read off its picture: the emoji whose names fit it best, and the words of those names. A photo of a cat gets 🐈 and "cat", so it comes up [as you type](https://wmkeyboard.pages.dev/emoji/stickers/#stickers-as-you-type) like a sticker you had tagged yourself. These tags are kept apart from yours and are never written into the pack.
## How it works
The model is [EmbeddingGemma 2](https://huggingface.co/google/embeddinggemma-2) by Google, released under the Apache 2.0 licence. It turns a piece of text, or a picture, into a list of numbers that places it among everything else by meaning, so texts and pictures about the same thing land close together whatever their language. The keyboard compares your query's numbers with each item's and lists the ones that stand out.
- **Everything stays on the phone.** The model runs on the phone's processor. Your queries, your clips, notes, snippets and pictures, and the numbers made from them are never sent anywhere. The only network request is the download of the model itself.
- **It learns in the background.** When you open the clipboard, notes, snippets, sticker or gallery panel, the keyboard works through what is new or changed since the last time, a few hundred texts or a few dozen pictures at a time, on a low-priority thread. The gallery keeps going a few dozen photos at a time for as long as its panel stays open, newest first. Until an item has been read, it is found by letters only, and the AI tool does not find it. Emoji come ready, because their numbers are part of the download.
- **It keeps out of the keyboard's way.** The model runs in a process of its own and needs about 350 MB of memory while it is loaded. It lets that memory go a few minutes after the last search, and if Android needs the memory sooner it stops that process. Typing never waits for it.
- **A query waits for you to pause.** The model reads your words a moment after you stop typing, so the matches by meaning arrive a little after the matches by letters. The first search after the model has been let go takes a few seconds more, while it loads.
## Details & edge cases
- **Turning it off deletes what it learned.** The numbers made from your clipboard, notes, snippets, stickers and photos live in the app's storage, out of backups. Turning the feature off deletes them. The model files stay until you press **Delete the model**.
- **Photos follow the permission.** Your photos are read only with the [gallery's](https://wmkeyboard.pages.dev/tools/gallery/#opening-it) access to them. Take that access away and the next time the gallery opens their numbers are deleted. The Play Store build has no access to the library, so it has no photo search.
- **The AI only sees what is safe to read.** **Search your clipboard** and the meaning halves of **Read your notes** and **Read your snippets** check every match against the clipboard, the notes and the snippets as they are now: a clip marked or shaped like a password or a one-time code, a locked note, and anything in the trash or deleted are left out even when their numbers are still in the index. All three start on **Ask**, like the other tools that read your things. **Search your clipboard** is only offered to the model while the feature is on and the text model is downloaded; without it, **Read your notes** and **Read your snippets** match words only.
- **Short queries are skipped.** A one-letter query is matched by letters only.
- **Nothing that stands out, nothing listed.** Every text is a little like every other to the model, so an item is listed only when it is clearly closer to your query than the rest are. A query about something you have nothing on finds nothing by meaning.
- **Long texts are read by their start.** Only about the first 200 words of a long note or clip are read.
- **Animated stickers** are read by their first frame.
- **Checked downloads.** Each file is checked against a SHA-256 digest built into the app before it is used, so the download address can be a mirror. The files are published as the `models` release of the project's data repository, repackaged from Google's LiteRT files without changing a weight. See [Network](https://wmkeyboard.pages.dev/privacy/network/#downloads-dictionaries-models-and-addons).
---
# Selection actions
URL: https://wmkeyboard.pages.dev/smart/selection-actions/
Section: Suggestions & correction
Summary: Select text and the keyboard offers what to do with it: edit it, reformat it, convert it, translate it, look it up, or hand it to another app. On the keyboard's bar, or from any app's selection menu.
Selecting a phone number in a chat and then dialling it usually means copying it, leaving the app, opening the dialler and pasting. The keyboard already knows what you selected, so it can offer the things that shape of text is for, and a good deal more: the everyday edits, a find and replace, line tools, case and chat formatting, conversions, and a way to hand the text to your contacts, maps or calendar.
Select `01712345678` and the bar reads **Select all, Call, Message, WhatsApp, Contact, Copy, Cut, Paste, Delete, Share, Format, Find, Replace**. Select a sentence and it reads **Select all, Copy, Cut, Paste, Delete, Share, Save as note, Format, Find, Replace, Repeat, Grammar, AI, Bangla, Fancy** and so on, with the actions that only make sense for what you selected (Sort on several lines, Colour on `#336699`, Zones on `5pm`) appearing when they do.
This is off by default. It adds a bar that appears out of a gesture you already make for other reasons, so you have to ask for it.
The same actions, with translation and the AI actions beside them, are also in the menu most apps show over selected text, with no keyboard open and nothing to switch on. See [In any app's selection menu](#in-any-apps-selection-menu).
smart/selection-actions-phone — A selected phone number, and the bar the keyboard offers for it.
## Turning it on
Advanced / Selection actions
Turn on **Selection actions**. Select some text in any app. The bar appears while the selection is live and goes away when you clear it.
To switch it on and off without opening settings, add the **Selection actions** tool to your toolbar. One tap flips the same switch, and a message says which way it went. Text that is already selected gets its bar straight away.
## In any app's selection menu
The bar lives in the keyboard, so it only sees text in a box you are typing into. For text anywhere else (a web page, a message someone sent you, an email), select it and pick **WM Keyboard** from the menu that appears over the selection, the one with Copy and Select all. On some phones it is behind the menu's three dots, and some apps leave it out of their menu altogether (see [When the entry is missing](#when-the-entry-is-missing)). You do not have to be typing with this keyboard, and the bar does not have to be on.
A sheet opens over the app with the text at the top and the actions under it, in three groups:
- **Language.** **Translate** puts the text into the language the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool translates into, through the engine and services you set up there. Each of your [AI actions](https://wmkeyboard.pages.dev/tools/ai/) follows it (Explain, Summarize, Formal and the rest, your own included) and writes its answer in place of the text. **Ask AI** opens the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) with the text attached, and **Look up** opens the [Look up](https://wmkeyboard.pages.dev/tools/look-up/) card on it.
- **Rewrite.** The actions from this page that change text without needing a text box: Format and its case options, Fancy, Sort, Unique, Number, Bullets, Check boxes, Digits, Zawgyi ⇄ Unicode, JSON, Base64, Decode and Remove trackers.
- **Open in.** Call, Message, WhatsApp, Email, Open, Contact, Map and Calendar, for a phone number, an address, a link, a place or a date.
The rewrites and the Open in actions come from the same **Actions** list as the bar, in the same order (see [Options](#options)), and each shows only where it applies. Translate needs the Translate tool switched on. The AI actions need the AI tool switched on and set up, with a provider or a downloaded model.
Each action works on the text as the last one left it, so you can translate a paragraph and then make it shorter, or sort lines and then number them. The arrow at the bottom left undoes one step at a time. Then:
- **Replace** puts the result in place of your selection. The app does the replacing, so it works in any app whose text box offers the menu.
- **Copy** copies the result and closes the sheet. On text you cannot edit, such as a web page, there is no Replace, and Copy is the way to take the result with you.
- The share button beside Undo hands the result to another app.
Calendar opens your calendar app's editor here rather than the keyboard's own [Calendar tool](https://wmkeyboard.pages.dev/tools/calendar/), since there is no keyboard open to show it in. The sheet leaves out the bar actions that work on the text box itself (Select all, Copy, Cut, Paste, Delete, Find, Replace, Move up, Move down), the ones that need a keyboard panel or the keyboard's loaded dictionaries (Bangla, Banglish, Hindi, Hinglish, Grammar, DeepL Write, Repeat, QR, Search, Speak, Zones, Colour, Save as note, Edit word), and chat formatting, which depends on the app you are typing in.
Nothing in the sheet is stored. Translate sends the text wherever the Translate tool sends it, which is nowhere when it translates on the phone. The AI actions do the same with the AI tool's provider, and [Data Saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds them back on mobile data as it does in the keyboard: when it is set to ask, the sheet says so, and tapping the action again sends it.
### WM Translate
For translating alone, turn on **WM Translate in the selection menu**. It adds a second entry that opens the same sheet with the translation already running. It ships off, because every entry is one more row in every app's menu and Translate is a single tap inside the WM Keyboard sheet anyway. The entry is only there while the Translate tool is switched on.
### Choosing the entries
Advanced / Selection actions
The group **In other apps' selection menus** has a switch for each entry the keyboard adds to other apps' menus:
| Entry | Switch | Default |
|---|---|---|
| WM Keyboard | WM Keyboard in the selection menu | On |
| WM Translate | WM Translate in the selection menu | Off |
| Ask AI | Ask AI in the selection menu | On |
| Look up | Look up in the selection menu | On |
Ask AI and Look up are the same switches as on the [AI tool](https://wmkeyboard.pages.dev/tools/ai/#options) and [Look up](https://wmkeyboard.pages.dev/tools/look-up/#options) pages, repeated here so all four sit in one place. Turning a switch off takes its entry out of every app's menu. Turning off the Translate or AI tool takes their entries out too. These switches do not depend on **Selection actions** at the top of the screen, which is the keyboard's own bar.
### Choosing the buttons on the sheet
The same group has a switch for four buttons on the sheet: **Translate on the sheet**, **Ask AI on the sheet**, **Look up on the sheet** and **Save as note on the sheet**. All four are on. **Save as note** keeps the text as a new note in the [Notes](https://wmkeyboard.pages.dev/tools/notes/) tool, and shows while that tool is switched on.
The sheet can't offer **Find**, because Find searches the text box you are typing in and no text box is open while the sheet shows.
### When the entry is missing
Not every app lists WM Keyboard, Ask AI or Look up in its selection menu, even with every switch above on. The menu is put together by the app you are in, and since Android 11 an app can only see the other apps on the phone it has asked to see in its own manifest. Chrome is allowed to see every app on the phone, so the entries are always in its menu. Other apps see WM Keyboard only when something they asked to see happens to cover it, and that varies from app to app: on one test phone Gmail and WhatsApp could see it, while Telegram and Google Keep could not. An app that can't see it builds its menu without the entries, and other apps' entries go missing along with WM Keyboard's: Google Translate, ChatGPT, a dictionary. Some apps also draw a menu of their own that has no place for other apps' entries at all.
Where the entries are there, they can be the last ones in the list behind the three dots, after Ask Gemini, Read aloud and whatever else your apps add. Scroll that list before deciding they are missing.
Nothing on WM Keyboard's side puts the entries back in such an app. Turning the switches off and on, reinstalling, or typing with WM Keyboard in that app leaves its menu as it was. Only that app's developer can add the request.
The keyboard's own bar works in every app, because the keyboard reads the selection itself rather than waiting for the app's menu. Turn on **Selection actions** (see [Turning it on](#turning-it-on)), select text in a box you are typing into, and the bar offers Translate, AI and the rest. For text you cannot type into, the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen) can ask the AI about the screen or look up a tapped word, outside the Play Store build. Copying the text and pasting it into a box, where the bar can reach it, works everywhere.
## What the keyboard reads
The keyboard reads your selection as a whole. That is the rule behind every result on this page, and it is deliberate:
- `01712345678` is a phone number.
- `call me on 01712345678 tomorrow` is plain text. You selected the sentence, not the number in it.
- `example.com/page` is a link. `report.txt` is not, because `txt` is not one of the endings a bare domain is allowed to have.
- Two lines are never one thing, whatever the pieces look like on their own.
Pulling a fragment out of a longer span is a different job, and the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/) already does it. That surface has the room to show you what it found and where it found it. This one does not, so it does not guess.
Phone numbers use the same formats as the clipboard, under [Phone formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/). With a format saved, a number has to match it, which is what keeps an invoice total or a row of figures from offering to dial itself. With no format saved, the plain shape rules apply.
On top of that reading, a few actions look at what the text holds: whether it has several lines, Bengali or Latin letters, digits in another script, a colour code, a date or time, a place. Those checks are cheap, and the dearer ones (dates, places) only run while an action that needs them is switched on.
## The bar
The bar is one row of chips that scrolls sideways, in an order you control under **Actions** (see [Options](#options)). Every action is drawn only where it applies, so the row you see is shorter than the list: Call never appears on a sentence, Sort never appears on a single line.
Two things sit outside the order. **Undo** is always first while there is something to undo. And the actions whose answer is a pick rather than a single result (Format on text, Fancy, Colour, Zones) open a short row of choices in place of the bar, with an arrow on the left to come back.
| Action | Appears on | Shipped |
|---|---|---|
| Undo | Anything, after the bar changed it | On |
| Select all | Anything not already the whole field | On |
| Copy, Cut, Delete, Share | Anything | On |
| Save as note | Anything, once the phone has been unlocked; never a password field | On |
| Paste | Anything, while the clipboard holds text | On |
| Format | A number, link or address the rewrite would change; any text | On |
| Find | Anything, when the field could hold another copy | On |
| Replace | Anything | On |
| Repeat | Anything, while the Text repeat tool is enabled | On |
| Snippet | Anything, while the Snippets tool is enabled. Opens your snippets: a pick replaces the selection, and `{selection}` in it reads what was selected | Off |
| Sort, Unique, Number, Bullets, Check boxes | Two or more lines | On (Check boxes off) |
| Move up, Move down | Anything short of the whole field | Off |
| Grammar | Text, with the [Grammar](https://wmkeyboard.pages.dev/smart/grammar/) tool on | On |
| DeepL Write | Text up to 2,500 characters, once [DeepL Write](https://wmkeyboard.pages.dev/tools/translate/#deepl) is set up | On |
| Translate | Text, with the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool on | On |
| AI, and the AI buttons | Text, with the [AI](https://wmkeyboard.pages.dev/tools/ai/) tool on and set up | On |
| Bangla | Latin text, with Bengali among your languages | On |
| Banglish | Bengali text | On |
| Hindi | Latin text, with the [Hindi phonetic](https://wmkeyboard.pages.dev/languages/hindi/) layout enabled | On |
| Hinglish | Devanagari text, with the Hindi phonetic layout enabled | On |
| Digits | Digits in another script | On |
| Zawgyi ⇄ Unicode | Burmese text | On |
| Colour | A colour code | On |
| Call, Message, WhatsApp | A phone number | On |
| Email | An address | On |
| Open | A link or an address | On |
| Remove trackers | A link with tracking parameters, alone or in text | On |
| QR | A link, with the QR generator on | On |
| Contact | A phone number or an address | On |
| Fancy | Text | On |
| Search | Text, with the Web search tool on | Off |
| Bold, Italic, Strike, Mono | Text, in a chat app whose syntax the keyboard knows | Off |
| JSON | A JSON object or array | Off |
| Base64, Decode | Base64 text; a percent-encoded link | Off |
| Speak | Anything | Off |
| Edit word | One word | On |
| Zones | A time | Off |
| Calendar | A date or time | Off |
| Map | Coordinates or an address | Off |
| camelCase, snake_case, kebab-case, CONSTANT_CASE | Inside Format's case options | Off |
## Undo
Every rewrite the bar makes (a case, a style, a chat marker, a grammar fix, a conversion, a replace) is remembered while the selection stays live. **Undo** is the first chip whenever there is one, and each tap walks one step back: lower case, then Title Case, then bold, then three taps of Undo returns the original.
The memory ends with the selection. Move it onto other text, clear it, or leave the field, and there is nothing left to put back. Before putting anything back the keyboard checks the field still reads as the last rewrite, so an edit you made in between is never overwritten. Cut and Delete take the selection away and are not undone here; your editor's own undo covers those.
## Editing
**Select all** selects the whole field. A long press usually selects only one word, so this is the fast way to act on everything you wrote. In a web page it goes in as Ctrl+A, so an editor in the page, like a code editor that only draws the lines on screen, selects its whole text itself rather than losing a selection the keyboard made for it. **Copy**, **Cut** and **Paste** are the editor's own; Paste replaces the selection and only appears while the clipboard holds text. **Delete** removes the selection. **Share** opens the [share sheet](https://wmkeyboard.pages.dev/reference/settings/share-sheet/) with the selected text, or Android's when the keyboard's sheet is off. **Save as note** keeps the selection in the [Notes tool](https://wmkeyboard.pages.dev/tools/notes/) as a new note, or adds it to the note you edited last when Tools / Notes / Save as note from a selection says so; a toast says which. It appears only while the Notes tool is enabled.
## Find and replace
**Find** jumps to the next place the selected text appears, wrapping round to the top, and keeps it selected so the bar stays and a second tap walks on. The match is brought into view, even in an editor that would leave it under the keyboard or off the screen.
**Replace** opens the [Find & replace](https://wmkeyboard.pages.dev/tools/find-replace/) panel above the keys, the same one its toolbar tool opens, with two fields: what to find, already holding the selection, and what to replace it with. Tap a field and type into it; the keys go to the panel, not to the app. The current match is selected in the field itself, where you can see it, and the panel says which one of how many. Enter in the find field goes to the next match; Enter in the replace field replaces the current one.
smart/selection-actions-replace — The Find and replace panel: two fields, the toggles, the buttons and the count.
Three toggles: **Aa** matches case, **[ab]** matches whole words only (in any script, so a Bengali word is a whole word too), and **.\*** reads the find field as a regular expression, with `$1` in the replacement standing for the first group. A pattern that is not valid says so under the toggles, and one that takes too long on the text is stopped rather than left to hang.
**Replace** swaps the current match and moves to the next. **All** replaces every match in one go, which Undo takes back in one go too. A field that does not report where its text is (a few web views) still lets you find, but not replace in place.
**Repeat** opens the [Text repeat](https://wmkeyboard.pages.dev/tools/utilities/#repeating-text) panel with the selection as the text to repeat: pick a count and a separator there and Insert writes the copies at the cursor. It appears only while the Text repeat tool is enabled.
## Lines
With two or more lines selected, four chips appear. **Sort** puts them in order, digits compared as numbers so `item2` comes before `item10`; sort a list that is already in order and it turns the other way round. **Unique** drops repeated lines. **Number** puts `1.`, `2.`, `3.` in front of each line, and **Bullets** a bullet. **Check boxes** puts `☐` in front of each line, a list to tick off. All three are toggles: a list that already has the prefix on every line loses it, and any one converts into the others, so a bulleted list becomes a numbered one in a tap. A ticked box (`☑`, `✅`) and a Markdown task box (`- [ ]`, `[x]`) count as boxes already.
**Move up** and **Move down** swap the lines your selection touches with the line above or below, and the selection goes with them, so pressing again keeps moving them. They work on one line as well as several, which is how a single item in a list changes places with its neighbour. Both are off to start with; turn them on under Actions.
## Format
**Format** rewrites the selection in place. What it does follows what the selection is:
- **A phone number** is written back in your own saved format. If you keep `+880 1XXX-XXXXXX`, then `01712345678` becomes `+880 1712-345678`. The separators come from the format you wrote, not from a shape the keyboard invented. With no format saved, a number that names a country code becomes E.164 (`+8801712345678`) and any other number becomes its bare digits.
- **A link** loses its tracking parameters. `utm_*`, `fbclid`, `gclid`, `igshid` and about twenty others go; everything else in the query, the path and the fragment stay exactly as they were. A link with no scheme gains `https://`.
- **An address** is lower-cased.
- **Anything else** opens the case options instead.
The Format chip is left off when the rewrite would change nothing. A number already in your format, or a link with nothing to strip, does not get a button that does nothing.
### Case options
On ordinary text, Format opens a row of chips in place of the bar: **lower case**, **Title Case**, **UPPER CASE** and **Sentence case**, plus, when you switch them on under Actions, the programmer's cases **camelCase**, **snake_case**, **kebab-case** and **CONSTANT_CASE**. Those four read their input in any convention, so `Hello World`, `hello_world` and `helloWorld` all mean the same two words. The arrow on the left goes back to the actions.
smart/selection-actions-case — Format on plain text: the four case options, with the arrow back to the actions.
Each one rewrites the selection and leaves the result selected, so you can try Title, then Sentence, then UPPER without reselecting between taps. None of them changes the spelling: UPPER CASE writes `ß` as the capital `ẞ` rather than `SS`, so lower case gets the word back exactly.
Title case and Sentence case leave a word that is already all capitals alone, so an acronym in the middle of a sentence survives. `the NASA launch` becomes `The NASA Launch`, not `The Nasa Launch`. That rule is suspended when the whole selection is capitals, because somebody selecting `HELLO THERE` and reaching for Title case is fixing shouting, not protecting acronyms.
**Fancy** opens the [Fancy Text](https://wmkeyboard.pages.dev/languages/fancy-text/) styles, each chip written in itself, and restyles the selection in the one you pick.
## Chat formatting
**Bold**, **Italic**, **Strike** and **Mono** wrap the selection in the markers the app you are typing in understands, and a second tap takes them off again. They only appear in apps whose syntax the keyboard knows, because markers an app does not read arrive as punctuation:
| Apps | Bold | Italic | Strike | Mono |
|---|---|---|---|---|
| WhatsApp, Viber | `*text*` | `_text_` | `~text~` | `` ```text``` `` |
| Slack, Google Chat, Messenger, Rocket.Chat | `*text*` | `_text_` | `~text~` | `` `text` `` |
| Threema | `*text*` | `_text_` | `~text~` | none |
| Telegram | `**text**` | `__text__` | `~~text~~` | `` `text` `` |
| Discord, Element, Beeper, Reddit, Mattermost, Zulip, GitHub | `**text**` | `*text*` | `~~text~~` | `` `text` `` |
Mono on several lines uses the app's fenced block where it has one. These four are off to start with; find them under Actions.
## Convert
- **Digits** turns figures in another script into `0` to `9`: `০১৭১২` becomes `01712`, and Devanagari, Arabic-Indic or Persian figures the same.
- **Colour** appears on a colour code (`#336699`, `#f00`, `0xFF336699`, `rgb(51, 102, 153)`, `hsl(210, 50%, 40%)`), wears a swatch of the colour, and opens its other spellings; tap one to write it in place.
- **JSON** pretty-prints a minified object or array with two-space indents, or minifies a pretty one. Key order and number literals survive either way.
- **Remove trackers** appears on a link that carries tracking parameters (`utm_source`, `fbclid`, `gclid`, `si` and the like) and takes them off, leaving everything that says which page it is. `youtu.be/abc?si=XyZ` becomes `youtu.be/abc`. It also takes the trackers a known site adds only there, such as Amazon's `/ref=…` path or X's `?s=20`, and turns a redirect link such as Google's `/url?q=…` into the link it leads to. It also shows on a sentence or a paragraph with such a link in it, and cleans every link there while leaving the words alone, and on a single link of any length (before version 0.6.2 a link longer than 320 characters, which tracked links often are, never offered it). Add parameter names of your own under Clipboard / Copy rules, where **Remove trackers from copied links** also cleans every link as you copy it. It is on to start with; if you have already picked your own set of actions, switch it on under Actions.
- **Zawgyi ⇄ Unicode** appears on Burmese text and converts it between Zawgyi, the old font encoding much Burmese is still written in, and Unicode, whichever way the text needs: Zawgyi becomes Unicode and Unicode becomes Zawgyi. It reads the whole selection to decide, and writes the result exactly as converted even in a text box the keyboard is [writing Zawgyi](https://wmkeyboard.pages.dev/languages/burmese/#writing-zawgyi) into. It is on to start with; if you have already picked your own set of actions, switch it on under Actions.
- **Base64** decodes Base64 text, and **Decode** a percent-encoded link (`hello%20world` to `hello world`). Both are careful about what counts: an ordinary word is never mistaken for Base64, and `50% off` is not encoded.
## Language
- **Bangla** turns Banglish into Bengali script, word by word: the spelling map first (`tmr` to তোমার), then the same phonetic reading the suggestion strip uses, which is what lets `asi`, `achi` and `achhi` all land on আছি, and only then a literal transliteration. Digits and punctuation stay as they were. It appears on Latin text while Bengali is one of your languages.
- **Banglish** goes the other way, writing Bengali script back in Latin letters by pronunciation (তোমার to `tomar`, ধন্যবাদ to `dhonnobad`, কী to `ki`), with the most readable spelling from the map for words it knows.
- **Hindi** does the same for Hinglish: the known-spellings list first (`theek` to ठीक, `nhi` to नहीं), then the downloaded Hindi word list through the same phonetic match the strip uses, then the rules. It needs the Hindi phonetic layout switched on, because that is what loads all three. On Latin text with both Bangla and Hindi available you get both chips, and you pick the language you wrote.
- **Hinglish** writes Devanagari back in Latin letters the way it is said, not the way it is spelled: करना is `karna` and नमस्ते is `namaste`, because spoken Hindi drops the inherent vowel in the first and keeps it in the second. Long vowels are doubled inside a word and single at its end (`naam`, `theek`, `mera`, `ladki`).
- **Grammar** runs the [Grammar](https://wmkeyboard.pages.dev/smart/grammar/) checker over the selection alone and commits every suggestion at once, offline with Harper. With [LanguageTool](https://wmkeyboard.pages.dev/smart/grammar/#checkers-harper-and-languagetool) switched on, the selection goes to its server when LanguageTool is the checker for your language, and never from a password field or in incognito. It shows a spinner while it works and says so when there is nothing to fix.
- **DeepL Write** sends the selection to DeepL Write and puts DeepL's rewrite in its place, in the [writing style](https://wmkeyboard.pages.dev/tools/translate/#options) you picked. It shows only after you add a DeepL API Pro key and turn on **DeepL Write** under Tools / Translate, and it works in the F-Droid build too. Undo brings the original back.
- **AI** opens the [AI](https://wmkeyboard.pages.dev/tools/ai/) tool on the selection. Under **AI buttons** you can give any AI action a chip of its own (Formal, Shorter, Friendly and Summarize are made for it): one tap runs it on the selection and the result lands in the AI panel, ready to apply. Applying replaces the selection, not the field around it. The AI's own Translate action reads **AI translate** as a chip, so it is not confused with the Translate chip below.
- **Translate** opens the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool with the selection in its box and the translation under it, using the translation engine you picked there (online, or on the device). **Replace text** in the panel then replaces the selection, not the field around it. It sits just before AI on the bar and is on to start with; if you have already picked your own set of actions, switch it on under Actions. Opening the Translate tool from the toolbar with text selected does the same thing.
## Look up
**Edit word** appears when the selection is one word and opens that word's [word card](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip), the same card that holding a word on the suggestion strip reaches through **Edit**. It shows where the keyboard found the word and how often you typed it, and carries Never suggest or Suggest again, Delete, the rank controls, the spelling and **Keep these capitals**. **Add** is about the selected word: it appears while that word is not in your personal dictionary, and adds it spelled as selected. Punctuation around the word does not count, so `"Hello,"` and `(don't)` are single words; `two words`, `2026` and `e.g.` are not. The card works on your dictionary, not on the text, so the selection stays as it was. It closes when the selection moves to other text or goes away. If you have already picked your own set of actions, switch it on under Actions.
**Search** opens the [web search](https://wmkeyboard.pages.dev/tools/search/) tool with the selection as the query. **Speak** reads the selection aloud with the phone's speech engine, or with a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) for that language (in Bengali for Bengali text), and turns into Stop while it does. **Zones** appears on a time and shows it in each of the zones you picked under Time zones, UTC and your own to start with; tap one to write it in place of the selection. A selected `now` or `time now` counts as a time too, the current one, so selecting it gives the time in each zone right now.
## Open in
**Call** and **Message** open your dialler and your messaging app with the number filled in. **WhatsApp** opens a chat with that number. **Contact** adds the number or address to your contacts. **Open** hands a link to your browser, and an address to your mail app. When the link belongs to an app instead (a YouTube link with YouTube installed, say), the chip wears that app's icon and a small outward arrow, so you know where the tap goes before you make it. **Email** writes a new message to the selected address. **QR** opens the [QR generator](https://wmkeyboard.pages.dev/tools/utilities/) with the link already in it. **Map** opens coordinates (`23.8103, 90.4125`) or something that looks like a street address in your maps app. **Calendar** adds a selected date or time (`14 Sep 2026`, `tomorrow 5pm`, `১৪ সেপ্টেম্বর`) as an event: the [Calendar tool's](https://wmkeyboard.pages.dev/tools/calendar/#adding-an-event) composer opens with the selection as the title and that date and time set, ready to save. With the Calendar tool switched off, or its **Save events** set to the calendar app, it opens your calendar app's editor instead.
None of these send anything anywhere themselves: each hands the text to an app you picked and stops.
## From plugins
[Plugins](https://wmkeyboard.pages.dev/plugins/overview/) can add their own buttons to the bar. They come after the built-in ones, each with the name and picture its plugin gave it, and a tap replaces the selection with what the plugin made of it; Undo puts it back. A few keep the selection and add their result after it instead, such as a word count. Clink's text actions install this way too: add a Clink actions repository under Addons, or open a `.clinkext` file (see [Clink actions](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#clink-actions)).
A plugin's script sees the selected text only when you tap its button, and never on a password field, where the bar does not appear at all. Each tap starts the script afresh, and it has no way to send anything off the phone; see [the plugin sandbox](https://wmkeyboard.pages.dev/plugins/security/). Plugins are off until you turn them on under Tools / Plugins / Manage plugins, and each plugin's actions can be switched off one by one under Advanced / Selection actions / From plugins. They also appear under Rewrite on the sheet the **WM Keyboard** entry in other apps' selection menus opens.
## Where the bar draws
Advanced / Selection actions / Where it appears
**Own row** is the default. The bar gets a row of its own above the keys, which arrives with the selection and leaves with it. The keyboard is one row taller while it is there, and the app is asked to scroll the selected line back into view as the row opens, so the text you are working on does not end up behind the keyboard. That row is reorderable like any other under [Rows & bars](https://wmkeyboard.pages.dev/reference/settings/rows/), so you can put it above or below the suggestion strip. While a panel such as Clipboard or Emoji is open, the row stays away: text selected under an open panel no longer makes the keyboard grow, and the row arrives when the panel closes.
**Suggestion strip** puts the actions over the strip instead, which costs no height at all. There are no word suggestions to show while text is selected, so the strip has nothing to give up. With a panel open the strip goes back to the toolbar, because the arrow on that row is how you get out of the panel.
## Options
Advanced / Selection actions
| Setting | Control | Default |
|---|---|---|
| Selection actions | Toggle | Off |
| Where it appears | Choice of 2 | Own row |
| Show on every selection | Toggle | Off |
| Show with nothing selected | Toggle | Off |
| Read what is selected | Toggle | On |
| Actions | Screen | 37 of 56 on |
| Icon only | Screen | None |
| AI buttons | Screen | None |
| Time zones | Screen | UTC and your own |
| WM Keyboard in the selection menu | Toggle | On |
| WM Translate in the selection menu | Toggle | Off |
**Show on every selection** keeps the bar up on a selection too long to read. The keyboard does not read a selection past about 4,000 characters, so by default such a selection takes the whole bar away, Copy and Paste included, even though neither of those ever needed the text read, because the app does that work itself. Turn this on and the bar stays, carrying Select all, Copy, Cut, Paste and Delete, whichever of them you have switched on. Everything else still waits for a shorter selection, since every other action begins by reading the text.
**Show with nothing selected** keeps the bar's row on screen all the time, not only while text is selected. With just a caret in the field, the row holds Select all and Paste, whichever of them you have switched on. Tap Select all and the full row for the whole text replaces it, so Copy, Translate and the rest are one tap further on and you never have to long-press to select first. The keyboard stays the same height as you move between selecting and typing. This switch only exists with **Own row** picked: over the suggestion strip it would take your suggestions away while you type, so it is hidden there. Password fields still never show the row.
**Read what is selected** is the entity half of the feature. Turn it off and every selection is read as plain text: you keep the edits, the case options and the conversions, and lose Call, Email, Open, QR and Contact. It is worth turning off if you find the guesses wrong more often than right, since the generic half is the half that always applies.
**Actions** is the list of everything the bar can offer, grouped, searchable, each with its own switch, and with a pencil that lets you drag the actions that are on into the order you want. The full list is on the [settings reference](https://wmkeyboard.pages.dev/reference/settings/selection-actions/).
**Icon only** picks the actions drawn as their icon with no name, for a shorter row once you know them by sight, and **Icon size** on the same screen makes those icons bigger or smaller.
## Details and edge cases
- **The bar never appears over a password field.** Putting a password in front of a Share chooser is the mistake this feature should not make.
- **An action is only offered when it can work.** WhatsApp is hidden when WhatsApp is not installed. Search, Translate, QR, Grammar and AI are hidden when their tools are switched off, which includes the tools that [power saving](https://wmkeyboard.pages.dev/typing/power-saving/) and the lock screen have already taken away, and AI is hidden until a provider is set up. Hiding a tool from the toolbox is not switching it off: turn off **Show in toolbox** on the tool's own page to keep, say, the AI chip here while the AI tool stays out of the toolbox grid.
- **Nothing is stored and nothing is sent by the keyboard.** Every action either edits the field you are already in, or hands the text to an app you picked. Grammar and the Bengali conversions run on the device. The one exception is DeepL Write, which you set up yourself: it sends the selection to DeepL.
- **Before your first unlock**, only the actions that touch the current field survive: the edits, the rewrites and Find. Paste needs a clipboard that is still locked, Speak needs a speech engine, Edit word needs the personal dictionary, which is locked too, and everything under Open in would have to start another app.
- **A very long selection gets only the actions that need no reading.** Past about 4,000 characters the keyboard stops reading the selection, so the case options, the rewrites and the entity actions all stand down. With **Show on every selection** off, which is how it ships, the bar goes away entirely and the toolbar's own [Copy tool](https://wmkeyboard.pages.dev/tools/text-editing/) is the answer; with it on, Select all, Copy, Cut, Paste and Delete stay on the bar, because the app does their work and the length costs them nothing.
- **The bar follows the selection, not the caret.** Extending a selection with [selection mode](https://wmkeyboard.pages.dev/tools/text-editing/#selection-mode) or a [trackpad](https://wmkeyboard.pages.dev/tools/trackpad/) drag re-reads it as you go, so the actions change under your finger as the selection grows into a whole phone number.
- **Updates keep your list.** An update that adds actions switches on only the new ones it ships on, and puts them at the end of your order. What you switched off stays off, and your order stands. (Up to 0.6.5, every update that added an action reset the whole list to the shipped one.)
---
# System spell checker
URL: https://wmkeyboard.pages.dev/smart/spell-checker/
Section: Suggestions & correction
Summary: WM Keyboard as your Android spell-check service, in every app.
WM Keyboard can also stand in as Android's own spell-check service, the one that draws the red underline in any app's text field, no matter which on-screen keyboard is active at the time. Spelling comes from the same word lists the suggestion strip reads, in every language you have a list for. For English, Harper, the offline engine behind the in-keyboard [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/) tool, adds grammar marks on top.
## Turning it on
Installing WM Keyboard doesn't make it your spell checker. That's a separate, OS-level choice, the same way you'd pick a default browser. The quickest route starts inside the app:
1. Open the Grammar check tool's settings.
2. Tap **Use Harper everywhere**, under the **System-wide** group. Android's own Spell checker screen opens. On OEM builds that hide that screen, the input-method settings screen opens instead.
3. Select **WM Keyboard spell checker**.
The same screen also opens from your phone's **Settings** directly: search for **Spell checker** (usually under System, Languages & input). There's one entry for WM Keyboard, offering a language for each word list it can download.
## What it checks
- **Spelling, every language.** Each word is checked against the word list you downloaded for that language (English has a built-in one), plus the words the keyboard has learned from you. Case doesn't count against a word: "github" passes because "GitHub" is in the list. The fixes offered are the ones autocorrect and the strip would give. A language with no word list on the phone gets no underlines at all, rather than every word marked wrong. The same goes for a word in another alphabet: while it checks English, a Russian word is left alone rather than marked, and a Russian list leaves an English word alone.
- **Grammar, English only.** Harper checks the sentence around the words and marks what it finds, such as a wrong "their" or a doubled word. On Android 12 and up these are reported as grammar marks rather than typos, and apps that tell the two apart draw them differently. Harper's own spelling rules are left out here, so the word lists stay the only judge of spelling, and a grammar mark never sits on top of a misspelt word.
- **Every build.** A build made without Harper's native library still checks spelling; only the grammar marks are missing.
Dialect (American, British, Canadian, Australian) isn't a separate choice on Android's screen. It follows the same **English dialect** setting the in-keyboard Grammar tool uses, so the grammar marks in other apps match what the panel would have flagged.
Earlier versions listed two entries under the same name, one for each half. If you had picked the word-list one, pick **WM Keyboard spell checker** again after updating; if you had picked the other, nothing changes.
## How it differs from typing suggestions
The system spell checker and WM Keyboard's own suggestion strip are separate mechanisms that share the same word lists and the same Harper engine underneath:
- **Where it runs.** The spell checker is a service of its own inside the WM Keyboard app, wired to Android's text-service framework rather than to the keyboard. It works in any app, with any input method, and keeps working when WM Keyboard isn't your current keyboard at all. In-keyboard [autocorrect and suggestions](https://wmkeyboard.pages.dev/smart/autocorrect/) only run while WM Keyboard itself is the active keyboard.
- **Reactive vs. proactive.** The spell checker only answers when the host app's text-service framework asks it to, underlining and offering fixes on request. [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) works the other way round. It fixes typos the moment you press space and offers next-word predictions as you type, gated by its own confidence threshold.
- **What triggers an underline.** Harper's lints carry a span, an offset and a length, and the spell checker only reports one whose span covers text. A pure insertion, like a missing comma, has zero length and gets silently dropped here, even though the same lint would show up as a fixable chip in the in-keyboard Grammar tool.
## Contacts get priority
Turn on **Suggest contact names** under Typing settings and the spell checker loads the same contact index the in-keyboard suggestion strip uses. It then treats that index specially in two ways:
- A word that matches a contact's name or a contact's email address is never flagged as a typo. No underline appears under a name it recognizes, in any app.
- A mistyped word close to a contact's name (a one-letter typo, or a prefix of one) is offered first in the correction menu, ahead of the word-list fixes. Addresses are only used to suppress the underline. A mistyped address is never corrected toward the right one.
One toggle covers both: **Suggest contact names** loads names and email addresses for the spell checker. The separate **Suggest contact emails** toggle, also under Typing, only affects the in-keyboard suggestion strip. It has no effect here, which is easy to miss when the two settings sound like they'd overlap. See [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/) for both toggles in full, including what gets read and how.
## Options
Tools / Grammar check
smart/spell-checker-options — The System-wide group on the Grammar check screen: Use Harper everywhere, and Underline only for Android 12 and up.
| Setting | Default | Notes |
| --- | --- | --- |
| Use Harper everywhere | Action row | Opens Android's Spell checker picker; only shown where the build has Harper |
| English dialect | American | Shared with the in-keyboard Grammar tool; changing it here changes both |
| Underline only, no fix popup | Off | Android 12 (API 31) and up only |
**Underline only, no fix popup** keeps the red underline but tells Android's text framework to skip the correction popup. Use it when you want a heads-up that something looks off without a list of replacements getting in the way. The row itself is hidden below Android 12 rather than shown-but-inert, since the underlying flag doesn't exist on older versions.
## Details & edge cases
- **Re-check delay doesn't apply here.** The Grammar check screen also has a **Re-check delay** slider (100–1500 ms, default 350 ms). That only paces how often the in-panel Grammar tool re-lints while you're actively writing. The system spell checker has no debounce. It answers synchronously whenever the host app's framework calls in.
- **Runs even when WM Keyboard isn't your keyboard.** The checker loads its word lists, Harper and, if enabled, your contacts itself, on its own schedule, rather than borrowing anything the IME set up, so it keeps working with any other keyboard app in front. It only needs to be selected as the system spell checker. Which on-screen keyboard you're using doesn't matter.
- **Runs locally, like everything else.** The word lists are files on the phone and Harper is a native library loaded on-device, and there's no network client anywhere in the spell-checker code path. Contacts are read into memory the moment the toggle is on and dropped again the moment it's off, never written to disk. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for what does and doesn't touch the network elsewhere in the app.
- **The Grammar check rows are in Settings search.** "English dialect," "Re-check delay," "Use Harper everywhere," "Underline only, no fix popup," and the four issue-type switches ("Correctness issues" and its three siblings) each turn up there, and each search result lands on the Grammar check screen.
- **Which language gets checked.** Left on **Use system language**, Android checks in the language of the keyboard you're typing with, and only falls back to the phone's language when the keyboard doesn't name one. WM Keyboard always names the active layout's language, even with **List languages in the Android switcher** off, so Indonesian typed on an English phone is checked as Indonesian, by this checker or any other one. Pick a language on the Spell checker screen to pin it instead. A pinned language is the only one checked, so if Russian words get no underlines at all, check that it isn't pinned to another language and that Russian's word list is downloaded.
- **Tapping a marked word in a browser hides the keyboard.** Chrome, Brave and other Chromium browsers open their own correction menu when you tap a marked word, and while that menu is open Android takes the keyboard down. Every tap on the word opens the menu again. Pick a fix from the menu, or tap just past the word to put the cursor there instead.
---
# Suggestions & prediction
URL: https://wmkeyboard.pages.dev/smart/suggestions/
Section: Suggestions & correction
Summary: How the suggestion strip picks the words it offers you, and how it learns the ones that are yours.
The suggestion strip is the row of tappable words above the keys. It offers completions while you're mid-word, corrections when what you typed doesn't look like a real word, and next-word predictions once you've finished one. All three come out of the same on-device engine, which also learns your own vocabulary and phrasing the more you type.
smart/suggestion-strip-anatomy — Word candidates in the suggestion strip, mid-word.
This page covers the strip itself: its layout, the completion/correction/prediction engine, and how ranking adapts to you. A few things that also live in the strip have their own pages: [autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/)'s confidence math, the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) screen, the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), [smart chips](https://wmkeyboard.pages.dev/smart/chips/) (calculator, currency, unit conversion), [password-manager chips and smart replies](https://wmkeyboard.pages.dev/smart/autofill/), [contact and app-name suggestions](https://wmkeyboard.pages.dev/smart/contacts/), and [emoji suggestions](https://wmkeyboard.pages.dev/emoji/customization/). Each is linked from the relevant section below rather than repeated here. Everything on this page is about the Latin/English-style strip. A conversion IME like pinyin or kana→kanji draws a different, scrolling candidate row that isn't covered here.
## What's on the strip
Word candidates fill three slots by default. **Number of suggestions** (see [Options](#options)) takes that as low as two or as high as six. Turn on **More slots for short words** and the strip grows past that number, by up to three slots, whenever the next words are short enough that every one still fits its share at full size: `a`, `is`, `it`, `in` can get five slots on a phone where `international` keeps three. Long words get the usual number again, so nothing is ever shrunk to make room. It does nothing while **Scroll the suggestions** or **Keep suggestions in place** is on. The strongest candidate, the one autocorrect would pick, sits in the middle with the runner-up to its left. That's the layout most keyboards use. Turn off **Best suggestion in the middle** and the candidates rank left to right instead.
**Swipe down for more suggestions** (off by default) is for the words that don't fit. Swipe down on the strip while it has words and a grid of up to 24 candidates covers the keys, best first, the strip's own words leading. Between words, the predictions for the next word page the same way. Tap one to use it as if you'd tapped it on the strip. Swipe the strip again, or tap **Back to the keys** at the top of the grid, to close it. With this on, a downward swipe over suggestions opens the grid rather than hiding the keyboard; over an empty strip it still hides it if that setting is on.
After the words the grid has two more groups, each under its own switch below that one. **Emoji in the grid** (on) adds up to 24 emoji that match the word, the same search the emoji panel runs; tapping one works like an emoji on the strip. **Fancy text in the grid** (off) adds the word written in every fancy text style, so you can pick a look by seeing it; tapping one puts it in place of the word, and nothing about it is learned. The fancy group only appears while the Fancy text tool is on, and never in a password field.
**Swipe up for more suggestions** (off by default) works the way HeliBoard's does. Swipe up on the strip while it has words and up to 12 more candidates open just above it, over the app, without covering the keys. Keep your finger down and slide it onto the word you want; the word under your finger lights up. Lift on it to use it, or lift anywhere away from the words (back down on the strip, say) to close them without picking anything. The best of the extra words sit in the row nearest the strip, so they're the shortest slide away.
**More suggestions in a grid** (off by default, shown once either swipe is on) lines the extra words up in columns, one per suggestion slot, the way the strip itself is laid out, for both the swipe-down grid and the swipe-up words. Off, each word takes only the width it needs.
Slots are equal width, so a long word gets no more room than a short one. Rather than cut it off, the strip shrinks it to fit: the point size drops first, down to about two thirds of normal, and past that the letters themselves narrow a little. Most words that would once have read "internationali…" now fit whole, set smaller and tighter. The shrinking itself has no settings, though the size it shrinks from does: **Suggestion text size** under Appearance / Toolbar runs from 80% to 160%. Short words are never scaled, and a word that already fits is drawn at full size. **Suggestion spacing**, on the same screen, is the gap either side of each word inside its slot, 6 dp out of the box. Lower it and a long word has a little more room before it shrinks.
If you'd rather the strip never shrank anything, turn on **Scroll the suggestions**. Every word is then drawn at its natural size and the row scrolls sideways when the words overrun the strip. The row also runs on past **Number of suggestions**, through every word the keyboard found for what you typed, up to two dozen, so a word that ranked fourth (an Avro reading, say) is a swipe away instead of nowhere. A slot still gets at least its equal share of the width, so a set of short words looks exactly as it does with the setting off. It's the setting for a narrow phone that wants five or six candidates: at fixed widths those slots are too tight to read. A new set of candidates always starts back at the left edge. The strip a panel's own search box gets (the clipboard, emoji, notes and dictionary searches, the to-do box) scrolls the same way and runs just as deep. Turn on **More suggestions arrow**, under the same setting, and when there are more words than slots a ▾ arrow at the end of the strip opens every word the keyboard ranked in a grid over the keys, the same grid Chinese Pinyin's arrow opens for its characters and **Swipe down for more suggestions** opens. Tap the arrow again to close it. It ships off, so the words keep that space; the swipe opens the grid either way. Neither the arrow nor the swipe is there under a panel's search box, where the panel is in the grid's place.
smart/suggestion-strip-emoji-tail — Word candidates followed by predicted emoji.
Up to four predicted emoji can ride after the word chips (see [Emoji in the strip](#emoji-in-the-strip)), and a row of punctuation marks can ride after that (see [Punctuation chips](#punctuation-chips)), but never both at once. The emoji tail always wins the shared space. Punctuation only appears when no emoji prediction is offered. The tail sits on top of **Number of suggestions**: three words and an emoji is four chips. Turn on **Emoji takes a slot** (see [Options](#options)) and the best emoji takes the last slot instead, the same width as a word, with one word fewer, so the strip never shows more chips than the number asks for. **Suggested emoji** sets how long the tail can get, from one emoji to four.
The words share the strip between them by default: two words split it in half, and an emoji tail, the punctuation marks, a tool's keyword chip or a rewrite chip (join, revision, undo) takes room from them, so the best word can drift from one keystroke to the next. Turn on **Keep suggestions in place** and the strip is always cut into **Number of suggestions** slots of one width. A slot with nothing to show stays empty, and the best word keeps its slot. With **Best suggestion in the middle** on that is the second slot, the runner-up on its left and the third word on its right, exactly where they sit on a full strip. Anything that is not a word takes a slot of its own rather than pushing the words aside: a suggested emoji or the punctuation marks the last slot, a tool's keyword chip or a rewrite chip the first. It has no effect while **Scroll the suggestions** is on, since that strip sizes each word to fit.
**Coloured suggestions** puts each word on a rounded pill in the theme's accent colour instead of separating the words with thin lines, with the best word's pill the deepest. With a Material You theme the colour comes from your wallpaper, the way SwiftKey shades its suggestion bar. A theme can give the pills a colour of their own instead of the accent: **Suggestion pills** and **Main suggestion pill** in the theme editor, on the suggestion strip's sheet.
Two more colours mark single words rather than slots. Both sit under Appearance / Toolbar and both stay off until you pick one. **Autocorrect word color** paints the word autocorrect has decided to put in for a space, and no other word, so the colour is a promise about what the next commit types. **New word color** paints a word that no word list, your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/), your contacts or the spell checker knows. That is usually the word you are typing, which lets you see it would be new to the dictionary before you add it from its hold menu or let the keyboard learn it; the colour goes the moment either happens. Where both apply the autocorrect colour wins, and neither is used on a layout that turns your keys into another script, where the strip shows readings rather than the word as typed.
When one of the keyboard's own text boxes has the keys (a panel's search bar, the AI prompt, Find and replace, a plugin's field), the strip moves to the row directly above the keys, since the panel has taken the toolbar's row. It shows the same words for the box's text that it would for your field, and tapping one replaces the word at the box's caret. The exception is the emoji panel's search box, which is querying the emoji catalogue rather than writing prose: its strip offers [emoji search terms](https://wmkeyboard.pages.dev/emoji/picker/#searching-for-an-emoji) instead of dictionary words. See [Panels and captured keys](https://wmkeyboard.pages.dev/tools/overview/#panels-and-captured-keys).
The strip shares its row with the toolbar, and several other features can claim that row instead of word candidates. A [smart chip](https://wmkeyboard.pages.dev/smart/chips/) (calculator, currency, unit conversion) takes the full row when it has an answer. [Autofill chips](https://wmkeyboard.pages.dev/smart/autofill/) take it over completely on a login field. The system's [smart replies](https://wmkeyboard.pages.dev/smart/autofill/#smart-replies) take the row when nothing is typed, then ride the tail once you start typing. A "paste your last copy" or one-time-code chip can appear from the [clipboard tool](https://wmkeyboard.pages.dev/tools/clipboard/). Typing `:` turns the row into [inline emoji search](https://wmkeyboard.pages.dev/emoji/customization/#inline-emoji-search-and-shortcodes). Any of these counts as "the strip has something to show," so the row rests on suggestions rather than falling back to the toolbar. **Suggestion strip always visible** (see [Options](#options)) keeps the strip as the resting view even before any of them has anything to offer.
## Completions, corrections, and predictions
One engine produces everything in the word-candidate slots, drawing from several sources depending on what you've typed:
- **Completions.** While you're mid-word, every source that has a word starting with what you've typed contributes a candidate: the active dictionary, any downloaded or custom-imported dictionary, a secondary language's dictionary if you type bilingually, your own [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) of learned words, and (if you've turned them on) [contacts, contact emails, and app names](https://wmkeyboard.pages.dev/smart/contacts/). If the only thing wrong with what you typed is a missing space, as in "ofthe", an "of the" candidate is offered too. A word joined with a hyphen is completed a part at a time: `auto-sp` offers `auto-space` and `seventy-ni` offers `seventy-nine`, as long as the parts before the last hyphen are words, even when the lists don't hold the whole compound.
- **Corrections.** If what you've typed matches no known word anywhere, the engine walks the dictionaries for words a short edit away: a letter dropped, added, doubled, swapped with its neighbour, or substituted. A word of five letters or more may be reached in two such edits; anything shorter gets one, since a two-edit fix on a four-letter word explains almost anything. Each candidate is scored by how plausible that slip is on your active keyboard layout, and by where your finger landed. The best-scoring one appears as an ordinary tappable candidate, not only as a silent autocorrect target. So an ambiguous typo like "helo", evenly split between "hello" and "help", still gets you both real words to pick from, even when [autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) itself stays quiet because neither candidate clearly wins. A word that is already known keeps its two nearest one-letter slips on the strip, ranked below it unless the words before it favour the slip: "is" also offers "us", one key over. Up to 0.6.3 a known word got no corrections at all, which hid the word you meant whenever the slip happened to spell another word, as one often does in a big downloaded list such as Arabic's, and that included words from a secondary language's list.
- **Predictions.** With nothing composing, right after you finish a word, the strip predicts what comes next instead. Your own learned pairs come first, the three-word context ahead of the two-word one. Next comes the following part of a contact's name, if the previous word matched one. Then your own gappy habits: what has tended to follow the word two back one word later, and the word three back two words later, so "how can someone" can still offer "help". Then a downloaded n-gram pack for your language, if you have one installed. Last is a small bundled set of common English pairs, so a fresh install has something to offer before you've typed anything of your own. If nothing knows the word you just typed, a name or a typo, the strip falls back to what tends to follow the word before it rather than going quiet.
- **Openers.** An empty field, the space just after a full stop, and the start of a new line are all the start of a sentence, and the strip predicts from that the same way it predicts from a word: the openers you use come first, then a bundled set of common English ones, so there is something to tap before you have typed a letter. A new line counts because it starts a new item or paragraph, so the strip no longer offers words that would follow the last word of the line above. The word you pick is learned as an opener too, so the list becomes yours. Openers only appear with **Suggestion strip always visible** on. With it off, an empty field, a new line and the space after a full stop rest on the toolbar with no arrow back to the strip, and the candidates come up with the first letter you type.
smart/suggestion-strip-ambiguous-correction — An ambiguous typo offered as two tappable candidates instead of being auto-corrected.
Candidates from every source merge into one ranked list, and the sources don't count equally. Each is weighted well above plain dictionary frequency, so a source that's more clearly you (or more clearly what you're reaching for) dominates once it has anything to offer:
| Source | Weight |
| --- | --- |
| Bundled/downloaded dictionary | baseline. A downloaded list counts words on a far bigger scale than the built-in one, so it's scaled down until its commonest word matches the built-in list's, keeping its own order |
| Custom-imported dictionary | ×100, or scaled down the same way if the list carries real word counts |
| Secondary-language dictionary | ×40, scaled 0.5×–2× by how much you mix that language in. A downloaded list with real word counts is capped lower, so its commonest word sits at half the primary's commonest and never buries a word you typed in full in your main language |
| Your personal dictionary | ×500 |
| App names | ×400 |
| Contact names | ×3,000 |
| Contact emails (2+ character prefix) | flat 4,000 |
Tap a candidate to insert it (see [Options](#options) for the space that follows). It's cased to match what you typed: a leading capital gives you a capitalized candidate ("Hte" → "The"), and typing in all caps gives you the candidate in all caps ("HTE" → "THE"). Anything else is inserted as stored.
When the chip is replacing a word that's already sitting in the field, it takes that word's capitals instead. This is the strip after a swipe or after handwriting, where the chips are other readings of the word that just landed, and it's also a word you tapped back into mid-sentence. So if auto-capitalization gave you "Van" at the start of a sentence and you swap it for "can" off the strip, you get "Can", and swapping a shouted "VAN" gets you "CAN". Only capitals that Shift, caps lock or auto-capitalization put there carry over. A name's or an acronym's own capitals are its spelling, not yours, so swapping "Joe" for "javelin" mid-sentence gives "javelin", and "QWERTY" for "qwery" gives "qwery". At the start of a sentence the new word still gets its capital. The same goes for the [recent words](#fixing-a-word-youve-typed-past) row and for a word picked from **Synonyms**.
## The context model
Everything above reads at most three words back. **Context model** (Typing / Automatic corrections, on) adds a small neural language model that reads the whole sentence so far. It is a file you download per language, from the language's own screen under Languages & layouts (the **Context model** row, about 10 MB; the Download button also offers a **Large** English model of about 17 MB, which predicts the next word about a point more often and takes longer on each word on a slow phone, and the row's menu switches between the two later), and it runs on the phone: nothing you type leaves it. English has one, German has one, and so does romanized Bangla, whose model reads English and Banglish in the same sentence. A language without a model keeps everything else on this page exactly as it was.
The model answers one question once per word you commit: given the sentence so far, how likely is each word of its vocabulary to come next. That one answer serves the strip three ways.
- **Predictions.** Its likeliest next words join the next-word strip, after your own learned pairs and before the downloaded word pairs, so "I was wondering if you" offers "could" and "would" where the pairs alone could only see "you". Measured on sentences the model had never seen, top-one next-word prediction rose from 14% to 20% (top three from 23% to 34%), and the letters you could leave untyped by taking the first suggestion from 38% to 44% on careful typing.
- **Ranking.** Its opinion of each candidate for the word you are typing joins the score the strip is ranked by, before the word pairs have their say: a candidate the model expects more than the strip's current leader moves up, one it expects less moves down, by a bounded amount. The keys you hit still outweigh it, so a far-fetched word the sentence happens to predict cannot climb over a word your taps spell. Measured on sentences the model never saw, the strip's first word is right about half a point more often on careful typing (87.2% to 87.5%), 3 points more on fast typing (79.5% to 82.7%) and 4 points more on sloppy typing (72.1% to 76.0%), where the keys say least.
- **Autocorrect.** Its score for a candidate is one of the things the [learned gate](https://wmkeyboard.pages.dev/smart/autocorrect/#the-learned-gate) weighs before a correction is applied.
Each model has a vocabulary of about twenty thousand words plus four thousand word pieces. A word outside it, a name, a term you taught the keyboard, a word from another language, is handled one of two ways. A model that carries word pieces spells it out of short letter groups and still gives it an opinion, so a name the sentence calls for is not held back for being new. While you type, this is done for the few words close enough to the leader for the model to change their order, four at most on each letter. A model without pieces gives no opinion: the word is neither lifted nor sunk, and your dictionary ranks it as before. The sentence it reads begins at the last full stop, question mark or exclamation mark, and it also knows where it is being read: a chat, an email, a search box or anywhere else, and whether the app is one you write casually in (the same lists **Adapt to app tone** uses), so a chat leans conversational and an email formal.
**Phrase suggestions** (Typing / Automatic corrections, off) lets the model offer the next two or three words as one chip after a space, when it is sure of each of them in turn: "going to be" rather than "going". Tap the chip and all of them go in. Nothing is offered when the model is not sure, which is most of the time.
**Re-check the previous word** (Typing / Automatic corrections, on) is the model reading backwards. The word you have just typed often shows what the word before it should have been: "I want to by" is fine until "a car" lands. Once the next word is in, the keyboard asks the model about the previous word's other readings, the words the strip had for it at the time, with both sides of the sentence in sight, and it keeps asking as the words after it land, so a reading that two or three later words make obvious is still found. When one fits twenty times better than what stands there, a chip offers it, the same chip the confusable rule uses for "their" and "they're". It never rewrites anything on its own, and it only ever offers a word the strip had already found for the taps you made, so it cannot invent a correction from the sentence alone. When you stop typing for a second with the strip empty, it also reads the last few words together once and offers the one swap among them that fits best, if any clears the same bar; the chip goes away as soon as you type on.
## Before you type
**Words before you type**, under Typing / Suggestions, puts your own words on the strip while nothing is typed. At the start of a sentence they replace the usual openers. After a word, predictions keep their slots, your words fill any left empty, and the rest head the grid behind the strip.
In a right-to-left script the best word starts at the right edge. **Right-to-left words start on the right**, in the same place, turned off keeps the strip and its buttons where they are in every other language.
With the toolbox button on the strip, a swipe up on it opens the pinned tools, the same as the arrow.
## When the cursor lands in a word
Put the cursor inside a word you have already written, by tapping it or by walking the caret back with the spacebar, and the strip stops predicting the next word and answers about that one instead. You get its completions and corrections, the same as if you were typing it, plus the word itself as the top candidate, which with **Best suggestion in the middle** on is the middle chip.
A word you highlight gets the same answer, abbreviations with a full stop inside them included: select `i.e.` or `e.g.` and the strip offers that, not the words it showed before. A word that only ends in a full stop is left alone, because a chip picked over it would take the sentence's full stop with it.
Right after a word counts too, as in `next word| to`. There the word is normally picked back up as if you were still typing it, so a letter you type goes on the end of it. Some apps won't let the keyboard do that, and in those the strip still answers about the word: picking a chip replaces it, and typing adds to the text.
That chip is there for the hold, not the tap. Tapping it would replace the word with itself, so it does nothing but put the strip away. Holding it opens the [word menu](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip), which is how you add the word to your dictionary, open its card to respell or re-rank it, delete it, or block it, all without leaving the sentence. Holding any other chip reaches the same menu, and its **Add** and **Search all words** items are about the word the cursor is in rather than the chip you held.
**Only offer new words** (Typing / Suggestions, off) takes that chip away, the same way it takes away the slot the word you are typing normally holds. With it on, every slot offers something different from what is already there, and the word menu is then only a hold on some other chip away.
A word a swipe wrote gets one thing more: the strip leads with that stroke's other readings instead of respellings of the letters standing in the field. See [Going back to a misread swipe](https://wmkeyboard.pages.dev/typing/glide-typing/#going-back-to-a-misread-swipe).
## Retyping the last word
**Retype word** is a text editing action for the moment a word went in wrong and you would rather type it again than fix it letter by letter. It is in the menu a held suggestion opens, as **Retype "word"** naming the word it would take, and you can also put it somewhere of its own: on a key or a flick in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/), on a long-press alternate, a key of the [text editing panel](https://wmkeyboard.pages.dev/tools/text-editing/), a [multi-finger swipe](https://wmkeyboard.pages.dev/typing/gestures/) or a `?123` chord.
It takes the word right behind the cursor back out of the field, with the one space after it if there is one, and leaves the cursor where the word began. The strip then shows that word first, so one tap puts it back exactly as it was. If you swiped the word, the other words that swipe could have been follow it, and taking one of them teaches the keyboard the same thing picking it right after the swipe would. A word you tapped out has no such list, so the strip offers only the word itself. Type anything and the strip goes back to ordinary suggestions.
A space the keyboard added after a picked or glided word goes with it; a space you typed yourself stays. It only ever takes the word at the cursor, never another copy of it further back. Behind the cursor there has to be a word, or a word and one space; after a full stop, a line break or two spaces it does nothing. It also does nothing while text is selected, in a field that won't say what is behind its cursor or exactly where the cursor is, or when the field has changed since the keyboard last looked. A word still being typed is taken back whole, and the strip keeps what it was offering for it.
## Fixing a word you've typed past
Turn on **Recent words** (Typing / Suggestions, off) and the three words you wrote last sit in a row of their own above the suggestion strip, the newest on the right. Each word is followed by what else it could have been, so fixing it is one tap. It is for the word you notice is wrong only after you have typed on. The strip keeps all its slots, and the keyboard grows by the row only while it has words to show; when they don't fit, the row scrolls, starting at the newest. Under Rows & bars / Recent words you can move the row in the row order, put the words at the start of the strip instead so the keyboard never grows for them, or have the row keep its height while it's empty so the keyboard doesn't jump between messages.
What follows a word is what the keyboard had for it as you wrote it: the suggestions on the strip at that moment, and the other readings of its swipe if you swiped it. A word typed where nothing is suggested, such as a browser's address bar, kept nothing, so it gets the dictionary's corrections instead. Tap one and only that word changes, where it stands. The word you are still typing stays as it is, the cursor stays where it was, and the text after the word moves by the change in length. Your old spelling goes to the front of that word's choices, so the same row changes it back.
A word on the row is a place in the text as well as a spelling, so if "form" appears twice, a tap changes the copy it was drawn for and not the other. If the text has changed since the row was drawn, such as an app rewriting it or you editing it by hand, the tap does nothing and the row is read again. The row holds only words you wrote in this field: it starts empty in every new field, and text that was already there never joins it, wherever you put the cursor. It also shows in a browser's address bar, where searches are typed, and never in password fields or while incognito is on. It is kept only while you type, never saved.
## Punctuation chips
Turn on **Punctuation suggestions** (off by default) and a short row of marks (. , ? ! ') rides after the word candidates, set off by a thin divider. Tap one and it commits exactly as typing that key would, so a full stop or comma is one tap away without switching to the symbols layout. Those five marks are only the default. A **Punctuation marks** text field appears under the toggle and takes up to twelve characters, one chip per character, so a Bengali danda or the Spanish inverted marks can sit here instead. Clearing the field puts the five back.
smart/punctuation-chips — Punctuation chips riding after the word candidates.
The row only appears while word candidates are already showing, and only when no emoji prediction has claimed the tail instead.
## Shift re-cases the strip live
Arm Shift and every chip in the strip re-cases in real time to match what would be typed if you tapped it right then. Shift is a latch rather than a hold, so one tap gives you Title Case, a double tap (caps lock) gives you UPPERCASE, and another tap goes back to lowercase. The chips follow the shift state, so they revert the moment it clears, which includes the letter or suggestion that spends it. The whole row follows, not only the top candidate. One thing is left alone: a contact-email suggestion, meaning anything containing `@`, since an address isn't meant to change case.
smart/suggestion-strip-shift-recase — Suggestion chips re-cased to Title Case while Shift is held.
This is a display rule only. It doesn't reject or change which candidate is offered, and it's unrelated to [reverting an autocorrected word](https://wmkeyboard.pages.dev/smart/autocorrect/#undoing-a-correction) with backspace.
## Emoji in the strip
Matching emoji append to the end of the word candidates once you finish typing a whole word. The same mechanism can offer an emoji on an empty buffer once it has learned that a word or two-word phrase you often type is usually followed by one, and after an emoji it offers the words you tend to type next. Emoji suggestions have their own settings and their own page: see [Emoji suggestions while typing](https://wmkeyboard.pages.dev/emoji/customization/#emoji-suggestions-while-typing) for the toggle, the default, and how tapping or holding one behaves. What matters here is the tail of the strip, which fits up to four emoji. The emoji tail and the [punctuation chips](#punctuation-chips) both want that space, and emoji always wins.
## Learning from what you type
Every word you commit, and every pair of words you type back to back, feeds a private on-device vocabulary. Both the completion ranking above and next-word prediction draw from it. How strongly a word is taught depends on how deliberately you typed it. Tapping a suggestion from the strip teaches harder than a word that was merely typed and committed, and a word autocorrect chose for you earns no credit at all, only the word pair around it. The [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) page covers that grading in full, along with adding, removing, and clearing what's been learned. What belongs here is what learning does to ranking: a learned word carries a ×500 weight (see the table [above](#completions-corrections-and-predictions)), so it competes with common dictionary words almost immediately instead of needing to be typed dozens of times first.
If you type in more than one language, the keyboard separately tracks which secondary language you reach for and how often. Older commits decay geometrically, so the weighting follows your recent habit rather than your whole history. That's the 0.5×–2× scaling factor on secondary dictionaries in the table above. It's a deeper multi-language topic than this page covers, and it isn't tied to word-level personal-dictionary learning. A second, much faster signal reads the language of the field you're in right now and leans ranking toward it. That one has its own page: [Mixed-language typing](https://wmkeyboard.pages.dev/smart/mixed-language/).
## Removing an unwanted suggestion
Press and hold the word on the strip. The menu that opens has **Never suggest "word"**, which puts it on the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/), and **Delete "word"** for a word the keyboard can forget: one you learned, one still waiting to be learned, one in Android's dictionary, one you adjusted the rank of, or one from a word list you imported. **Forget what you learned about "word"** sits beside it for a list word you have taught something: it drops the learning, its capitals and rank included, and keeps the word. **Synonyms** lists words with the same meaning, and tapping one puts it in place of the word. **Edit** opens the word card, which carries Never suggest and Delete and the rank controls besides. With [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) on, selecting a word in the text offers **Edit word**, which opens the same card for it. The [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip) page describes the menu and the card in full, and **Word menu** under Typing / Suggestions picks which of the optional items it shows.
The same two lists are editable from settings, if you would rather not chase the word on the strip: the [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) screen for learned words, and the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) screen for words that should never be offered or corrected to, whether or not they were ever learned.
## Options
Typing / Suggestions
smart/suggestions-settings-group — The Suggestions group on the Typing settings screen.
| Setting | Default |
| --- | --- |
| Suggestions | On |
| Punctuation suggestions | Off |
| Number of suggestions | 3 (range 2–6) |
| Swipe down for more suggestions | Off |
| Swipe up for more suggestions | Off |
| More suggestions in a grid | Off |
| Emoji takes a slot | Off |
| Suggested emoji | 4 (range 1–4) |
| Keep suggestions in place | Off |
| Coloured suggestions | Off |
| Scroll the suggestions | Off |
| Suggestions in every field | On |
| Suggestion strip always visible | Off |
| Best suggestion in the middle | On |
| Shorten long words | In the middle |
| Numbers from the letter keys | Off |
| Remember numbers you type | Off |
| Space after a suggestion | On |
| Space after a suggestion (per language, under Languages & layouts) | On (Off for Bangla) |
| Block offensive words | On |
| Context-aware suggestions | On |
| Context model | On |
| Re-check the previous word | On |
| Phrase suggestions | Off |
| Adapt to app tone | On |
| Suggestion blacklist | Empty |
**Suggestions** is the master switch for the whole strip described on this page. Turn it off and completions, correction candidates and next-word prediction all stop. [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) keeps working: while its own toggle is on, the word you're typing is still underlined as it's composed, and space or Enter still corrects it, fixes a missing apostrophe and learns it. Turn both off and letters go straight into the field with no underline. **Number of suggestions** is how many word slots the strip splits itself into. Three suits a phone; a tablet or a landscape screen has the room for more, at the cost of each candidate getting narrower. **Scroll the suggestions** drops the fixed slots for a row that scrolls sideways, so more candidates cost nothing in legibility (see [What's on the strip](#whats-on-the-strip)). **Suggestions in every field** overrides the handful of apps that ask the keyboard to hide suggestions on an ordinary text field. It also covers a browser's address bar: with it on, the words you type there are completed on the strip, though never autocorrected. Password fields and structured keypads (number, phone, and similar) never show suggestions whatever this is set to. **Space after a suggestion** adds a trailing space when you tap a candidate, the same as finishing a word yourself would. When the word you picked already has a space after it, as it does when you go back and fix a word mid-sentence, no second space is added. The caret moves past the space that's there instead, so the next word you type starts after it rather than running into the one you just picked. The same goes for a chip that replaces a word in the middle of a sentence, with the cursor inside the word or the word highlighted, and for one picked in a browser's address bar. In front of a mark that closes up onto the word, such as `?`, `,`, `.` or a closing bracket or quote, no space is put in: replace `question` in `A question?` and you get `A questionable?`. The space waits instead, and if you go on typing a word at that spot it goes in first, so `A questionable spa…?` comes out without a press of the spacebar. A mark typed there, or moving the cursor, drops it. A word typed into the middle of another one, with letters on both sides of the cursor, gets no space at all, since one would split the word in two. Each language also has its own **Space after a suggestion** switch, under Languages & layouts / your language / Spacing, on by default for every language but Bangla. Turn it off for one language and picks made while typing it go in bare, while every other language keeps the space. That suits Bangla, where a picked word often takes a case marker, postposition or suffix straight after it, and a kar picked in the middle of a word must not split it, so Bangla starts with it off. The main switch still rules every language, and the per-language rows are greyed out while it's off.
None of these reach a conversion keyboard. Chinese, Cantonese and Japanese put the composer's own character candidates on the strip, and that list is how the language is typed rather than a guess about it, so it survives **Suggestions** being off, a field that asks for a silent strip, and a password box alike. See [Chinese, Japanese & Korean](https://wmkeyboard.pages.dev/languages/cjk/).
**Numbers from the letter keys** (off) is for boards with the number row hidden, where each top-row letter carries a small digit in its corner. When every letter of the word you're typing sits on one of those keys, the strip's last slot also offers the number those digits spell: `qwe` on QWERTY offers `123` beside the words, and one tap types it. It reads the keys you pressed rather than the letters, so AZERTY's `azerty` spells `123456` and a layout with no digit hints offers nothing. A key's digit counts wherever its popup lists it, so `a` is still 1 with à ahead of it. The number is never the first slot, never what a space commits and never an autocorrect target. Two letters at least, so a lone `I` stays a word rather than an 8. Five digits or more are grouped the way the [number chip](https://wmkeyboard.pages.dev/smart/chips/) groups them, under its **Grouping style**, as long as **Group long numbers** is on. With the number row shown the digits have keys of their own, nothing is offered, and the row is hidden.
**Remember numbers you type** (off) completes the numbers you type often, the way Samsung's keyboard does. A number of three digits or more that stands on its own is kept when you press space or enter after it, or leave the field. The next time you type its first digits, the strip shows the numbers you've typed that start with them, the most used first, and one tap replaces your digits with the whole number. No space follows it, since a number is as often followed by a full stop as by a word. A number is only one of its own after a space, at the start of the text, or after `+`, `(` or `#`, so the `880` of `+880` counts. Digits stuck to letters (`win10`) and the pieces of a number split by a comma, a colon or a dash (`1,234`, `12:30`, `0171-234`) are left alone. Nothing is kept from a password field, in incognito, or while **Learn from typing** is off. Number and phone fields have no strip at all, so this works in ordinary text fields. The list stays on the phone, holds up to 100 numbers, and is deleted with the other learned data under [Storage](https://wmkeyboard.pages.dev/privacy/data/).
Seven of the rows above aren't in the Suggestions group at all. They live under Typing / Automatic corrections, and they're listed here because what they do is change the strip:
- **Space after a suggestion** (on) is described above, and sits with the other spacing rules rather than with the strip.
- **Block offensive words** (on, and only visible while autocorrect is on) uses the same filtering mechanism as the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) to keep a bundled list of profanity and slurs out of the strip, not only out of autocorrect.
- **Context-aware suggestions** (on, also only visible while autocorrect is on) reorders the top eight candidates using the words you typed before them: your own learned word pairs and triples, a downloaded n-gram pack if you have one, common English pairs, and words already used in this conversation. It only reorders when there's real evidence to reorder on, so an unfamiliar context keeps the usual order. It never changes which word autocorrect would apply.
- **Context model**, **Re-check the previous word** and **Phrase suggestions** (the first two on, the third off, all only visible while autocorrect is on) are the neural model described in [The context model](#the-context-model): the sentence-aware next words, the chip that re-reads the word before, and the few-words chip.
- **Adapt to app tone** (on, and visible whatever autocorrect is set to) tilts ranking by where you're typing. Chat-speak like "lol" and "gonna" ranks a little higher in messaging apps and a little lower in email apps and email fields. It's ranking only: no word is ever hidden or blocked, and autocorrect doesn't change. The context model reads the same lists to know where it is.
A physical keyboard can also pick a strip candidate by number without touching the screen (see [Hardware keyboard](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) for how that's bound).
## Details & edge cases
- **A known word gets only its two nearest slips.** Anything in the active dictionary, a custom or secondary-language dictionary, or your personal dictionary is offered as-is, with its completions and at most two words one edit away, each scored as at most half as likely as the word you typed before context moves it. Wider corrections, two edits away, are only walked for a word nothing knows. Autocorrect never replaces a known word either way.
- **A dictionary word and a contact name can collide on capitalization.** A contact's name keeps its own capitalization ("Wasi") independent of how a dictionary word would be cased. If the same word would otherwise appear twice, once lowercase and once capitalized, only the better-scored one is shown.
- **Missing-space splits need a real word on both sides.** "ofthe" only becomes "of the" if both halves are independently known words. An arbitrary split of an unknown word never appears as a candidate. Turn off **Add missing spaces** under [autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/) and no split appears at all, so "history" half-typed is never offered as "his to".
- **The word as typed keeps a slot.** Unless **Only offer new words** is on, the strip always holds what you have typed so far, exactly as typed, in the slot left of the middle, even when the engine has no idea what the word is, and in a browser's address bar as much as a text box. A long word the dictionary lacks no longer empties the strip.
- **The word as typed can go first.** **Word as typed first** (Typing / Suggestions, off) draws it in the leftmost slot, wherever the engine ranked it, the way Multiling O Keyboard does. Only the drawing moves: the word a space writes is still the one in bold, and a hardware keyboard's digit badges follow the slots as drawn.
- **A long word borrows room from short ones.** When one word would not fit its equal share of the strip and the words beside it are short, it takes the room they leave, so "international" next to "a" and "it" is drawn whole instead of shrunk. The short words keep exactly the width they need. While every word fits an equal share, the slots stay equal, so each word sits where your thumb expects it. A scrolling strip and **Keep suggestions in place** keep their own widths.
- **Shrinking a word has a floor, and past it the ellipsis is back.** The two stages together fit roughly twice the slot width. A word longer than that is cut, and so is one in a slot squeezed narrow by a long smart reply or a full row of predicted emoji riding the tail. The cut goes in the middle of the word by default, as most keyboards do, so its start and its ending both stay readable: "Punct…tion". Set **Shorten long words** to At the end for "Punctuat…" instead.
- **Only the word candidates scale.** Conversion candidates (Pinyin, kana, jyutping) keep their fixed larger size, since Han and kana need it, and they scroll instead of splitting the bar evenly. Predicted emoji, punctuation chips and the clipboard pill are all fixed-size too.
- **The strip fades rather than snapping.** The composing buffer or field can empty out completely: you delete back to nothing, clear the field, or back out of a `:` emoji search. The last set of candidates stays visible and fades out over a short animation instead of disappearing instantly.
- **A contact-email suggestion is never added to your personal dictionary**, since an address isn't a word to learn. Tap it inside a field the keyboard recognizes as an email input and it commits with no trailing space, because the address is usually the whole field. Tap it from an ordinary text field and it gets a trailing space like any other suggestion. [Contact-aware typing](https://wmkeyboard.pages.dev/smart/contacts/) has that behavior in full.
For the word-typo confidence math, and for how a correction is applied automatically instead of only offered, see [Autocorrect](https://wmkeyboard.pages.dev/smart/autocorrect/). [Personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) has the full story on what gets learned, how strongly, and how to review or clear it. To keep a specific word out of the strip entirely, see the [suggestion blacklist](https://wmkeyboard.pages.dev/smart/blacklist/).
---
# Emoji customization
URL: https://wmkeyboard.pages.dev/emoji/customization/
Section: Emoji & expression
Summary: Emoji fonts, keyword language packs, predictions and the emoji row.
The emoji panel itself (search, skin tones, favourites) is covered in [Emoji picker & search](https://wmkeyboard.pages.dev/emoji/picker/). This page is about the settings that shape it: which font draws the emoji, which languages emoji search understands, when emoji get suggested while you type, and whether a dedicated row of them sits above the keys.
emoji/settings-overview — The Emoji settings screen, with the live font preview row.
## Choosing an emoji font
Emoji / Emoji style / Emoji font
WM Keyboard ships no emoji font of its own. It draws emoji with whichever one you pick here. A preview row on the settings screen renders 😀😂🥰😎🤔👍❤️🎉 through the real rendering pipeline, so what you see there is what the keyboard shows, not a mockup.
- **System** (default): your phone's own emoji pack. On Samsung phones that's Samsung's set, not Google's.
- **Google**: fetches Noto Color Emoji, the stock-Android look, once through the system font provider and caches it on the device.
- **Installed**: a list of every emoji font you've installed, picked with one tap. Fonts arrive there from [Addons](https://wmkeyboard.pages.dev/addons/installing/), such as Twemoji or OpenMoji, or from your own storage: **Add emoji font file** under the list takes a `.ttf` or `.otf`, keeps it, and selects it. That's the route on an offline build, and once a few are in the list you can move between them without picking a file again. Deleting the font you currently have selected falls back to System automatically.
- **Custom**: a single font file you import yourself, kept separate from the library. Each import replaces the last one, so use Installed if you want to keep more than one.
Text you send is always plain Unicode, whichever font you pick. The app or phone on the other end draws it with its own emoji font. This setting only changes how emoji look while you're composing them.
An emoji font converted from a color format usually has no variation-selector table, so ❤️ (U+2764 + the invisible U+FE0F that makes it draw as color) can't resolve in it. WM Keyboard strips that invisible selector before handing the glyph to your chosen font, rather than let it fall back to the system font and look inconsistent with the rest of the row.
Not every emoji font your phone can open is one it can draw. Android paints colour emoji from bitmap (CBDT) and layered (COLR) fonts, and the newer COLRv1 kind only from Android 13. A font whose pictures are stored as SVG or as Apple's `sbix`, or a COLRv1 font on an older phone, maps every emoji and then draws an empty space for each. WM Keyboard draws one emoji from the font when you pick it, and if nothing comes out it uses your phone's own emoji font instead, so the panel never turns into a grid of blank cells. The preview row shows the phone's emoji in that case: pick a different font file to get a different look.
## Fixing emoji that won't display
Older phones and some OEM skins ship an emoji font that predates the newest emoji, and those show up as an empty "tofu" box. There's a one-tap fix for that, plus a toggle for tidying away whatever it can't reach.
### Getting a font that has them
Emoji / Emoji style / Get the newest emoji
A card sits under the font picker offering the current Noto Color Emoji, around 10 MB, from WM Keyboard's own addon repository. Tap **Download**. The font installs into the font library and selects itself, so the panel and the keys redraw with a face that actually has the new emoji in it. It selects itself once, as the download lands; a choice you make afterwards, Custom or System, stays put when you come back to this screen. The card then disappears. It comes back as **New emoji are available**, with an **Update** button, when the repository publishes a newer build.
Under the card, **Add emoji font file** takes a `.ttf` or `.otf` from your phone, adds it to the font library and switches **Emoji font** to it, without having to pick **Installed** first.
Switching **Emoji font** to Google is not the same thing, and that's the part that catches people out. A downloadable font is requested by family name with no version attached. The system font provider answers with whichever build of Noto it already has, which on a phone with a recent system emoji font is usually the older of the two, so the boxes stay. Installing a complete addon font like Twemoji or OpenMoji from [Addons](https://wmkeyboard.pages.dev/addons/installing/) works as well, and a **Download more** row right below the picker opens the catalogue filtered to emoji fonts. The download card is the shortcut to the font most people are after.
### Hiding the ones that are left
Emoji / Emoji style / Hide emoji that your device cannot show
This toggle (on by default) checks both your chosen font and the phone's own system font. It only hides an emoji from the panel, search, and suggestions when neither can draw it. An emoji your chosen font is missing but the phone's system font can still draw isn't hidden. It's quietly drawn in the system font instead, in a different style from the rest of the row, so it still gets through. An emoji you [added by hand](#adding-emoji-the-keyboard-doesnt-have-yet) is never hidden.
## Adding emoji the keyboard doesn't have yet
Emoji / Emoji panel / Added emoji
The emoji panel lists the emoji of the Unicode release the app was built with, Emoji 17.0 today. When Unicode publishes a newer one, its emoji can be typed on other phones before an update of WM Keyboard carries them. This screen is the way round that wait. Whatever you add here joins the panel at the end of the tab you pick. Panel search and inline `:word` search find it by its name and keywords, and it can be a favourite and shows up in Recent like any built-in emoji.
There are two ways to add them:
- **Add an emoji** opens a form. Paste the emoji (copy it from a message, a website or Emojipedia), give it a name and a few comma-separated keywords, and pick its tab. ZWJ sequences such as a new family or profession emoji paste the same way as a single character. The form refuses an emoji the keyboard already has and says which tab it is in.
- **Import emoji-test.txt** adds a whole release at once. Download [emoji-test.txt](https://unicode.org/Public/emoji/latest/emoji-test.txt) from unicode.org in a browser, then pick it here. The keyboard adds every emoji in the file that it doesn't have yet, named and sorted into tabs by the file's own groups. Skin tone versions are left out: the panel shows one face per emoji, and the tone picker on a long press only knows the tones of built-in emoji.
Press a row in **Your added emoji** to change it or delete it, or swipe it to delete. When an app update includes an emoji you added, the row says so. Nothing breaks in the meantime: the built-in entry takes over the slot and picks up your keywords, so you can delete yours whenever you like.
Your phone's emoji font has to be able to draw a new emoji for it to look right. If it can't, the form and the row both tell you, and the emoji shows as a box or as its separate parts while you compose. It still goes into your message exactly as typed, so the person you send it to sees it if their phone is newer. [Getting a font that has them](#getting-a-font-that-has-them) fixes the look on your side.
Added emoji are stored with your settings, so a [backup](https://wmkeyboard.pages.dev/reference/settings/backup/) carries them and sync copies them to your other devices. A list holds up to 500 emoji.
## Emoji search in your language
Emoji / Emoji panel / Emoji keywords
emoji/keyword-packs — The Emoji keywords screen, with per-language download and delete controls.
Emoji search ships with English and Bangla keywords built in, so it works out of the box in both languages. For anything else, download a keyword pack. WM Keyboard's data repository carries packs for 126 languages, each a small gzipped file of `{emoji, name, keywords, category}` entries sourced from Unicode CLDR.
Packs stack on top of the bundled English and Bangla keywords rather than replacing them, so downloading, say, Spanish adds Spanish search terms without losing the two you already had.
The examples in this screen's own copy follow the languages you type in. The line ends "…all find 💰", and the words in front of it are up to three of your own: dinero and お金 for a Spanish and Japanese typist, पैसा and деньги for a Hindi and Russian one. It's the same feature either way. Showing you a script you don't read is a poor way to demonstrate it.
Keywords stack, but names don't. Only one name fits in an emoji's long-press popup. A pack's display names are used while you're typing that pack's language, and the emoji's standard Unicode name is used otherwise, so a Spanish pack never renames your emoji while you're typing Bangla.
**Download automatically** (on by default) fetches a keyword pack the first time you enable a language you type in, and skips anything already downloaded. Turn it off and packs only arrive when you tap Download next to a language yourself. A wider switch sits above it: **Download data automatically** under Languages & layouts / Language data, also on by default, covering every automatic download the keyboard makes. With that one off, packs stay put, and this toggle is hidden until it comes back on.
Deleting a downloaded pack leaves a marker behind so automatic download won't quietly re-fetch it. If you want it back, download it again yourself.
You can also import a pack by hand. Two formats work: a plain tab-separated file (`emoji`, comma-separated keywords, optional display name), or the data repository's own JSON file directly. WM Keyboard sniffs the format, so you don't have to convert anything first. Imported packs are kept separate from downloaded ones on disk, but merge into the same search index.
## Emoji suggestions while typing
Emoji / Suggestions / Emoji suggestions
emoji/word-suggestions — Emoji suggestions appended to the strip after typing a whole word.
Turn this on (it's on by default) and matching emoji append to the end of the suggestion strip as you finish typing a whole word. "Birthday" offers 🎂🎉🥳🎁, and it works the same way for Bangla words. It also fires on an empty buffer from words you've just typed, learned over time, so a word like "you" can suggest ❤️ without you typing anything new.
The keyboard learns emoji the way it learns words, as part of the phrase around them. Send "I love you ❤️" a few times and both "you" and "love you" start offering ❤️, the two-word phrase ranked ahead, so "thank you" can learn 🙏 while "love you" keeps ❤️. It works the other way too: after "❤️ see you", typing ❤️ offers "see" as the next word. Learning waits until the text settles, the same as for words, so an emoji you delete straight away teaches nothing. A word the keyboard doesn't recognise never gets an emoji attached, since it might be a typo. **Learn from typing** and incognito's **Pause learning** cover all of it.
The chip doesn't vanish at the space, which is exactly when you'd reach for it. Type "happy birthday" and 🎂 is still there after the space, until the next word replaces it. Four sources feed the match, stacked so a hit from any one of them counts:
- a small curated table, which is where the ordering judgements ("birthday" leads with 🎂, not 🥳) and the Bangla triggers live;
- GitHub's own word-to-emoji data, which covers far more English than a hand-written table can: "hooray", "espresso", "workout";
- the `:shortcode:` names, which mostly earn their place on two-word matches nothing else can reach. "Alarm clock" offers ⏰, "crossed fingers" offers 🤞. Two-letter codes are left out on purpose, because `:it:` and `:us:` are flags, and firing them at anyone typing "it" or "us" is the wrong-emoji case this whole feature avoids;
- exact keyword hits from the emoji catalog, in whatever languages you have keyword packs for.
**Press an emoji suggestion**, the row under the switch, decides what happens when you tap one:
- **Replace word** (default, Gboard-style): swaps the word you typed for the emoji. Typing "birthday" and tapping 🎂 leaves only 🎂.
- **Keep word**: adds the emoji after the word instead, giving "birthday 🎂".
**Long-press a suggestion to get the other one for that insert.** With Replace word set, holding 🎂 keeps the word and gives you "birthday 🎂"; with Keep word set, holding it replaces the word. The setting decides which of the two a tap does, and the hold is the one-off for the other, so neither costs a trip to settings. Under TalkBack the long-press is announced by what it will actually do, not as "long press".
Both of those describe tapping while the word is still being typed. Tap the chip after the space and there's no word left to replace, so the emoji lands at the cursor under either setting: "happy birthday " becomes "happy birthday 🎂". A hold there does the same thing as a tap, since there's no word left for the two modes to disagree about.
## Inline emoji search and shortcodes
Typing a `:` isn't part of the suggestions setting above. It has its own toggle, and that toggle lives on the Typing settings screen rather than the Emoji screen:
Typing / Suggestions / Inline emoji search
With this on (the default), typing a colon at a word boundary turns the whole suggestion strip into an emoji search: `:cat` offers 🐱 and friends, replacing ordinary word suggestions entirely while the search is active. Backspacing over the colon returns to normal suggestions.
emoji/inline-colon-search — Typing a colon turns the suggestion strip into emoji search results.
The same toggle also covers exact shortcode names. If you type the *closing* colon and the text between the two colons is an exact match (`:tada:`, `:thumbsup:`), the whole thing is replaced with the emoji immediately, with no tap needed. A name that's only a partial or unknown match is left as ordinary typed text. An unrecognized shortcode is never quietly eaten.
An emoji you pick from the search, by tapping it or by closing the colon, counts as used the same as one tapped in the panel: it moves to the front of the Recent tab and the emoji row.
## The dedicated emoji row
Emoji / Emoji row / Emoji row
A row of your own emoji, similar to Gboard's, that sits between the toolbar and the keys.
emoji/emoji-row-own — The emoji row in Own row mode, above the key rows.
- **Off** (default): no emoji row.
- **Button**: adds a toggle to the toolbar strip that swaps it for the emoji row on tap.
- **Own row**: keeps the emoji row permanently visible above the keys.
A [keyboard mode](https://wmkeyboard.pages.dev/typing/modes/#keyboard-modes) can set the emoji row its own way: the Chat mode shows it as its own row, and Passwords turns it off. Every mode that sets a different value from yours is listed under the setting as a chip, like "Chat: Own row". Tap the chip to open that mode's editor.
Once the row is showing (anything other than Off), three more settings appear:
- **Emoji row content**: Most used (default), Recent, or Favourites. Whichever you pick, your favourited emoji always sort to the front.
- **Emoji in the row**: a slider from 3 to 16, defaulting to 8. The row splits its width into that many slots and shrinks the emoji to fit, so a higher number packs more emoji in at a smaller size.
- **Scroll the emoji row**: on by default. Swipe the row sideways for the emoji past the visible slots. Turn it off and the row never moves: it shows exactly that many slots and nothing past the last one, so a sideways swipe can't slide the whole thing out from under your finger.
Before you've used any emoji there's nothing of your own to fill the row with, so it starts on a set of 24 common ones. That's deliberately more than fits across a phone, which is what scrolling is on by default for. They're the only entries the **Default skin tone** setting applies to; everything the row picks up from your own history is already an exact emoji, tone included.
Where the row sits relative to the toolbar and the symbol row (above them, below them, in between) is a separate setting under Rows & bars on the settings home screen, not on this screen.
## Arranging the panel like your old keyboard
Emoji / Emoji panel / Emoji categories / Arrange like
Years on one keyboard teach your thumb where the emoji are. **Arrange like** copies that layout: pick Gboard, SwiftKey or HeliBoard and the panel takes that keyboard's tabs, in its order, with every emoji in the place it had there. **Unicode** (the default) is WM Keyboard's own order, straight from the Unicode release.
Each keyboard groups emoji a little differently from WM Keyboard's catalogue, whose Nature tab holds both the plants and the sky and weather. Picking a preset moves emoji to the tab that keyboard keeps them on, and hides any tab left empty:
| Preset | Tabs | What moves |
|---|---|---|
| Gboard | 9 | Hands and body parts join the faces on Smileys, and so do the moon faces and stars. Plants, weather and landscapes join Animals. Clocks join Objects, and instruments and cameras join Activities. Nature is hidden. |
| SwiftKey | 8 | Faces and people share one Smileys tab. Plants join Animals, and the sky and weather join Travel. Nature and People are hidden. |
| HeliBoard | 9 | Unicode's own groups: plants join Animals, and the sky and weather join Travel. Nature is hidden. |
The grid changes with the order, so the same swipe scrolls to the same emoji you'd reach on that keyboard:
| Preset | Columns | Row height | Emoji size | Scrolling |
|---|---|---|---|---|
| Gboard | 8 | 51 dp | 34 | One long list |
| SwiftKey | As many as fit at 44 dp | 44 dp | 30 | A page per tab |
| HeliBoard | 8 | 45 dp | 35 | A page per tab |
These are each keyboard's values on a phone held upright at its default keyboard height, read from its own resources and code. Gboard's column count is a server setting that defaults to 8, and its rows grow when it shows fewer columns. All of them can be changed afterwards under [Emoji panel](https://wmkeyboard.pages.dev/reference/settings/emoji/) (**Columns**, **Row height**, **Emoji size**, **One long list**).
The orders were read from Gboard 18.4, SwiftKey 9.13 and HeliBoard 4.1.
A preset is a starting point, not a mode. It fills in the same tab order, hidden tabs and per-tab emoji order that you can [drag by hand](https://wmkeyboard.pages.dev/reference/settings/emoji/#emoji-categories), so you can keep rearranging afterwards. Once you've moved something or resized the grid the setting shows **Your own**, and picking a preset again asks before it replaces your changes. An emoji the other keyboard doesn't have, such as one from a newer Unicode release, goes next to the emoji it follows in the catalogue, on whatever tab that one sits on.
## Details & edge cases
- **The font preview is exact, not illustrative.** The eight-emoji preview row on the Emoji font setting runs through the same shaping code the keyboard itself uses, including the ❤️ variation-selector workaround, so what you see there is what you'll get on the keys.
- **Installed emoji fonts share storage with text fonts**, but are listed separately in the picker. A colour emoji font would make an odd choice for key labels, so the two lists never cross over.
- **Added emoji have no tone or gender variants.** Each emoji you add, or that an emoji-test.txt import adds, is its own cell in the grid with no long-press variants. An import adds the man, woman and person forms of a new profession emoji as three separate cells.
- **Bundled Unicode data, not user data.** Keyword packs, shortcode names, and the trigger words behind emoji suggestions are all static files. They ship with the app or download from WM Keyboard's own data repository, and none of it depends on what you type.
- **Emoji predictions can drop under battery pressure.** Power Saving mode has its own setting that can suppress emoji predictions to save cycles. See [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/) for that behavior.
- **Kaomoji and ASCII emoticons are a separate system.** They live in their own library behind the Kaomoji tab, ignore the emoji font setting entirely (they're plain text), and keep their own favourites and recents. See [Kaomoji & text art](https://wmkeyboard.pages.dev/emoji/kaomoji/).
- **Two other keys can open the emoji panel directly.** The language-switch key can be replaced with an emoji key (on by default, under Layout & size), and the comma key can be too (off by default). Both are separate from the emoji row and the toolbar button described above.
---
# GIFs
URL: https://wmkeyboard.pages.dev/emoji/gifs/
Section: Emoji & expression
Summary: Search GIFs from KLIPY and GIPHY and send them, with a copy-to-clipboard fallback when an app won't take them directly.
The GIF tool searches KLIPY and GIPHY from a panel inside the keyboard, then sends the result straight into the field you're typing in. No switching apps, no copy-paste. On the F-Droid build it also searches Wikimedia Commons without any key at all, an educational corpus rather than reaction GIFs. Every other build needs a KLIPY or GIPHY key before the tool can search anything online, unless you turn on [GifCities](#gifcities), which needs no key. [Offline GIF packs](#offline-gif-packs) need neither a key nor a connection.
emoji/gif-panel-trending — The GIF panel, showing trending results on open.
## Searching for a GIF
Open the GIF tool and it fetches trending GIFs immediately, before you type anything. Close the panel or move to another text box and it keeps your search: it opens on that search next time, unless you turn off **Remember the last search**, which makes every opening start on trending. Start typing in the search box and results update live, 450ms after you stop. There's no Enter to press. While there's a query, an ✕ at the end of the search box empties it in one tap and brings trending back, with the keys still on the box for a new search. That's different from the [web and image search tools](https://wmkeyboard.pages.dev/tools/search/), which wait for you to submit the query; GIF and sticker lookups are cheap enough on KLIPY and GIPHY to search as you type.
Results are laid out as justified rows of animated previews rather than a fixed grid: every preview keeps its own aspect ratio and nothing is cropped, so squarish results sit three across and a pair of wide ones fills a row on its own. **GIFs per row** sets how many share a row, from one to six, and **Gap between cells** the space around each one. The search box names where results come from. On KLIPY it reads "Search KLIPY" and on GIPHY "Search GIPHY", each with that service's small "Powered by" mark at its end, as both services ask of apps that use them. GIPHY's mark keeps its colours, so on a light theme it sits on its own small black plate. The same goes for the sticker panel.
Before you've typed anything, a scrollable row of category chips sits above the grid: Trending first, then Reactions, Love, Happy, Sad and so on. Tap one to run it as a search, which is a way to browse without thinking of a word. Tap the active chip, or Trending, to go back. The chips come from the provider itself when it publishes categories, and from a built-in list when it doesn't, so the row is there offline too. It belongs to the default view only. Type anything and it steps aside for the results. With the full-screen picker turned off it also needs a certain amount of key height to be worth drawing, so a short three-row layout with the number row off loses it rather than losing the last row of the grid.
Along the bottom sits the same row the [emoji panel](https://wmkeyboard.pages.dev/emoji/picker/#switching-to-gifs-and-stickers) ends in: ABC, a switch to emoji, GIFs and stickers, space and backspace. The switch jumps straight to the emoji or sticker panel, and so does a sideways swipe across the results: right for emoji, left for stickers. ABC goes back to the keys. Emoji / Emoji panel / Switch to GIFs and stickers moves the switch next to the search box instead, or turns it off, and the row keeps its ABC, space and backspace either way. If you rearranged the emoji panel's bottom row, the GIF panel takes your row. The row steps aside while you type a search, since the keys come back under the panel then.
The GIF panel is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) too, so you can lay it out yourself: Tools / GIFs / Layout / Panel layout. The search box, the chips and the results are one component, **GIF search and results**, and every other cell is a key, so the panel can carry a different bottom row, a second row of keys, or the switch in a place of its own. Once you change it, it stops following the emoji panel. The panel is as tall as Emoji / Emoji panel / Panel height makes the emoji panel, so the three panels never change the keyboard's height between them.
### Favourites and recent GIFs
Long-press a GIF and choose **Add to favourites**. A **Favourites** chip then leads the category chips, ahead of Trending, and tapping it shows every GIF you added, the newest first. To take one out, long-press it (in Favourites or anywhere else it shows up) and choose **Remove from favourites**. The chip only appears once you have a favourite. You can keep up to 500.
The category chips are left out when the keyboard is too short for them, or in a field that can't take images. Then a **star** button sits at the end of the search bar instead: tap it to show your favourites, and again to go back.
Turn on **Recent GIFs** and a **Recent** chip sits next to it, with the last 48 GIFs you sent from the panel, the newest first. It is off by default, because it keeps a record of what you sent. While [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on, sending a GIF does not add it to the list. Turning the setting off deletes the list.
Favourites and recent GIFs are the provider's GIFs, so they normally load from the provider like any other result and need a connection. Turn on **Keep favourites on the phone** and the keyboard saves a copy of each favourite, so Favourites shows and sends with no connection, and a GIF stays even if the provider drops it. The copies download when you add a favourite or open Favourites, and only on a connection that [data saving](https://wmkeyboard.pages.dev/privacy/data-saver/) allows. Turning the setting off deletes the copies. The favourites stay, and load from the provider again. Favourites and recent GIFs are for the GIF panel only. The sticker panel has [your own sticker packs](https://wmkeyboard.pages.dev/emoji/stickers/) instead.
Both chips sit in the category row, so they follow its rules: they go away while you type a search, and a short panel that has no room for the row has no room for them either.
### Making the panel taller
A short bar sits across the top of the panel. Drag it up to make the panel taller, so more rows of results show at one time, and drag it down to make it shorter. When you let go, the panel keeps that height from then on. The bar sets the same **Panel height** as the emoji settings, so the emoji and sticker panels change with it, and the three still open at one height. The panel never covers more than most of the screen, and the bar is not there while you type a search, or where the screen has no room for a taller panel.
### Hiding the search while you scroll
With **Hide the search when you scroll** on, scrolling down the results folds the search box and the rows of chips away, and the results get their room. Scroll back up, or pull down at the top of the results, and they come back. The panel itself stays the same height. While you type a search, the search box always shows. Off by default, and shared with the sticker tool.
If neither provider has a key configured and no offline pack is installed, the panel shows a "needs an API key" notice with a shortcut into settings instead of a grid. The F-Droid build never hits that notice, since Commons is already searching.
## Sending a GIF
emoji/gif-no-rich-media — A field that can't accept images. The panel dims and shows the standing notice.
Tap a result and it downloads and lands in the field in one step. There's no separate "insert" confirmation.
You don't have to wait for one to arrive before tapping the next. Tap several in a row and they queue up: the first downloads while the rest wait, each with a small number in its corner for its place in the line, and they go out one after another in the order you tapped them. Tap a numbered GIF again to take it back out of the queue. If you leave the field before the queue is done, the GIFs still waiting are dropped rather than sent somewhere else.
What happens to each one depends on what the field will accept:
1. **The field doesn't take images at all.** Plain text boxes can't receive a GIF through Android's `commitContent` mechanism. The panel shows a standing notice, "This field does not accept GIFs. Try a chat app or a notes app.", and tapping a result does nothing beyond a short vibration. Nothing downloads. This is a pre-send check, not a failed attempt.
2. **The field takes images.** The full-size file downloads and commits using a MIME type the field advertises it can accept. With Tools / GIFs / Send GIFs as set to Sticker, a WebP result first offers WhatsApp's private sticker MIME (`image/webp.wasticker`) before falling back to plain `image/webp`. Android has no official "this is a sticker" flag, so this is a best-effort convention rather than a documented API. The PNG fallback only ever applies to a WebP-format download. If the field won't take `image/webp` but does accept PNG, a still WebP is re-encoded to a still PNG rather than failing outright. An animated WebP is skipped, since a PNG can't hold the animation. A plain `image/gif` download (what most GIF-panel results actually are) has no such fallback. If the field doesn't accept `image/gif`, it goes straight to the clipboard fallback below.
3. **Nothing matches.** If the field won't take any form the tool can produce, the file goes to the system clipboard instead, with a toast: "This app does not accept images here. The keyboard copied the image. Paste it instead." Paste it in manually from there.
Animated GIFs can never actually become stickers. Android has no animated-WebP encoder, so a GIF-format result keeps sending as a plain image no matter what `Send GIFs as` is set to. Only sources that already provide WebP can take the Sticker path.
## Offline GIF packs
The panel can also show GIFs kept on the phone, which need no key and no connection: [CleverKeys' offline GIF packs](https://github.com/tribixbite/CleverKeys/releases/tag/CleverKeys-GIF). Download a pack in your browser, then under Tools / GIFs / Providers press **Offline GIF packs** to open its screen, press **Import** and pick the zip. **Get the files** on the same row opens the release page.
Once a pack is installed the panel gains an **Offline** chip. It is a grid of its own in every provider mode, never mixed in with online results, and like the user's own sticker packs it never asks about [mobile data](https://wmkeyboard.pages.dev/privacy/data-saver/). Search matches the words each GIF is tagged with; the category chips are the packs' own (Funny, Love, Sad and the rest), and one searches for everything filed under it. A search lists up to 240 GIFs. Tapping one sends it as any other result is sent, without a download.
Only the **Discord community** packs (about 100 MB each, around 900 animated GIFs) carry GIFs to send. The **thumbnail** packs hold 80-pixel still previews and nothing else, so the import refuses them and says so. Each pack installed is listed with its size and a Delete. Importing a pack again replaces it.
Everything about getting files onto a phone that cannot download is on [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/).
## Long-pressing a result
Long-press a GIF instead of tapping it and a small menu opens over the grid. At its top are the GIF's name and, for a GIPHY result whose uploader GIPHY names, who made it ("by @name").
- **Add to favourites** puts the GIF in the [Favourites chip](#favourites-and-recent-gifs). On a GIF that is already a favourite the row reads **Remove from favourites** and takes it out.
- **Copy** downloads the GIF and puts it on the system clipboard, so you can paste it wherever you like rather than into the field you happen to be in. It's the same thing the fallback in step 3 above does, except you asked for it instead of finding out afterwards that the field wouldn't take the file.
- **Report** opens a mail draft to the maintainer naming the provider, your search term, and the result's id and URL, with room at the top to say what's wrong with it. Nothing is sent from the keyboard. The draft lands in your mail app, and you can read, edit, or abandon it. KLIPY and GIPHY answer a search out of their own catalogues, so a result nobody vetted can come back. This is where that goes.
Back (or tapping outside the menu) closes it without doing anything.
Saving to a pack is sticker-only, since a GIF can't become one. That row shows on the [sticker panel's](https://wmkeyboard.pages.dev/emoji/stickers/) version of this menu, not here.
emoji/gif-long-press — Long-press menu over a GIF result: Copy and Report.
## Options
Tools / GIFs
GIFs are part of the **Stickers, GIFs and emoji mixes** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). One switch there takes the GIF panel off the toolbar and the toolbox along with the stickers and the mixes, and brings it back with the providers you had chosen.
| Setting | Default | What it does |
|---|---|---|
| Providers | Depends on the build | Who the panel asks, in order: KLIPY, GIPHY, Wikimedia Commons or another MediaWiki, GifCities and offline GIF packs. Add, reorder, switch off or set up each one, and choose **Tabs**, **Mixed** or **One at a time**. See [Choosing providers](#choosing-providers). |
| Full-screen picker | On | The toolbar, emoji row and symbol row step aside so the GIF grid gets the full keyboard height, with search moved up next to a back button. Shared with the sticker tool. |
| GIFs per row | 3 | A slider from 1 to 6: the most GIFs side by side in a row. Fewer makes each one bigger. A row of wide GIFs can hold fewer than this. The sticker tool has its own. |
| Gap between cells | 4 dp | A slider from 0 to 12 dp: the space around each result. Shared with the sticker tool. |
| Panel height | Same as keyboard | How much taller than the keyboard the emoji, GIF and sticker panels open, up to 400 dp more. The same setting as Emoji / Emoji panel / Panel height and the [bar on top of the panel](#making-the-panel-taller). |
| Remember the last search | On | Reopening the panel lands on the search and category chip it was left on. Off, it always opens on trending. Shared with the sticker tool. |
| Hide the search when you scroll | Off | Scrolling down folds the search box and the chips away, scrolling up brings them back. See [Hiding the search while you scroll](#hiding-the-search-while-you-scroll). Shared with the sticker tool. |
| Recent GIFs | Off | A **Recent** chip with the last 48 GIFs you sent. Turning it off deletes the list. See [Favourites and recent GIFs](#favourites-and-recent-gifs). |
| Keep favourites on the phone | Off | Saves a copy of each favourite, so it shows and sends with no connection. Turning it off deletes the copies and keeps the favourites. |
| Panel layout | Standard | The keys around the results. **Standard** ends the way the emoji panel ends. See [Panel layouts](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts). |
| Send GIFs as | Image | Whether a tap tries to land the result as a sticker-style MIME first or always as a plain image. See the sticker-limitation note above. |
| Content filter | High | Four levels (Off, Low, Medium, High) mapped to each provider's own content rating (High maps to G-rated results, Off to R). Shared with the sticker tool. |
| Results for each search | 24 | A slider from 6 to 48. How many GIFs one search or the trending view loads. Shared with the sticker tool. |
You only need one working provider for the tool to work: a KLIPY or GIPHY key, Commons (already in the list on the F-Droid build), GifCities, or an offline pack. **Send GIFs as** is only shown here, and the sticker tool's **Send stickers as** only on its own page. Each governs the panel it sits under, so neither page offers the other's setting. See [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/) for that one.
## Choosing providers
Tools / GIFs / Providers is the list of services the panel asks. It works like every other [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag to reorder, switch a row off to skip it, and use **Add a provider** for another one.
- **KLIPY** and **GIPHY** need an API key, unless the build carries one. Paste a free key from `partner.klipy.com` or `developers.giphy.com` in the provider's setup. KLIPY replaced Tenor after Google retired Tenor's third-party API in mid-2026. The keys are shared with the sticker tool.
- **Wikimedia Commons** needs no key. Its GIFs are mostly educational rather than reactions. Add it again with an address to search another MediaWiki as well.
- **GifCities** needs no key. See [GifCities](#gifcities).
- **Offline GIF packs** is the packs you imported. Its screen lists them, each with a Delete, and the row that imports more.
**How to use them** decides the chip row:
- **Tabs** (the default): one chip per provider, to switch between them.
- **Mixed**: one grid that interleaves every provider evenly, with an **Online** chip for it and an **Offline** chip for your offline GIF packs. GIPHY keeps a chip and a grid of its own, because its terms forbid mixing its results with another service's.
- **One at a time**: one grid from the first provider that answers. If it fails or finds nothing, the next one is asked. Offline packs keep their own chip.
Without an offline pack or GIPHY next to another provider, Mixed and One at a time show no chip row at all.
## GifCities
GifCities is the Internet Archive's search over the GIFs it saved from GeoCities home pages before GeoCities closed in 2009. Add **GifCities** under Tools / GIFs / Providers and it joins the panel as one more provider, with a chip of its own in Tabs mode or mixed into the Online grid. It needs no key and no account, so it works on every build.
Expect the 1990s web rather than reaction GIFs: under construction signs, spinning globes, dancing figures, animated email icons. Results smaller than 32 pixels on a side (bullets, spacers) are left out. GifCities has no trending feed, so with the search box empty it shows a search for a different classic term each day.
- **What it sends.** Your search goes to `gifcities.archive.org`. Previews and the file you send load from the Wayback Machine at `web.archive.org`.
- **No content filter.** These GIFs carry no rating, so **Content filter** has nothing to apply to them.
- **GIFs only.** The sticker panel never offers it, and it has no category list of its own, so the panel's built-in categories stay up.
## Details & edge cases
- **Not pinned to the toolbar by default on a phone.** The onboarding wizard turns the GIF tool on for every persona, but a phone's out-of-the-box pinned toolbar only holds Emoji, Clipboard and Settings, so GIF sits in the [toolbox](https://wmkeyboard.pages.dev/tools/overview/) until you drag it onto the bar or open it from there. A tablet is the exception: its wider default bar pins GIF from the start.
- **Trending, not random.** An empty search box always shows each provider's trending feed, not a blank grid, useful as a starting point before you type a query.
- **24 results per fetch, and you can change it.** That's the number each provider is asked for on a search or a trending call, and the **Results for each search** slider moves it anywhere from 6 to 48. In Mixed mode every provider is asked for that many and the merged grid is then cut back down to it, so the number is what the grid holds in total, not what each source contributes.
- **Two of these rows never grow a restore button.** Settings you've moved off their shipped value normally pick up [a small restore button](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back) next to the control. **Content filter** is the exception on this screen. It's drawn as a bare segmented row under a heading rather than as a full setting row, so there's nowhere for the button to sit. Put it back by picking High yourself. The provider list has **Reset to default** instead.
- **No size cap on downloads.** Stickers cap the source file size. A GIF download has no maximum, so a very large or very long GIF still downloads in full before sending.
- **Recently sent files are cached, not stored permanently.** Downloaded files are kept in a local cache, so re-picking the same GIF skips re-downloading it. The cache keeps only the 30 most recently touched files and deletes the rest, so it isn't a place to go looking for something you sent a while ago.
- **Network use is opt-in and tool-scoped.** KLIPY and GIPHY are only contacted while the GIF panel is open: trending on open, live search as you type, and the download on tap. Nothing happens in the background. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full breakdown of what each online tool sends.
For custom sticker packs, long-press-to-save, and the Local tab, see [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/). For the toolbar and toolbox in general, see [The toolbar](https://wmkeyboard.pages.dev/tools/overview/).
---
# Kaomoji & text art
URL: https://wmkeyboard.pages.dev/emoji/kaomoji/
Section: Emoji & expression
Summary: A kaomoji tab with tag tabs, your own faces, favourites, search, suggestions and Emotic import (ノ◕ヮ◕)ノ*:・゚✧
Kaomoji are Japanese-style faces built from CJK and box-drawing characters, like `(╯°□°)╯︵ ┻━┻`. Western ASCII emoticons like `:-D` and `<3` are the simpler cousin. Both live in one library behind the Kaomoji tab at the end of the emoji panel. The library is yours to change: add faces, give them tags, sort them, star the ones you use, and bring in a library you built in [Emotic](https://f-droid.org/packages/futuristicgoo.emotic/).
emoji/kaomoji-tab — The emoji panel's Kaomoji tab, with its tag tabs across the top.
## Turning the tab on
Emoji / Emoji panel / Kaomoji tab
The tab is off by default. Turn it on and a tab labelled `^_^` appears at the end of the emoji panel's tab strip, after Recent (or Most used) and every Unicode emoji category, so every tab you already know keeps its place.
Kaomoji and emoticons share that one tab. If you would rather keep them apart, turn on Emoji / Emoji panel / Kaomoji / Separate Emoticons tab and a second tab, labelled `:)`, holds the ASCII faces. Each face knows which family it belongs to. A face you add is filed by its characters: printable ASCII alone makes an emoticon, anything else a kaomoji. You can change that in its editor.
All the tab's own settings are on one page:
Emoji / Emoji panel / Kaomoji
## Tag tabs
A row of tags runs across the top of the tab. Tap one and the grid shows only the faces under that tag. The row scrolls sideways when it holds more than fits, and keeps the tag you picked in view. It never pages by swiping: a sideways swipe on the panel still moves between emoji categories, as it always did.
The row starts with three tags of its own. Favourites holds the faces you starred and Recent the faces you used, latest first; each appears once it has something in it. All holds every face in your order. Your own tags follow, in the order you gave them. The keyboard ships 312 faces under 18 tags: Happy, Love, Sad, Angry, Table flip, Confused, Surprised, Cool, Cute, Animals, Actions, Sleepy, Greetings, Party, Apologies, Playful, Neutral and Figures. Moods the two families share, such as Happy and Love, are one tag holding both. A face can carry more than one tag and shows under each.
Emoji / Emoji panel / Kaomoji / Open on picks the tag the tab opens on: the last one you used (the default), Recent, Favourites or All.
If you prefer the tab as it used to be, turn off Emoji / Emoji panel / Kaomoji / Tag tabs. The tab then shows one long list with a heading for each tag.
## Edit buttons
A narrow column of four buttons sits beside the grid. Search opens the search box. Add opens the editor for a new face, which starts under the tag you have selected. Edit puts the grid in edit mode: tap a face to change it, or hold a face and drag it to a new place, then tap the tick to finish. Tags opens the tag list in place of the grid.
Emoji / Emoji panel / Kaomoji / Edit buttons puts the column on the left (the default) or the right, or turns it off. Under it, one switch for each button keeps only the ones you want. With the column off, the search button sits at the start of the tag row and the other three at its end, so you can still change your faces from the keyboard.
Dragging in edit mode moves a face only among the faces that tag shows. Every face outside that tag keeps its place in All. The Recent tag is in time order, so nothing there can be dragged.
## Holding a face
Hold a face and a bar of actions opens over the bottom of the grid. The star adds the face to Favourites or takes it off. The pencil opens its editor, and the copy button puts it on the clipboard without typing it. The bin asks once more, then removes the face, and the cross closes the bar.
## Adding and changing a face in the keyboard
The editor opens in place of the grid, and the keys come back underneath it so you can type. Type or paste the face into the box at the top. Under it, a row of chips holds every tag: tap a chip to put the face under that tag, or tap it again to take it off. The box at the end of the row types a new tag; press Enter and it joins the face. The first chip switches the face between kaomoji and emoticon.
The tick saves, the cross cancels, and the bin deletes a face that already exists (it asks once more). Enter in the face box also saves.
## The tag list
The Tags button replaces the grid with a list of your tags, each with the number of faces under it. For each tag you can move it up or down the row, hide it from the row with the eye (its faces stay in All), rename it, or delete it. Deleting a tag takes it off its faces and does not delete the faces. Renaming a tag to a name that already exists merges the two. The + at the top adds an empty tag, and the gear opens the library page in the settings app.
## Search
The search button opens a box with the matches above it, and the keys come back underneath. It searches tags and the faces themselves. A tag that matches exactly comes first, then tags that start with what you typed, then tags that contain it, then faces whose characters contain it. Press a match to type it, or press Enter to type the first one.
The emoji panel's own search lists matching faces too, under a Kaomoji heading after the emoji. Turn that off with Emoji / Emoji panel / Kaomoji / Kaomoji in emoji search.
## Kaomoji suggestions
Emoji / Emoji panel / Kaomoji / Kaomoji suggestions
This is off by default. Turn it on, and when the word you are typing is the name of a tag, the suggestion strip offers up to two faces from that tag in front of the emoji suggestions. Your starred faces come first, then the ones you use most. Add a tag such as `shrug` or `tableflip` to the faces you reach for, and typing that word puts them one tap away. A tag shorter than three letters never triggers this.
A face from the strip goes in the same way an emoji suggestion does: after your word, or in place of it, as Emoji / Suggestions / Press an emoji suggestion decides. Holding it does the other.
## The library in the settings app
Emoji / Emoji panel / Kaomoji / Faces
The Faces screen lists every face, filtered by a row of tag chips. Drag a face by its handle to move it, press it to change its text, tags, family and star, and press the bin to delete it. Add a face from the row at the top.
Emoji / Emoji panel / Kaomoji / Tags
The Tags screen does the same for tags: drag to reorder the tag row, the eye to hide a tag, the pencil to rename it, the bin to delete it.
Both screens and the keyboard write the same file, so a change in one shows in the other the next time it opens.
## Importing from Emotic and other files
Emoji / Emoji panel / Kaomoji / Import
The import reads an Emotic export, a file this keyboard exported, or a plain list.
Emotic 0.1.11 and later export a `.tar.gz` file; older versions export the bare `emoticdb.sqlite` database. The keyboard reads both. It brings in every face with its tags, keeps Emotic's order for the faces and the tags, and drops the repeats Emotic's database can hold.
A plain list has one face on each line. A tab after the face, followed by tags with commas between them, files it under those tags.
Before anything changes, a dialog says how many faces and tags the file holds. **Add** puts the new faces after yours and gives a face you already have any tags it was missing. **Replace all** empties the library first.
Emotic also keeps reaction images, which it calls emotipics. When the export carries some, the keyboard puts them in a new sticker pack named Emotic, with their Emotic tags as the stickers' search words. Images Emotic marked as excluded stay out. See [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/) for using the pack.
Treat an archive from someone else with care. The keyboard reads only the database and the image folder out of it, never writes a file outside its own cache, and refuses an archive past 512 MB or an image past 20 MB.
## Exporting, backing up and resetting
Emoji / Emoji panel / Kaomoji / Export saves your faces, tags, hidden tags and stars to a JSON file. How often you used each face stays behind.
The library also has its own Kaomoji section in [backups](https://wmkeyboard.pages.dev/privacy/backup/), on by default for an export and for automatic backups. Sync leaves it out unless you add it, because every tap on a face changes it.
Emoji / Emoji panel / Kaomoji / Reset the library goes back to the faces the keyboard ships, after one confirmation. It deletes the faces you added, your changes, your stars and your recent faces.
Until you change something or use a face, the library is the built-in set and no file exists. A later version that adds faces to the built-in set shows them to you only while that is still true.
## Inserting long faces safely
A cell shrinks its font size step by step until the whole face fits on one line. It never wraps and never trails off with an ellipsis, since half a kaomoji reads as garbage rather than a smaller version of the same face. The glyph stays invisible for that first layout pass, so you never see it flash past its own edges before settling.
Tapping a cell commits the face as one atomic insert. Any word you were mid-typing is flushed first with autocorrect turned off, and then the face goes in as plain text in a single step. It never passes through autocorrect or auto-capitalization, so `(¬‿¬)` reaches the field exactly as shown.
If Emoji / Emoji panel / Return to the keyboard after you insert is on, tapping a face closes the panel and drops you back on the keys, exactly like a real emoji tap does.
## Details & edge cases
- Faces stay out of emoji history. A face counts for the Kaomoji tab's Recent tag and for the suggestion order, and never for the emoji panel's Recent, Most used or the emoji row.
- The emoji font setting does not apply here. Faces render as plain text with no font override. Emoji / Emoji style / Emoji font only touches genuine Unicode emoji glyphs.
- The built-in faces stick to glyphs Android ships. They use characters covered by Android's bundled fonts (Noto Sans CJK and Noto Sans Symbols), so every one renders out of the box. A face you add can use anything, and may show as boxes on a phone without a font for it.
- In the continuous list, with Emoji / Emoji panel / One long list on, the Kaomoji section starts with its tag row, and that row carries the search, add and tags buttons at its ends. Faces cannot be dragged in that list; use edit mode with the list off, or the Faces screen.
- After a restart, until you unlock the phone once, the tab shows the built-in set, since your library file cannot be read yet.
- The library, the import and the suggestions work the same in every build.
For the rest of the emoji panel (search, skin tones, favourites, and recents for actual Unicode emoji), see [Emoji picker & search](https://wmkeyboard.pages.dev/emoji/picker/). For the emoji row and emoji fonts, see [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/).
---
# Emoji mixes
URL: https://wmkeyboard.pages.dev/emoji/mixes/
Section: Emoji & expression
Summary: Put two emoji together as one sticker, from a source you add: over the emoji panel, on the suggestion strip, and in the Mixes panel.
An emoji mix is two emoji drawn as one picture, sent as a sticker. WM Keyboard doesn't come with any mixes and doesn't fetch any on its own. It shows mixes from a source you add yourself, and until you add one, nothing about mixes appears on the keyboard.
Emoji / Emoji mixes
## Adding a source
Tap **Add a source** and paste an address, or use **Import a file** or **Import a folder**. The keyboard works out what kind of source it is from its shape:
- **List of mixes.** A file that names each pair and the address of its picture. It can be JSON, where each entry has an image address and the two emoji, a plain list of image addresses, or plain text with two emoji and an address on each line. The keyboard downloads it once, packs it into an index of under a megabyte on the phone, and from then on knows every mix the list has. This is the kind that lets the keyboard list all the mixes of one emoji.
- **Image address.** One working address of a mix picture. The keyboard reads the pattern that every other picture follows from the folders and names in it. It needs a list of mixes beside it to know which pairs exist, and it points that list's pictures at the host you named.
- **Mixer.** An address that answers any two emoji with their mix. Paste one example, or write a pattern with `{left}` and `{right}` where the emoji go (`{left_hex}` and `{right_hex}` take code points such as `1f603`, and `{size}` takes a size in pixels). When you add it, the keyboard asks it once for a pair that has no mix, to learn the picture it sends when there is none. A mixer alone works in the Mixes panel. To get mixes on the suggestion strip, add a list of mixes too.
- **Imported images.** A zip file or a folder of pictures named for their two emoji, such as `1f603_1f601.png`, `u1f603_u1f601.webp` or `😃_😁.png`, or kept in dated folders. They are copied onto the phone and work with no connection.
Each source has a switch. Sources answer in list order: tap one to fetch its list again, move it up, or remove it along with its files.
After a restart, mixes come back once you unlock the phone for the first time. Lists and imported images are stored behind the lock screen, so the keyboard can't read them before then.
## Where mixes show
**Over the emoji panel.** Pick an emoji in the emoji panel and a row of its mixes rises over the grid, the mix of it and the emoji you picked just before it first. The button at the end of the row opens the Mixes panel on that emoji.
**On the suggestion strip.** When the text before the cursor ends in one or two emoji written together, their mixes show on the strip, the mix of the two first. **How the strip shows them** picks thumbnails, one chip that opens a row, or the row on its own. With **Replace the emoji** on, sending a mix takes the emoji it was made from back out of the text. That happens only after the mix has arrived, so a mix that can't be fetched leaves the text alone.
**The Mixes panel.** A tab beside emoji, GIFs and stickers on the [panel switch](https://wmkeyboard.pages.dev/emoji/picker/#switching-to-gifs-and-stickers), and a tool you can pin to the toolbar. Two slots sit at the top with their mix beside them. Pick an emoji from the bottom rows and it goes into the slot that is lit; the grid in the middle lists every mix of the first emoji. The emoji in the bottom rows come in the emoji panel's order, one category at a time: the category tabs sit in the panel's header, where the GIF and sticker panels have their search box, or above the bottom rows when the GIF and sticker tools' **Full-screen picker** is off. Your recent emoji get the first tab when any of them have mixes.
The lit slot shows a small cross while it holds an emoji. Tap it, or long-press either slot, to take that emoji out; with only the second slot filled, the grid lists that emoji's mixes instead. The search button opens a box that finds emoji by name as you type: tap one, or press Enter for the first, and it goes into the lit slot. The swap button trades the two emoji, and the shuffle button picks a random emoji and one of its mixes. Long-press the shuffle button to choose which categories it picks from, with one column of ticks for the first emoji and one for the second, so a shuffle can always put an animal with a food, say. A column always keeps at least one tick, and **Every category** ticks them all again.
Some sources include the magic wand 🪄, which mixes with every emoji: its mix draws the other emoji again in an older, rounder style.
With motion on, the mix lands with a small burst and the grid of mixes rises in, one cell after the other. With motion off under [Appearance](https://wmkeyboard.pages.dev/reference/settings/appearance/), all of that is still.
Your own [stickers](https://wmkeyboard.pages.dev/emoji/stickers/) come before mixes: if the text asks for one of your stickers, that offer shows instead.
## Saving a mix
Long-press any mix for its menu:
- The top of the menu names the two emoji the mix was made from.
- **Star** puts it in a row at the top of the Mixes panel.
- **Add to my stickers** copies it into a sticker pack named Emoji mixes, tagged with both emoji, so it is offered as you type with no connection.
- **Save to photos** puts it in Pictures/WM Keyboard.
## Privacy and data use
The keyboard only talks to the hosts of sources you added. A mix's address names its two emoji, which is all the request carries, and every request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as Emoji mixes. Pictures fetched to send are kept in the cache, up to 400 of them, and **Clear fetched mixes** empties it. Lists and imported images stay until you remove their source.
While [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds media on the connection you're on, the strip and the panel show only mixes from imported images. **Fetch lists again each month** downloads each list again once it is a month old, only on a connection that is not metered. It is off by default.
## Settings
Emoji mixes are part of the **Stickers, GIFs and emoji mixes** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). With the bundle off, this screen and the mixes panel are gone, and the sources you added wait for it to come back.
| Setting | Default | What it does |
|---|---|---|
| Sources | none | The sources you added, in the order they answer. |
| Over the emoji panel | On | A row of mixes when you pick an emoji in the panel. |
| On the suggestion strip | On | Mixes when the text ends in one or two emoji. |
| How the strip shows them | Thumbnails | Thumbnails, One chip, or A row above. |
| Replace the emoji | On | Sending a mix takes its emoji back out of the text. |
| Mixes panel | On after the first source | The tab on the panel switch and the toolbar tool. |
| Order | Your emoji first | Your recent and most used emoji first, or the source's own order. |
| Send as | Sticker | Sticker where the app takes one, otherwise a picture. |
| Size in the Mixes panel | 72 dp | How big each mix is in the panel's grid. |
| Fetch lists again each month | Off | The monthly download described above. |
| Clear fetched mixes | n/a | Empties the cache of fetched pictures. |
---
# Emoji picker & search
URL: https://wmkeyboard.pages.dev/emoji/picker/
Section: Emoji & expression
Summary: Semantic emoji search in your language, skin tones and dual-tone pairs, and favourites and recents in the emoji panel.
The emoji panel does more than lay emoji out in a grid. Search understands synonyms and typos in English and Bangla, every toned emoji remembers the variant you last picked for it, and your favourites and recents ride at the front of their own tab.
## Opening the panel
Emoji / Access / Emoji button in toolbar
The most direct route is the emoji button in the toolbar (on by default). Tap it and the panel opens over the keys. If you would rather have another tool there, or several, **Buttons beside the suggestions** on the same screen swaps it for the clipboard or adds any other tools beside it, in the order you drag them into, with their own size if you want one. If you've turned on a [dedicated emoji row](https://wmkeyboard.pages.dev/emoji/customization/#the-dedicated-emoji-row), its own launcher icon opens the same full panel.
Two more toggles replace a bottom-row key outright with an emoji key, so opening the panel takes one less tap:
Layout & size / Bottom row keys / Emoji key instead of 🌐
**Emoji key instead of 🌐** is on by default, so the 🌐 key opens the emoji panel instead of switching language. Language switching doesn't disappear. It moves to a spacebar swipe, set to Language by default, so you get it back for free. See [Gestures](https://wmkeyboard.pages.dev/typing/gestures/). **Key in place of 🌐**, under the same switch, can put a tool there instead of the emoji panel, such as Stickers.
**Comma key opens emoji**, under the same group, is off by default. Turn it on and the bottom-row comma key becomes an emoji key too, with comma itself demoted to a long-press alternate. Its own info text warns that turning this on also removes the emoji tool from the toolbar, since the key is meant to replace it. Drag the tool back into the toolbar if you want both.
**Press and hold enter for emoji**, also in that group, is off by default and leaves every key where it is. Turn it on and holding Enter opens the emoji panel when you lift, for a bottom row that has no emoji key. A tap on Enter is unchanged. More on [Key press settings](https://wmkeyboard.pages.dev/reference/settings/keypress/#press-and-hold-shortcuts).
### Tabs, pages and the long list
Each category has a tab in the strip across the top. With few enough tabs they share the strip's width. When there are too many to fit at a comfortable size (the history tab, the categories, and the Kaomoji tab if you turned it on), every tab keeps that size and the strip scrolls sideways under your finger instead of squeezing them. The tab you're in is kept in view as you move through the panel.
By default each category is a page, and you swipe left or right to go to the next. Emoji / Emoji panel / One long list makes the panel one list instead, with each category's name as a heading, so one long swipe up runs through every emoji. The tabs jump to a heading and follow the list as you scroll.
Emoji / Emoji panel / Panel height opens the emoji, GIF and sticker panels taller than the keyboard, so more fit on screen while the letters stay compact. The three share the height, so switching between them doesn't move the keyboard. A short bar across the top of the panel drags the same height taller or shorter, [as on the GIF panel](https://wmkeyboard.pages.dev/emoji/gifs/#making-the-panel-taller). Full screen panels have no bar.
### Switching to GIFs and stickers
Emoji / Emoji panel / Switch to GIFs and stickers
The bottom row of the panel carries a switch with three buttons: emoji, [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) and [stickers](https://wmkeyboard.pages.dev/emoji/stickers/). Tap GIF or sticker and that panel takes the emoji panel's place, with the same bottom row and the same switch under its results, so you can go back and forth without the toolbar. The setting above moves the switch to the top row, next to the category tabs (next to the search box on the other two), or turns it off. The GIF and sticker panels keep the emoji panel's ABC, space and backspace in every case. A button shows only while its tool is on. With both the GIF and the Sticker tool off there is nowhere to switch to, so the switch is not drawn at all; if your panel layout places it, the layout editor says so and links to the GIF tool, where you can turn it on.
With Emoji / Emoji panel / Open the last used panel on, the emoji key reopens whichever of the three you left open, the way most keyboards treat emoji, GIFs and stickers as one panel.
Typing `:cat:` is a different feature entirely: it searches or auto-commits an emoji from inside the suggestion strip while you type, without opening this panel at all. See [Inline emoji search and shortcodes](https://wmkeyboard.pages.dev/emoji/customization/#inline-emoji-search-and-shortcodes).
## Searching for an emoji
Tap the magnifying-glass tab at the start of the tab strip. The panel switches into search mode and a search pill appears. With the full-screen panel on (the default) the pill takes the header row beside the back button, in place of the tab strip. With it off, the pill sits under the results instead, right above the keys that type into it, so the emoji stay at the top of the panel where your thumb left them.
The pill is not a text field you type into directly. Tap it, and the letter keys underneath become a query editor: what you type appends to the pill instead of going to your document, and the keyboard's own backspace key deletes from the pill. A small backspace icon inside the pill deletes from your actual text field instead, without leaving search. Tapping the pill again unlatches typing, and the tab strip returns once the query is empty, so backspacing your query away is the way back to browsing. An ✕ at the end of the pill, while it holds a query, empties it in one tap: the full grid comes back, and the keys stay on the pill for a new query.
Search understands more than an exact keyword match, layered in this order, where matches on more than one query word add up:
1. **An exact shortcode.** `tada` finds only 🎉, unambiguously, ahead of anything else.
2. **A shortcode prefix.** `thumb` starts narrowing toward `:thumbsup:` and its neighbors.
3. **An exact keyword.** `cat` matches every emoji tagged with the word "cat".
4. **A synonym.** A small hand-curated list expands common searches. `party` also surfaces 🍾 via "celebration", and `happy` also reaches emoji tagged "joy" or "cheerful". Bangla concepts expand the same way (খুশি → হাসি, আনন্দ).
5. **A keyword prefix.** `cele` starts matching "celebration" before you finish typing it.
6. **A one-typo fuzzy match.** `hapy` still finds 😀, for query words of four letters or more.
Turn on **Find any character by its Unicode name** under Emoji / Emoji style (off by default) and the same search also reaches tens of thousands of other characters by the words of their official names: `em dash` finds —, `copyright` finds ©, `lambda` finds λ and Λ, `ogonek` finds ą, ę and the rest, and `g clef` finds 𝄞. Music and maths symbols and old scripts are in it. Chinese, Japanese and Korean ideographs are not, because Unicode names them by number only. Those come after the emoji results, and a character the catalogue already knows as an emoji is listed once. Tapping one inserts it like an emoji; a hold shows its name.
### Characters on the suggestion strip
You don't have to open the panel for a character whose name you know. Turn on **Suggest characters by their Unicode name** in the same place (off by default) and the strip offers a character when the words you just typed are exactly its name. Type `snowman` and ☃ appears beside the emoji chips; type `em dash` and — does. Tapping the chip puts the character in place of its name, so `em dash` becomes — with no words left behind.
A trailing "sign" or "symbol" can be left off, so `degree` offers °, `euro` offers € and `section` offers §. Greek letters go by their own names: `lambda` offers λ and `capital omega` offers Ω. A name can run to four words, the one you're typing and three before it. Only whole names count, which keeps the strip quiet while you write ordinary sentences. That's also why the setting is off by default: a few names, such as degree and section, are everyday words too.
With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, the emoji whose names mean what you typed come after all of these, in any language: "gato", "猫" and "rocket launch" find 🐈 and 🚀 with no keyword pack for Spanish or Chinese and no keyword that says "launch".
English and Bangla keywords are merged into the same index, so a query never needs to name a language. বিড়াল and "cat" surface the same set of cat emoji, and that holds for every cat-family emoji in the catalog, not only the smiley ones. Typing in a language you haven't installed a keyword pack for still works for anything carrying English or Bangla tags. To download a pack for another language, see [Emoji search in your language](https://wmkeyboard.pages.dev/emoji/customization/#emoji-search-in-your-language).
### The strip completes your query
While the search box has the keys, the suggestion strip sits in the row directly above them, and what it offers comes from the emoji catalogue rather than from your language's word list. Type `ca` and it offers `car`, `cat`, `card`, `cash`, but not `cable` or `cathedral`, which are perfectly good words that no emoji is tagged with and that would leave you staring at an empty grid. Everything on the strip finds at least one emoji.
It completes against everything the search itself understands: keywords in every language you've got a pack for, shortcode names like `tada`, and the curated synonyms above. A second word narrows to terms that share an emoji with the first, so `red hea` offers `heart` ahead of `headphone`. Tapping a term replaces the word at the caret and leaves a space, ready for the next one.
## Picking a skin tone
Emoji / Skin tone / Default skin tone
Long-press any emoji in the panel grid (search results included) to open its variant popup. If Emoji / Emoji panel / Emoji descriptions is on (the default), the popup opens with the emoji's name at the top, so near-identical faces are easy to tell apart.
Most toned emoji offer a single row of six choices: the neutral base plus five Fitzpatrick skin tones. Tap any one to insert it. A small number of two-person emoji, like 🤝 and 👫, open a dual-tone picker instead. It has two independent swatch rows, one per person, so each side of a handshake or hug can carry its own tone.
Whichever variant you tap becomes that emoji's new default. The picker remembers your last choice per emoji and shows it from then on, in the grid and in your recents. Two settings shape how tones work everywhere else:
Emoji / Skin tone / Override with last used
**Default skin tone** sets the tone every toned emoji gets when you haven't picked one for it yourself. That covers the panel grid, the suggestion strip, search results and the starter set in the emoji row alike. The [setup wizard](https://wmkeyboard.pages.dev/start/setup/#running-the-setup-wizard) asks for it on its Emoji page, so it's usually set before the first emoji you insert; this row is where you change it later. **Override with last used** (on by default) keeps your per-emoji memory ahead of that default, so an emoji you've already toned by hand stays the way you left it. Turn it off if you'd rather the default tone win everywhere and your per-emoji picks only apply to that one insertion.
Emoji with gender or role variants (a runner shown as 🏃♀️ or 🏃♂️, a doctor as 👨⚕️ or 👩⚕️) collapse into the same long-press popup, so the grid isn't cluttered with every combination as its own cell. The popup then stacks one six-cell row per member, so a single tap picks the member and its tone together. The two-person emoji are the exception: their dual-tone picker puts the members in a row of their own at the top, above the two tone rows.
## Favourites, recents, and most used
Emoji / Emoji panel / History tab
The first tab in the panel shows your history, once you have at least one recent or favourited emoji. **History tab** chooses what fills it. **Recent** orders by when you last used an emoji, and **Most used** ranks by frequency. **Both** shows the two at once: your 16 most used emoji under a **Most used** heading, then the recents under **Recent**. Favourited emoji are pinned to the front either way. **Off** removes the tab, so the panel opens on the first emoji category and every category keeps a fixed place. The keyboard still remembers what you use, for the emoji row and emoji suggestions.
The history tab doesn't reorder while you tap through it. What you pick shows up the next time the tab comes into view: when the panel opens, or when you close or clear a search, so an emoji you found by searching is in Recent as soon as the grid is back.
Long-press any emoji to star it as a favourite from the same popup used for skin tones: the star toggle sits right above the variant grid. Once you have two or more favourites, a **Reorder favourites** row appears in that popup too, opening a drag-to-reorder list so you can put your most-used ones first.
Long-pressing a cell on the history tab specifically also offers **Remove from recents**, for pulling a one-off emoji back out without waiting for it to age off the list. For clearing the whole list at once:
Emoji / Emoji panel / Clear recents button
Off by default. Turn it on to add a "Clear recents" button to the Recent tab. It only appears on the Recent view, not Most used, since a frequency ranking isn't something a single tap should wipe.
## Reordering the tabs and the emoji
Emoji / Emoji panel / Emoji categories
The panel ships its category tabs in Unicode's own order. **Emoji categories** is where you change it: drag a category to move its tab, press the eye to hide a tab you never open, and press a category to open its emoji and arrange those too: press and hold one, then drag it where you want it.
Hiding a category hides only its tab. Those emoji are still found by search and still offered in the suggestion strip.
The history tab is not in that list: it orders itself, with your favourites pinned first as above. The Kaomoji tab is not either. It is switched on and off by its own toggle, always sits at the end, and sorts its tags on its own page.
Full reference: [Emoji categories](https://wmkeyboard.pages.dev/reference/settings/emoji/#emoji-categories).
## Animated emoji
Emoji / Emoji panel / Animated emoji
Around 880 emoji have an animated version published by Google as part of [Noto Animated Emoji](https://googlefonts.github.io/noto-emoji-animation/). Long-press any of them in the panel and the popup loops the animation, with a **Send animated emoji** button under it that sends the 512×512 GIF into the field you're typing in.
The button normally appears once the animation has loaded, so it doesn't offer to send something you haven't seen. The one exception is when no preview is coming at all, held back by data saving's "ask each time" or lost to a failed fetch. The button still appears there, since pressing it is the only way to answer that. The popup stays open afterward, preview and all, so you can fire off two in a row or pick a skin tone next.
Two files exist for every animation, and the popup uses them for different jobs. Opening it fetches the WebP (around 350 KB) to loop in the preview. Pressing the button fetches the GIF (around 800 KB) and sends that. Long-press is also how you reach skin tones and favourites, so most long-presses send nothing and shouldn't cost the larger file. The GIF is what goes out because an app that accepts `image/webp` may still draw only its first frame, which would make a "send animated emoji" button a liar.
Both come from `fonts.gstatic.com`, and nothing is downloaded until you long-press. Nothing about you goes with either request: it's a plain fetch of a fixed URL, with no query, no key, and no identifier.
The button is deliberately absent in three cases:
- **The emoji has no animation.** Flags, most ZWJ sequences, and plenty of symbols aren't in the set. The app ships the list of which ones are (`assets/emoji/animated.txt`, about 8 KB) rather than asking the server, so a long-press never stalls on a request that was going to 404.
- **The app can't take images.** A GIF goes through the same rich-content path as a sticker, so a field that accepts no images (a plain search box, most password fields) doesn't get the offer. See [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/#sending-a-gif) for what does and doesn't accept them.
- **The setting is off.** Turn **Animated emoji** off and long-press goes back to skin tones and favourites only.
Sending one counts as using that emoji, so it lands in your recents like an ordinary tap would.
## Sending an emoji as a picture
Emoji / Emoji panel / Send emoji as image
Under the animation (and on every emoji, animated or not) is **Send emoji as image**: the emoji itself, drawn at 512×512 in whichever [emoji font](https://wmkeyboard.pages.dev/emoji/customization/#choosing-an-emoji-font) you've picked, sent as a transparent WebP.
It answers the oldest emoji problem there is: the other person's phone drawing your emoji as something else, or as an empty box. Send it as a picture and they see the one you saw.
The picture is drawn in the font the keyboard itself draws with, whichever that is: an imported font, one installed from an addon repository, Google's Noto Color Emoji, or the phone's own set. Noto is fetched once from the same system font provider Compose draws it with, then kept. Everything else is already on the device, so this normally works offline and sends nothing anywhere.
How it goes out follows the sticker tool's own setting, so one preference governs every sticker the keyboard sends:
Tools / Stickers / Send stickers as
Left at **Sticker** (the default) WhatsApp receives it as a real sticker. Set it to **Image** and it arrives as an ordinary picture in the message box instead. That's the thing to try if an app does something unhelpful with a sticker, like sending it instantly or closing the keyboard afterward.
Like the animated button, this one only shows on a field that accepts images, and it can be switched off on its own.
The animations are **Noto Animated Emoji** by Google, licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The credit is repeated under the send button in the app, and the full notice is in Settings → About → Licences, under the downloaded data packs.
## Details & edge cases
- **Inserting doesn't close the panel by default.** Emoji / Emoji panel / Return to the keyboard after you insert is off, so you can tap several emoji in a row (including several results from the same search) without the panel folding away after each one. Turn it on to jump straight back to the keys after a single insert. The same setting also governs the clipboard panel.
- **Full-screen panel is the default, not an opt-in.** Emoji / Access / Full-screen emoji panel is on out of the box: the toolbar, emoji row, and symbol row all step aside, a slim back header takes their row, and the panel's layout fills the rest. Turn it off to keep the toolbar within reach while picking emoji.
- **Search and the panel grid both respect "Hide emoji that your device cannot show."** An emoji neither your chosen font nor the phone's system font can draw is filtered out everywhere: panel, search, and suggestions alike. See [Fixing emoji that won't display](https://wmkeyboard.pages.dev/emoji/customization/#fixing-emoji-that-wont-display).
- **Kaomoji are a separate system.** With the Kaomoji tab on, the panel's search lists matching faces under a Kaomoji heading after the emoji, matched by tag or by the face's characters. They never join the emoji history. See [Kaomoji & text art](https://wmkeyboard.pages.dev/emoji/kaomoji/).
- **The panel is a layout.** Its category tabs, search box and grid are cells of a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts), with an abc, [GIF and sticker switch](#switching-to-gifs-and-stickers), space and hold-to-repeat backspace row along the bottom so you can type a word, drop in an emoji, and keep typing without leaving the panel. Open Emoji / Access / Panel layout to move the tabs, drop the search box, give the grid the whole height, or add keys of your own.
- **Every build.** Search, skin tones, and favourites aren't gated by build: the same emoji panel ships everywhere.
### Coverage
The bundled catalog is Emoji 18.0 and covers 1,923 base emoji across ten categories (activities, animals, flags, food, nature, objects, people, smileys, symbols, travel). Of those, 332 carry Fitzpatrick skin-tone variants, five uniform tones each, and 19 of that 332 also support independently-toned pairs like 🤝. Together that's 2,040 additional toned sequences on top of the base count. An emoji newer than your phone's emoji font draws as a box until the font catches up; picking a newer font under **Emoji font** fixes that.
For the settings that shape all of this (which font draws the emoji, keyword packs for other languages, word-typing predictions, and the emoji row), see [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/).
---
# Sticker generator
URL: https://wmkeyboard.pages.dev/emoji/sticker-generator/
Section: Emoji & expression
Summary: Describe a sticker and the phone draws it offline, or an image service you pick does: the engines, the phone's models, prompts in any language, the safety filter, and importing your own model.
The sticker generator draws stickers from a sentence you type. By default the model runs on the phone itself, so it works offline and the picture never goes anywhere until you send it. You can also have an image service draw them instead: two free ones that need no account, the big paid ones with your own key, or a Stable Diffusion server you run. You pick a result, the background comes off, the [sticker editor](https://wmkeyboard.pages.dev/emoji/stickers/#the-sticker-editor) opens on it, and from there it goes into one of your packs and out like any other sticker.
Open it from Tools / Stickers / Your stickers / Sticker packs / Create stickers, from **Create from a prompt** on a pack's page, or from the keyboard: in the sticker panel's **My stickers**, the sparkle chip beside the + chip opens it, saving into the pack the panel is showing.
## Engines
Who draws is a [provider list](https://wmkeyboard.pages.dev/tools/providers/), **Engines**, at the bottom of the generator's screen. Each row is one engine you switch on, order, and set up with a key or an address; tap a row for its own screen, where **Test** checks the key with a request that draws nothing and costs nothing (fal.ai has no such request, so it has no Test). A new install lists **On this phone** switched on, and Pollinations and AI Horde switched off: those two send your prompt away, so they wait until you turn them on.
With more than one engine on, chips above the prompt pick the one that draws, and the line under them says where the prompt goes. Set the list to **One at a time** under its **List settings** and a tap tries the engines in order instead, moving to the next when one fails or has no answer.
| Engine | Key | What it is |
|---|---|---|
| **On this phone** | none | The models below, downloaded or imported. Nothing leaves the phone |
| **Pollinations** | optional | Free and needs no account. Without a key it uses Pollinations' old host, which picks the model itself (Sana, today) and puts a small pollinations.ai mark in a corner. A key from [enter.pollinations.ai](https://enter.pollinations.ai) moves it to the newer API, where the model field takes any model it lists |
| **AI Horde** | optional | Free Stable Diffusion on volunteers' graphics cards. Anonymous use waits in a queue, and the status line shows your place in it; a free key from [aihorde.net](https://aihorde.net/register) moves you up. Asked for 512 × 512 |
| **OpenAI Images** | yours, or the AI list's | GPT Image (`gpt-image-1-mini` when the model field is blank). Draws on a transparent background, so no background removal is needed. Paid per picture |
| **Gemini Images** | yours, or the AI list's | Google's Nano Banana models (`gemini-2.5-flash-image` when blank). One picture per request |
| **Grok Imagine** | yours, or the AI list's | xAI's `grok-imagine-image-2.0` |
| **Stability AI** | yours | Stable Image Core when blank; type `ultra`, or an SD 3.5 model such as `sd3.5-large`, to use those. A picture its filter blurs is held back |
| **Black Forest Labs (FLUX)** | yours | FLUX from its makers, `flux-2-klein-4b` when blank; any of their endpoint names works, such as `flux-2-pro` |
| **Recraft** | yours | Illustration, icon and vector-style models, the service's own default when blank |
| **Replicate** | yours | Any text-to-image model there, as `owner/name` (or `owner/name:version`); FLUX Schnell when blank |
| **fal.ai** | yours | Any image model's fal id, `fal-ai/flux/schnell` when blank |
| **Together AI** | yours | FLUX and other open models, FLUX.1 Schnell when blank |
| **Hugging Face** | yours | Models on Hugging Face's own inference servers, Stable Diffusion 3 Medium when blank. The token needs the Inference Providers permission |
| **Cloudflare Workers AI** | yours, with the account id | FLUX.1 Schnell when blank, or another `@cf/` image model. The account id goes in its own field |
| **Automatic1111, Forge or SD.Next** | optional | Your own web UI, started with `--api`. A key of `user:password` is its `--api-auth`. The model field switches the checkpoint for that one request |
| **ComfyUI** | optional | Your own ComfyUI. Queues a plain text-to-image workflow with the checkpoint you name, or the first one the server lists |
| **OpenAI-compatible images** | optional | Anything with OpenAI's image API, such as LocalAI, DeepInfra or Nebius: the address up to the version, and a model name |
OpenAI Images, Gemini Images and Grok Imagine borrow the key you gave the same service on the [AI list](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs) when their own key field is empty, so one account works for both. Every engine but the phone can be added twice, for a second key or a second server, and works on every build, F-Droid's included: each is a plain web request, with no company's code in the app.
An online engine gets the prompt in English with the style's words, and the seed, the size and how many pictures you asked for; nothing else about you. Its pictures come back to the app, are shrunk to at most 1024 pixels a side and shown with the phone's, and are deleted when you leave the screen unless you save one. Each request appears under **Sticker generator** in [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/). On a metered connection, [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/)'s **Cloud AI** row decides: at **Ask each time** the screen says so and a second press of **Generate** is the yes; at **Turn off** only the phone's model draws.
With an online engine, **Pictures per tap** on **Auto** draws one, since each may be paid for. The negative prompt goes to the engines that read one; GPT Image, Gemini and Grok Imagine leave it out. Size, steps and guidance are the phone's model's settings and disappear while an online engine is picked.
## Models on the phone, and what each phone gets
Two models come with the app's catalog. Neither is in the APK: the phone downloads the one you pick, after you accept its licence.
| Model | Download | How it draws | Runs on |
|---|---|---|---|
| **SDXS Dreamshaper** | about 880 MB, plus the 87 MB picture filter | one step, at 384 × 384 or 512 × 512 | every phone, on the processor |
| **Stable Diffusion 1.5 (NPU)** | about 710 MB from Qualcomm, plus the picture filter | 20 steps with guidance, at 512 × 512 | Snapdragon 8 Gen 3, 8 Elite and 8 Elite Gen 5, on the neural processor |
SDXS is the default everywhere. It was distilled to draw in a single step, and it draws clean, outlined cartoon subjects on a white background, which is what a sticker wants and what the background remover lifts cleanly. Measured on a computer and scaled to phones, one picture takes roughly:
| Phone | 384 × 384 | 512 × 512 |
|---|---|---|
| Recent flagship (Snapdragon 8 Gen 3 or 8 Elite, Dimensity 9300, Tensor G4) | under 1.5 seconds | about 1 to 2 seconds |
| Mid-range (Snapdragon 7 series, Dimensity 7000 series) | about 2 to 2.5 seconds | about 4 seconds |
| Older or low-end | about 5 to 6 seconds | about 10 seconds |
The first picture after the generator opens takes longer, because the model has to load. It then stays loaded for a few minutes, so the next prompts are fast. While loaded it takes one to two gigabytes of memory, in a process of its own: if Android needs that memory back it ends the generator, never the keyboard.
**Stable Diffusion 1.5 (NPU)** is Qualcomm's own build of the original Stable Diffusion for each chip's neural processor, about 2.5 seconds a picture on an 8 Elite by Qualcomm's figures. It is offered only on the three chips above, and it uses the same Qualcomm runtime as [running AI models on the NPU](https://wmkeyboard.pages.dev/tools/ai/#local-models). It is not better at stickers than SDXS. Choose it for what SDXS can't do: a negative prompt, a guidance strength and a step count. Its first picture also waits for the NPU runtime to download, if nothing has fetched it yet.
The model rows show what is downloaded. The bin deletes a model; About / Storage / Sticker generator lists them too.
## Writing a prompt
Describe one subject: *a red panda drinking coffee*, *a happy avocado giving a thumbs up*. The style chips under the box add the look, so you don't have to type it. **Sticker** (the default) asks for a die-cut cartoon with bold outlines on white; the others are **Cartoon**, **Kawaii**, **Pixel art**, **3D**, **Line art**, **Emoji**, and **No style** for a prompt that already says how it should look.
Under the box, a counter shows how many of the model's 75 tokens the prompt and its style take. The model reads no further than that. When a prompt runs past it, the words it won't read are named in red, so you know to shorten it, rather than finding out from a picture that ignored them.
### Prompts in other languages
The models understand English. A prompt in another language is translated first, and the English is shown under the box, where you can correct it before the next tap. The [on-device translators](https://wmkeyboard.pages.dev/tools/translate/#on-device-translation) are asked first (Firefox Translations, NLLB-200, Google's), so with a model for your language downloaded nothing leaves the phone. Only if none of them can translate it does the prompt go to the online translators on your translate list. A prompt in Latin letters counts as English unless Google's language identifier, where the phone has it, is sure it isn't.
### Improve with AI
With an [AI provider](https://wmkeyboard.pages.dev/tools/ai/) set up, **Improve with AI** asks it to rewrite your idea as a short, visual prompt: one subject, what it looks like and does, its colours. The rewrite replaces what you typed, so you can edit it or press Generate as is. This sends your prompt to that provider. The on-device AI model is not offered here, being too slow for a quick rewrite.
## Generating
**Generate** draws several pictures at once, each with its own seed, and each appears as soon as it is done. Flagships draw four, mid-range phones two, small phones one, and the NPU two; **Pictures per tap** under **Advanced** changes that. **Cancel** stops within a fraction of a second. With the NPU model, the pictures fade in as rough previews while the steps run.
Tap a finished picture, then **Cut out and edit**. The background remover lifts the subject off the white, and the editor opens with it, ready to crop, erase or add a border before you save it into a pack. The sticker goes into the pack you came from; opened from elsewhere, into the last pack you used, the first pack, or a new **Generated** pack when there is none. The generated pictures themselves are scratch and are deleted when you leave the screen; only the ones you save are kept.
### Advanced
- **Size**: 384 × 384 is faster and plenty for a sticker; 512 × 512 is SDXS's own size and holds more detail.
- **Seed**: the number every picture starts from. **Keep the seed** draws the same picture again for the same prompt, which helps when you are adjusting the wording; off, every tap tries new ones.
- **Steps**, **Guidance** and the **Negative prompt** (what to leave out) appear only for a model that uses them: the NPU model, or a model you imported. SDXS draws in one step without guidance, so it has none of them.
- **Remove the background**: on by default. Off, the editor opens on the picture as drawn. A picture that came back with a transparent background (GPT Image draws them that way) skips it. The F-Droid build has no background remover, so there the editor's eraser does the job.
- **Safety filter**: see below.
- **Draw on the GPU**: off by default, and listed only on a phone with a GPU the generator can use: an Adreno, or another GPU with Vulkan 1.2 (Mali, Xclipse, PowerVR). On, the on-device models (not the NPU model, which has its own) run on the graphics chip through OpenCL, or Vulkan where the phone has no OpenCL, at the same full precision as on the processor, so a seed draws nearly the same picture. The first picture after switching it on waits while the GPU compiles its code, which is kept for next time. Whether it is faster depends on the phone, so compare it with a generation on the processor. If the GPU cannot load a model, the processor draws it.
## The safety filter
Every prompt is checked before anything is drawn, as typed and in its English translation, against a list of sexual terms, and a match is refused. With **Safety filter** on (the default), every picture then goes through an on-device classifier (Falconsai's NSFW detector, 87 MB, downloaded with the first model), and one it judges unsafe is never shown: its place says it was held back. Both checks run on the phone. A word list can refuse a harmless prompt that happens to contain one of its words; rephrase it.
The prompt check applies to online engines too. Their pictures are not run through the phone's classifier; instead **Safety filter** turns on the service's own filter wherever it has a switch for one (Pollinations, AI Horde, fal.ai, Together AI, Replicate and FLUX's strictness), and a picture a service refuses or blurs is held back the same way. Every paid service also applies its own rules whatever the switch says.
## Licences
The models' weights come with their own licences, which you accept before the download: SDXS is under the CreativeML Open RAIL++-M licence, Qualcomm's Stable Diffusion 1.5 under the CreativeML Open RAIL-M licence and its NPU runtime under Qualcomm's. Both RAIL licences forbid using the model for illegal, harmful or deceptive content, and those restrictions carry over to whatever you make with it. SDXS comes from the project's data repository as an MNN conversion of [IDKiro/sdxs-512-dreamshaper](https://huggingface.co/IDKiro/sdxs-512-dreamshaper); Qualcomm's build comes straight from Qualcomm and is never copied by this project. See About / Licenses.
## Importing a model
Any Stable Diffusion 1.x or 2.x checkpoint can be imported once it has been converted on a computer; the phone never converts one itself. The converter is `tools/imagegen/convert_sd.py` in the [source repository](https://github.com/wasi-master/WMKeyboard). With Python and a few packages:
```sh
pip install torch diffusers transformers safetensors onnx MNN
python tools/imagegen/convert_sd.py my_model.safetensors my_model --name "My model" --steps 20 --guidance 7 --taesd
```
The source can be a `.safetensors` or `.ckpt` file, a diffusers folder, or a Hugging Face model id. Use `--steps 4 --scheduler lcm` for an LCM model, `--steps 1 --timesteps 999 --guidance 0` for a one-step model like SDXS or SD-Turbo, and `--taesd` to swap the model's image decoder for the much cheaper TAESD, which looks the same at sticker size. `--int8` halves the files at a small cost in faithfulness. The script calls MNN's converter library directly rather than its `mnnconvert` command, which reports every conversion to Alibaba Cloud.
The output folder holds five files: `text_encoder.mnn`, `unet.mnn`, `vae_decoder.mnn`, `merges.txt` and `wmimage.json`. Copy them to the phone, zipped or not, then **Import a model** and pick the zip or all five files. The generator checks that each network loads and takes the inputs `wmimage.json` describes before it lists the model; a model that doesn't fit is turned away with the reason. Imported models run on the processor, like SDXS. A 4-step LCM model at 512 × 512 takes several seconds a picture on a flagship and about 20 on a mid-range phone; an ordinary 20-step model with guidance makes 40 full-size passes, which on a phone's processor means minutes a picture.
**Stable Diffusion 1.5 (NPU)** can be imported the same way, from the build [Qualcomm AI Hub](https://aihub.qualcomm.com/) publishes for your chip: the zip it downloads, or its `text_encoder.bin`, `unet.bin`, `vae.bin` and `metadata.json` picked together, alongside `merges.txt` (the same CLIP tokenizer SDXS uses, from this project's [data repository](https://github.com/wasi-master/wmkeyboard-data/releases#release-models)). Qualcomm's zip doesn't carry it; when SDXS or another imported model is already on the phone, its copy is used and the zip imports on its own. It needs no `wmimage.json` — it runs on the schedule above — and it must be the build for your phone's chip, or the neural processor turns it away when it loads. This is the only route to the NPU model on the **offline build**, which has no network to download it with. Its runtime still has to be installed first, under Storage / Downloads. While the generator's engine library is missing, the **Engine** group says so and its **Get the files** lists the libraries to fetch and import, the NPU's too when that model is picked. A model on the phone whose files can't be read says so under **Generate**, instead of leaving the button off.
The checkpoint's own licence still applies to an imported model.
---
# Stickers
URL: https://wmkeyboard.pages.dev/emoji/stickers/
Section: Emoji & expression
Summary: Sticker search, custom packs, stickers offered as you type, and saving stickers straight from the keyboard.
The sticker tool searches KLIPY and GIPHY. Where the app on the other end supports them, results go out as real stickers, and you can build your own packs from photos already on your phone. It sits right next to [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) in the toolbox and shares most of its settings.
## Searching and sending
emoji/sticker-panel-unsupported-field — The standing notice a field that can't take images shows, with results dimmed underneath.
Open the tool from the toolbox, or from the toolbar once you've pinned it there. Type to search KLIPY and GIPHY, or switch to **My stickers** for packs you've built yourself. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) set to **Text and pictures**, a search in **My stickers** also finds your stickers by what they show, after the ones whose names or keywords match. Tap a result to download and insert it. While it downloads, the tile dims and shows a spinner over the preview. Tap more while it does and they queue up behind it, numbered in the order they'll be sent, then go out one by one. Tapping a numbered sticker again takes it out of the queue. This works for your own stickers too, and the queue is dropped if you move to another field before it finishes.
The emoji and GIF panels are one tap away. The bottom row under the results holds ABC, a switch to emoji, GIFs and stickers, space and backspace, the same row the [emoji panel](https://wmkeyboard.pages.dev/emoji/picker/#switching-to-gifs-and-stickers) ends in. A swipe to the right across the results goes to the GIF panel. That setting can put the switch next to the search box instead, or turn it off; the ABC, space and backspace stay. Like the GIF panel, the sticker panel is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) you can rearrange: its component is **Sticker search and results**. It opens as tall as the emoji panel's **Panel height** setting makes it, and like the GIF panel it has a [bar across its top](https://wmkeyboard.pages.dev/emoji/gifs/#making-the-panel-taller) that drags it taller or shorter. **Hide the search when you scroll** folds the search box and the chip rows away while you scroll down the results, [as on the GIF panel](https://wmkeyboard.pages.dev/emoji/gifs/#hiding-the-search-while-you-scroll).
With the search box empty, a scrollable row of category chips (Trending, Love, Happy, Sad, Cute…) sits above the grid. Tap one to run it as a search, and tap the active chip to go back to trending. An ✕ at the end of the search box, while it holds a query, empties it and goes back to trending in one tap. It works the same as the [GIF panel's row](https://wmkeyboard.pages.dev/emoji/gifs/#searching-for-a-gif), and like that one it steps aside as soon as you type. **My stickers** doesn't get the row, because its pack chips already fill that job.
Android has no dedicated "this is a sticker" flag. The only signal a receiving app gets is the file's MIME type, and WhatsApp in particular only recognizes its own private `image/webp.wasticker` type, not plain `image/webp`. So with **Send stickers as** set to Sticker, the keyboard offers that WhatsApp type first and falls back to a normal image if the field doesn't advertise it. A source that isn't WebP, an animated GIF result say, can never go out with the sticker MIME, because the keyboard sends a GIF as the GIF it is rather than re-encoding it. Those always send as a plain image whatever that setting says.
Your own still stickers keep the shape you made them in, so most apps get them as they are. WhatsApp and Messenger are the exception: both want a sticker as a 512×512 square, so a sticker sent into either goes out as a copy letterboxed into that square, with transparent bars on the short sides. The copy is made once and kept in the cache, so sending the same sticker again doesn't redo it.
A small sticker would have to be stretched to fill that square. Once the upscaler is on the phone (it downloads the first time you use **Upscale** in the [sticker editor](#the-sticker-editor) or the [photo editor](https://wmkeyboard.pages.dev/tools/gallery/#the-editor)), a still sticker under 384 pixels on its long side is redrawn two or four times larger first, and arrives sharp. That takes a moment on the first send and none after, since the square is kept in the cache like the plain one. If the upscaler takes longer than 20 seconds or fails, the plain square goes instead. Without the upscaler nothing is downloaded for this, and the sticker is stretched as before.
If the field can't take images at all, the tool doesn't download first and fail after. It vibrates once and shows a standing notice. Results stay visible underneath at 45% opacity, so you can still long-press one to save or copy it (see [Long-pressing a sticker](#long-pressing-a-sticker) below).
An animated sticker is its own case. Accepting `image/webp` and playing an animated one are two different things, and nearly every chat app does only the first: Messenger and Telegram both take the file and show one frame of it. A field can't be asked which kind it is, so the keyboard goes by the one type that is a promise. Where the field offers WhatsApp's sticker type, an animated sticker goes out as the animated WebP it is, and WhatsApp plays it. Anywhere else that takes GIFs, it goes out as a GIF, because a GIF moves in all of them. The keyboard draws the animation frame by frame and writes a 512×512 GIF with a transparent background. A toast says so, since the first send of a sticker takes a moment. The GIF is kept in the cache, so sending the same sticker again is immediate. A GIF is a rougher picture (256 colours a frame, and edges that are either there or not), and in Telegram it arrives as a GIF and not as a sticker. That is the price of it moving. If the GIF can't be made, the WebP is sent after all, still. If you've moved to another field by the time the conversion is done, the sticker is not dropped into a field you didn't send it to.
If the field does take images but rejects a still sticker, the keyboard tries a format the field does take before it gives up: a still WebP goes as a PNG. Only when nothing gets through does the file land on the system clipboard with a toast telling you to paste it. That's the same thing the clipboard tool's own image and file commits do when a field refuses them.
## Long-pressing a sticker
Long-pressing a KLIPY or GIPHY result opens a menu with three kinds of row. Above them, a GIPHY sticker whose uploader GIPHY names says who made it ("by @name").
- **Save to a pack**, shown as "Save to a new pack" if you have none yet, or "Save to ``" for each pack you've made. The keyboard can't pull a sticker back out of a chat someone sent you. To keep one of those, save it to your phone from the chat app, then add it with the **+** chip under [My stickers](#my-stickers-and-your-own-packs).
- **Copy** puts the sticker on the system clipboard instead of into the field, to paste wherever you like.
- **Report** opens a mail draft to the maintainer naming the provider, your search term, and the sticker's id and URL, with room at the top to say what's wrong with it. Nothing leaves the device on its own. The draft sits in your mail app for you to read, edit, or drop. KLIPY and GIPHY search their own catalogues, so results arrive unvetted, and this is where a bad one goes. [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) have the same menu, minus the save rows.
Long-pressing a sticker from **My stickers** starts with what the sticker is and what finds it: its title (or "No title"), the pack it's in, its keywords, and its emoji. Below that it shows **Change title and keywords**, which opens the app on that sticker's pack with its title and keywords already up to edit, so a keyword you think of mid-chat is two taps away; save, and Back returns you to the chat. Then "Manage packs" (which jumps straight to the pack management screen covered below) and **Copy**. There's no save row, since you already have it, and no report row, since it's your own file and there's nobody to report it to.
emoji/sticker-long-press-save — Long-press menu over a KLIPY result: save to a pack, copy, or report.
## My stickers and your own packs
emoji/sticker-pack-manage — A sticker pack's management screen, with reorder and export controls.
**My stickers** is always its own tab or chip. Your packs are never interleaved into the KLIPY or GIPHY grid, in either source layout mode. With more than one pack, a second row of filter chips (All, then one per pack name) appears above the grid, and search there matches a sticker's title, its keywords and its pack name together.
The **+** chip at the end of the source chips' row, beside **My stickers**, is the quick way to add a sticker. It opens the app on the pack with the photo picker already up, so adding one sticker is a tap and a pick. The pack is the one you filtered the grid to, or your only pack if you have one. Under **All** with several packs, it asks which pack first. It sits on the source chips' row so that a single pack, with nothing to filter, costs no row of its own. When **My stickers** is your only source and there are no source chips, the **+** chip starts the pack filter row instead. With no packs yet, the empty grid's **Manage packs** button is the way in instead.
Beside the **+** chip, the sparkle chip opens the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/): describe a sticker and the phone draws it offline, or an [image service you pick](https://wmkeyboard.pages.dev/emoji/sticker-generator/#engines) does, into the same pack.
Building and managing packs happens in the app, not the keyboard, under Tools / Stickers / Your stickers / Sticker packs.
- **New pack** starts one empty. **Import a pack** reads a `.wmstickers` file, or a `.wastickers` file made for WhatsApp. **Signal sticker packs** adds a pack made for Signal from its link, as described [below](#signal-sticker-packs). **Telegram sticker sets** does the same for a Telegram set, as described [below](#telegram-sticker-sets). **WhatsApp stickers** brings in a WhatsApp pack file or the stickers WhatsApp has saved on the phone, as described [below](#whatsapp-stickers).
- **Create stickers** below the list of packs, and **Create from a prompt** inside a pack, open the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/).
- Inside a pack, **Add stickers** opens the photo picker for up to 30 images in one trip. Each one is processed automatically. A still image keeps its own shape: anything over 1024 pixels on its long side is scaled down to that, a smaller one stays the size it is, and either way it's re-encoded as WebP, trying progressively lower quality (90 down to 50) until it lands under 400 KB. An animated source, GIF or animated WebP, is stored byte-for-byte instead, since re-compressing one would only cost quality. Those are capped at 2 MB rather than the 400 KB still budget. An animated PNG (APNG) is the one animation that is converted: nothing on Android plays an APNG and no app takes one as a sticker, so its frames are read out and rebuilt as a 512×512 animated WebP, aiming for WhatsApp's 500 KB animated budget. If the conversion fails, the first frame is kept as a still, and the result says how many stickers that happened to. Any single source file over 12 MB is refused before processing even starts.
- Pick exactly one still image and it opens in the [sticker editor](#the-sticker-editor) first, instead of being added as-is. Pick several and they're all added the old way, because an editor thirty times over isn't a flow anyone wants. Either way the photo behind each sticker is kept for later editing, as described below.
- Per sticker, you can set its **title** and its **keywords**, reorder it within the pack, move it to a different pack, or delete it. The title is the sticker's name, and it's what a screen reader reads out for it. Keywords are anything else you'd want to type to find it: words, short phrases or emoji. Put a comma between words and phrases ("cat, good night"). Emoji need nothing between them, so "😂🐱" is two keywords. Both fields feed the search in **My stickers**, and both can [offer the sticker while you type](#stickers-as-you-type).
- To delete several stickers at once, press and hold one in the pack. That starts selecting: each tick shows in the sticker's corner, a tap adds or removes another sticker, and a bar along the bottom counts them. **Select all** ticks the whole pack, and **Delete** asks once, with the count, before removing them all. Back or the cross leaves selection without deleting anything.
- Per pack, you can rename it, **export** it as a `.wmstickers` file to share or back up, or delete it. Deleting warns you to export first if you want to keep a copy.
- To act on several packs, long-press one to start picking, then tap the others, or use **Select all** in the bar at the bottom. The upload icon saves the ticked packs in one `.zip` holding a `.wmstickers` file per pack; the trash icon deletes them after asking once. While picking, a tap ticks a pack instead of opening it, and the close button or Back stops picking.
- A pack with stickers in it also has **Share with everyone** (the globe), which sends it to the [community repository](https://wmkeyboard.pages.dev/addons/community/) for anyone to install. Only share pictures you made or may pass on.
- A pack tops out at 200 stickers. You can have up to 50 packs in total.
### Keywords from AI
A pack you imported often comes with no keywords at all, which leaves its stickers out of [stickers as you type](#stickers-as-you-type). **Suggest keywords with AI** fills them in, using the provider picked in the [AI tool](https://wmkeyboard.pages.dev/tools/ai/). It's on a pack's page, for every sticker in it or, while you're selecting, for the ones you picked. On the **Sticker packs** list it covers every pack, or the packs you picked.
- A sheet opens first and sends nothing until you press **Start**. It names the provider and how many requests the run takes: one per sticker. **Anything to add** is passed on to the model, for example "these are for my family chat" or "add Bengali slang".
- The model is asked for up to ten keywords per sticker, in the [languages you type in](https://wmkeyboard.pages.dev/languages/overview/): the feeling or situation the sticker fits, what it shows, and a few emoji. It doesn't repeat the keywords the sticker already has.
- Claude, OpenAI, Gemini, Grok, Ollama, LM Studio and OpenAI-compatible services see the sticker itself, sent as a still picture up to 512 pixels wide. An animated sticker sends its first frame. The model on your phone sees it too if it can read pictures, and otherwise works from the words. Brave Answers, DeepSeek and Privatemode only get the sticker's title and its pack's name, and each card says when a suggestion came from the words alone.
- Each suggestion is a chip. Tap the ones you want, and type your own in **Your own keywords** under it. **Save** adds what you chose to each sticker's keywords and keeps the ones it had. Stickers you chose nothing for stay as they were.
- **Stop** ends the run. The suggestions that already came back stay, and **Start** carries on with the rest.
- With [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holding cloud AI back, the run stops at the first sticker and says why.
### The sticker editor
The editor opens two ways: picking a single still image from **Add stickers**, or tapping a sticker you already have and choosing **Edit the image**. Animated stickers don't get it at all, since there's no still to edit and the editor works on one picture, not on a run of frames.
The sticker keeps whatever shape the crop gives it, up to 1024 pixels on its long side, and there are four tabs:
- **Crop**: the whole picture with a frame over it. Drag an edge or a corner to change the frame, or drag inside it to move it. The frame can be any shape, and what's inside it is what's kept, in its own proportions. Nothing is squared off or stretched.
- **Erase** and **Restore**: a round brush with a size slider. Erase clears pixels to transparent, and Restore paints them back. A ring the size of the brush appears in the middle of the canvas when you switch to either tab or move the slider, then fades once you stop, so you can see what you're about to paint with before you paint with it. **Undo** goes back one stroke at a time, twelve deep.
- **Border**: the white or black outline that hugs the cut-out shape, WhatsApp-style, with a width slider from 0 (off) up to 24. The brush and border sizes are measured as if the sticker were 512 pixels on its long side, so the same value looks the same on a small sticker and a large one. It traces whatever is left after erasing, so it's most useful once the background is gone. The border is a setting rather than a stroke. Sliding it back to 0 is its own undo, which is why the Undo button doesn't touch it.
**Remove the background** does the cutout in one tap. It's in every build but F-Droid's, and even there the model isn't in the APK, so the first tap offers a download with a progress bar and then runs. On a phone with Google Play services the model is Google's and comes from Play services on demand. On a phone without them, or with a stand-in such as microG that doesn't carry that module, the app downloads its own instead: U²-Net-P, an open (Apache-2.0) 5 MB network, fetched from the project's data repository, checked against a fixed SHA-256 before it's used, and run entirely on the phone. On the Play Store build the interpreter that runs it is itself an extra part that Google Play delivers first (about 2 MB, shared with offline voice typing), and the one progress bar covers both. Google's is the better of the two, which is why it goes first where it's available. The downloaded model shows up as **Background remover** under Stickers on the [storage screen](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-app-is-using), and deleting it there only means the next cutout downloads it again. If no model can be fetched, the brushes still work and the screen says so. If it finds nothing recognizable as a subject, or would keep the entire image, it says that too and leaves your picture alone rather than erasing everything. In the lite build the button isn't there at all.
**Upscale** redraws a small sticker larger and sharper than stretching it would. It appears below the crop step when what you cropped is 640 pixels or less on its long side: four times the size for 256 pixels or less, twice otherwise, and the row shows the size it will make. A sticker is still kept at up to 1024 pixels when you save. The work happens on the phone, with waifu2x's network for drawings, and transparent edges are redrawn too, so the outline stays crisp. A bar shows the progress and **Cancel** stops it. The first time, the upscaler downloads: a 2.2 MB network from the project's data repository, checked against a fixed SHA-256, and on the GitHub and Play builds the engine the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/) runs on, if that isn't on the phone yet. After an upscale the larger picture is the one the sticker comes from: anything you erased is scaled to match, Undo can't go back past it, and a later **Edit the image** starts from it. The network shows up as **Upscaler** under **Sticker generator** on the [storage screen](https://wmkeyboard.pages.dev/reference/settings/about/#seeing-what-the-app-is-using).
Within one editing session an edit is destructive. Cropping again after you've erased something warns you first, because a new crop throws the erasing away. Nothing is written until you press Save.
Between sessions it isn't. When you add a sticker from a photo, the keyboard keeps that photo (resized to 1536px on its long edge, as WebP) alongside your packs, together with the edit you made: where you cropped, what you erased, the border, and even the brush size you were using. **Edit the image** reopens the photo with all of that still on it, so a second pass picks up where you left off instead of starting over. Change the border, widen the crop, erase a bit more, then save again.
A resumed edit opens on the **Erase** tab rather than **Crop**, since the crop is already made and a stray drag there is the one gesture that would throw the erasing away. **Undo** starts empty each time you open the editor, because it only covers the strokes of this session. "Crop again" is what clears an old cut-out, and it asks first.
Stickers that arrived some other way (saved from KLIPY or GIPHY, imported in a pack) have no separate photo behind them, so they reopen as themselves. The first time you edit one, the version you're replacing is kept as its original, and from then on it resumes like any other.
None of this leaves the phone. Exporting a `.wmstickers` pack writes the manifest and the finished stickers only, the settings backup's sticker section walks the same list, and Android's own cloud backup and device transfer skip the folder entirely. It does take up space, so About / Storage / Sticker packs lists it as its own row ("Images you made stickers from"). Delete only that row to get the space back while keeping every sticker. Deleting it costs the resumable edits too, and those stickers go back to reopening as themselves.
## Stickers as you type
You don't have to open the sticker tool to send one of your own stickers. Type its title in a chat, or one of its keywords, or one of its emoji, and the keyboard shows it. Tap it and it's sent. This is only for stickers in your own packs, and it all happens on the phone. Nothing is searched online.
- **The title counts only when you've typed all of it.** A sticker called "grumpy cat" shows up after "grumpy cat", not after "grumpy". If you want a shorter way in, add "grumpy" or "grr" as a keyword. A keyword can be as short as you like.
- **Words have to be whole words.** A sticker with the keyword "cat" shows up for "my cat" but not for "concat" or "cats". Capitals don't matter.
- **Emoji work anywhere.** A 😂 keyword fires right after "lol😂", with no space needed. Skin tones don't matter either way, so a 👍 sticker shows up for 👍🏽 too.
- **The longest match comes first.** With one sticker called "happy birthday" and another with the keyword "birthday", typing "happy birthday" shows both, the first one first. Within one match, stickers whose title it is come before stickers that only have it as a keyword. Up to 24 show at once.
- **Only in fields that can take a picture.** If a chat app's message box doesn't accept images, nothing shows, because a sticker couldn't be sent there anyway. Password fields never get it.
- **Untagged stickers can be tagged for you.** With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) set to **Text and pictures** and **Tag my stickers** on, a sticker with no title and no keywords gets a few from what it shows, such as 🐈 and "cat", and comes up for them. Your own keywords are never changed.
- **It works from the emoji panel too.** Pick an emoji in the panel that one of your stickers has as a keyword or title, and that sticker shows in a row above the panel. In the panel only emoji count. Words you typed before you opened it don't.
**Where they show** has three choices:
- **Tray** (the default) is a row of large stickers at the top of the keyboard, above every other row. It slides in when something matches and slides away when nothing does, and the word suggestions stay where they are. The ✕ at the end hides it. To put it somewhere else, drag **Sticker suggestions** in Rows & bars / Row order. It can go anywhere, below the keys included.
- **On the strip** puts three small stickers on the suggestion strip, next to the word suggestions. If there are more, a **+N** button opens the tray with all of them.
- **One chip** is a small chip on the strip that says how many stickers match. Tap it to open the tray.
In the two strip styles, a long press on the stickers or the chip hides them.
**Hold a sticker in the tray to see it bigger.** It shows large above the keyboard, with its title under it. Keep your finger down and slide along the tray, and each sticker you pass shows large in turn. Lift your finger and it closes. Nothing is sent, so tap the sticker when you've found the one you want. A slide that starts before the hold scrolls the tray as usual. Turn this off with **Hold to see them bigger**, and a hold does nothing.
**After you send one** decides what happens to the text that asked for the sticker. **Remove the typed text** (the default) takes the title, keyword or emoji back out of the message box, so "see you 👋" becomes "see you " once the 👋 sticker has gone out. **Keep the typed text** leaves it where it is. Either way, once you've sent a sticker or hidden the tray, the same text doesn't bring it back. Type something else and it's live again.
The sticker goes out the same way it would from the sticker panel, with **Send stickers as** deciding between a sticker and a plain image.
### Importing a `.wmstickers` file
A `.wmstickers` file is a ZIP with a `pack.json` manifest and one image per sticker. Opening one, from **Import a pack** or from a file manager, first shows every sticker in it on a page of its own, all ticked. Tap a sticker to leave it out, or use **Select none** and tick the few you want. Only the ticked stickers are converted and added when you press **Add**, which matters for a pack of animated stickers: each animation takes a while to convert, and the ones you leave out cost nothing. The same page opens for a `.wastickers` file. Pick several files at once, or a `.zip` of packs saved with the bar's upload icon, and the page is skipped: every sticker of every pack is added, and one message counts the packs and names any file it could not read.
Import forgives hand-written or slightly malformed manifests, repairing what it can rather than rejecting the whole file, and it reports what happened:
- Imported successfully, with a note of anything it had to fix along the way.
- "The app cannot read any sticker in that pack." if the manifest named stickers but none of them survived. Import reports that rather than quietly creating an empty pack.
- "That file is not a WM Keyboard sticker pack." if the format tag doesn't match.
- "You already have 50 sticker packs. Delete one first." if you're at the pack limit.
An import is also capped against oversized or booby-trapped files: at most 500 entries and 64 MB total per archive.
### Signal sticker packs
Packs made for Signal (and for Molly, which is Signal under another name) can become packs of your own. Signal has no sticker shop. A pack is public to whoever holds its link, and the link is all there is: `https://signal.art/addstickers/#pack_id=...&pack_key=...`. The second half of that link is the key the stickers are encrypted with, so the link is both the address and the way in.
Open Tools / Stickers / Your stickers / Sticker packs / Signal sticker packs. There are two ways in from there:
- **Add a pack from a link** takes a pasted link. The `sgnl://addstickers?...` form that Signal itself opens works too. You can also share a pack link to WM Keyboard from a browser or a chat, or paste it into **Import from a link** on the Add-ons screen. All of them end up in the same place. The field never fills itself from the clipboard.
- **Find packs on signalstickers.org** opens the community's gallery in your browser. That is where the thousands of packs people have made are listed, with its own search and its Latest, Trending and Most viewed orders. Press **Add to Signal** on a pack there and Android asks which app should open it. Pick **Add stickers to WM Keyboard**. If Signal and Molly aren't installed, it opens WM Keyboard straight away. The gallery is run by volunteers, not by Signal and not by this project, and the keyboard never reads it: you browse it like any other website.
The keyboard offers no list of packs of its own, not even the ones Signal ships with. A sticker stays the work of whoever drew it, and the packs built into Signal are art Signal commissioned and keeps the rights to. So a pack only gets here because you brought its link, the same as any picture you add to a pack yourself. Add packs you may use.
What opens next is a preview and not an import: the pack's name, who made it, how many stickers it holds, and the stickers themselves. Every sticker starts ticked, and a tap on one leaves it out; **Select all** and **Select none** sit above the grid. Only the ticked stickers are downloaded and converted, and with some left out the button reads "Add 12 stickers" instead. Nothing is kept until you press **Add this pack**. After that the pack sits in **Your packs** like any other. You can rename it, edit its stickers, export it as a `.wmstickers` file, and send from it in every app, not only in Signal. Each sticker keeps the emoji its maker gave it as a keyword, so typing 😂 in **My stickers** finds the ones tagged with it, and typing 😂 in a chat [offers them](#stickers-as-you-type). Open the same link again and the preview offers to open your copy instead of adding a second one.
The stickers come from Signal's sticker storage, the same place a Signal client gets them from when you tap a pack link there. No Signal account is involved and nothing about you is sent, only the pack's id. Every file is checked against the key from the link before it is opened, so a file that was changed on the way is refused. A link whose key doesn't fit says so, and so does a pack that Signal no longer has. While [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is on, the preview asks before its first request. That one answer covers the pictures and the import.
Stickers are made to fit the same way as any other you add: a still WebP no more than 1024 pixels on its long side and under 400 KB is kept as it is, and anything else is resized and re-encoded.
Animated Signal stickers are APNG files, which Android can't play and which no other app accepts as a sticker. Each one is converted to an animated WebP as it is added, frame for frame and with the same timing, so it moves in **My stickers** and goes out to WhatsApp as a real animated sticker. Other apps get it as a GIF, as described under [Searching and sending](#searching-and-sending). In the preview, before you add the pack, animated stickers already play, so you can tell what kind of pack it is. Every frame is encoded again, so this is the slow half of adding an animated pack, and it is what the "Adding N of M" half of the progress row is counting. On Android 8.1 and older the grid shows an animated WebP as a still, but it is still sent animated.
### Telegram sticker sets
Sticker sets made for Telegram, and its sets of custom emoji, can become packs of your own too. A set is shared as a link, `https://t.me/addstickers/` (or `https://t.me/addemoji/` for emoji), and the name at the end is all there is to it.
Unlike Signal, Telegram has no open storage that a set can be read from. The one way in that needs no Telegram account is a bot: a free Telegram account meant for programs, which the Bot API lets ask for any public set. So you make a bot once, and the keyboard asks Telegram for sets through it. The keyboard never signs in to your own Telegram account and does nothing else with the bot.
Open Tools / Stickers / Your stickers / Sticker packs / Telegram sticker sets.
1. Press **Make a bot with @BotFather**. It opens Telegram's bot that makes bots. Send it `/newbot`, then a display name, then a username that ends in `bot`.
2. @BotFather answers with a token, which looks like `123456789:AAE...`. Copy all of it into **Bot token**. The field says so if what you pasted can't be a token.
3. **Add a set from a link** now takes a pasted link, or only the set's name. In Telegram, open a set, press the menu, and press **Copy link** to get one. The `tg://addstickers?set=...` form Telegram opens works too, and Android offers WM Keyboard as **Add stickers to WM Keyboard** when you press **Open in Telegram** on a `t.me/addstickers` page in a browser. You can also share a set link to WM Keyboard, or paste it into **Import from a link** on the Add-ons screen. The field never fills itself from the clipboard.
The token is yours and stays on this device. It is left out of backups unless you turn on **Include API keys**, the same as the other keys. There is no built-in token: one token shared by every copy of the app would be one that Telegram could block for everybody at once. If you revoke the token in @BotFather, the screen tells you that Telegram no longer takes it.
As with Signal, what opens is a preview: the set's title, how many stickers it holds, and the stickers, with the animated ones already playing. Video stickers show the still Telegram made of them. As with Signal, every sticker starts ticked and a tap leaves one out. Leaving out the animated and video stickers you don't want is what makes a big set quick to add, since those are the ones converted to animated WebP on the way in. Nothing is kept until you press **Add this set**. Each sticker keeps its emoji as a keyword, so it is [offered while you type](#stickers-as-you-type) like any other. Opening the same set again offers your copy instead of a second one. The keyboard offers no list of sets of its own. Sets are other people's work, so add the ones you may use.
Telegram has three kinds of sticker, and each becomes one you can send in every app:
- **Still stickers** are WebP pictures, kept as they are when they are small enough, and resized like any other picture when they are not.
- **Animated stickers** are `.tgs` files: animations drawn from shapes, made in After Effects and saved for the Lottie player. No other app accepts one. Each is drawn frame by frame, at up to 30 frames a second, and saved as an animated WebP.
- **Video stickers** are short `.webm` videos with a see-through background. Android's video decoder throws the transparency away, which would put every one of them on a black square, so the keyboard reads the transparency out of the file on its own and puts it back on each frame. They become animated WebP as well.
Turning animated and video stickers into WebP is the slow part of adding a set, and the "Adding N of M" half of the progress row counts it. A sticker that cannot be converted is kept as its first frame, and the result says how many that happened to. Like animated Signal stickers, the result goes to WhatsApp as a real animated sticker and to other apps as a GIF.
The requests go to Telegram's Bot API, `api.telegram.org`, and carry your bot token and the name of the set. While [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is on, the preview asks before its first request.
### Emotic reaction images
[Emotic](https://f-droid.org/packages/futuristicgoo.emotic/) keeps reaction images, which it calls emotipics, beside its emoticons. Importing an Emotic export from Emoji / Emoji panel / Kaomoji / Import puts its faces in your [kaomoji library](https://wmkeyboard.pages.dev/emoji/kaomoji/#importing-from-emotic-and-other-files) and its images in a new pack named Emotic. Each image keeps its Emotic tags as keywords, so it shows up in search and [as you type](#stickers-as-you-type). Images marked as excluded in Emotic are left out, and an image past 20 MB is refused.
### WhatsApp stickers
WhatsApp doesn't share its sticker packs with other apps. A pack gets into WhatsApp through another app's content provider, and only WhatsApp is allowed to read that provider; the packs WhatsApp itself ships, and the ones you've marked as favourites, live in its private database. No app can read either without root, and this keyboard doesn't try. Two things on the phone *are* open, and Tools / Stickers / Your stickers / Sticker packs / WhatsApp stickers reads both. Nothing leaves the phone, and WhatsApp doesn't learn about any of it.
- **Open a .wastickers file.** This is the pack file the sticker-maker apps export: Sticker Maker, Personal Stickers for WhatsApp, and the desktop tools that imitate them all write it. It's a plain ZIP with a `title.txt`, an `author.txt`, the tray icon as a small PNG, and one WebP (or PNG) per sticker. Export or share a pack from the app you made it in, then open the file here, tap it in a file manager, or share it to WM Keyboard from the share sheet. It opens on a page of every sticker in the pack, with its title and who made it at the top, all ticked. Untick the ones you don't want and only the rest are added. The title becomes the pack's name. The author is shown once and not kept, because a pack here has no author field. The tray icon is left out, found by its name (`tray.png`, `cover.png`, `icon.png`) or, when it's named something else, by being the one PNG among WebPs. A pack made entirely of PNGs keeps every picture, so a tray named by a timestamp in one of those arrives as a small extra sticker rather than a real sticker being dropped. **Import a pack** on the Sticker packs screen takes the same file, so you don't have to know which of the two formats you were handed.
- **Add the stickers that WhatsApp saved.** WhatsApp keeps a copy of every sticker you've sent or received under `Android/media/com.whatsapp/WhatsApp/Media/WhatsApp Stickers` (WhatsApp Business uses `com.whatsapp.w4b` instead). That folder carries a `.nomedia` marker, so the photo picker that **Add stickers** uses never shows these files. The system's folder picker does, and this row opens it there. Confirm the folder and every WebP in it shows on a page, newest first, with the newest 200 ticked, since a pack holds 200. Untick what you don't want, or tick others instead, and the ticked ones become one pack called "WhatsApp stickers". Tick more than 200 and the pack keeps the first 200, and the result says how many were left out. Stickers that were made for WhatsApp are already 512×512 WebP files, so they're kept byte for byte, animated ones included. The keyboard reads the folder once and keeps no access to it afterwards.
- **Pick stickers for one of your packs** opens the system's file picker in the same folder with multi-select on, and adds the ones you choose to a pack you already have, or to a new one it asks you to name. The precise route when the folder holds far more than you want.
Neither the file nor the folder carries emoji or keywords, so stickers arrive with none. Give them some from the pack's own screen if you want them [offered while you type](#stickers-as-you-type). Since WhatsApp packs are other people's work as much as Signal packs are, add the ones you may use.
Sticker packs are an optional section of the app's full settings backup. They're off by default, because images make the backup file large. Turn them on in the Backup screen if you want packs included, or export a pack individually from its own screen instead.
## Options
Tools / Stickers
Stickers, GIFs and emoji mixes are one bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the three panels, the pack screens and the generator leave the keyboard and the app together; your packs stay on the phone.
Stickers isn't pinned to the toolbar by default. It lives in the toolbox alongside GIFs until you pin it. Reach the settings below from **Tools** in the app.
- **Sticker packs** jumps to the pack management screen above. Only shown under Stickers, not under GIFs.
- **Full-screen picker**, under Tools / Stickers / Layout, default **on**. The toolbar, emoji row, and symbol row step aside and the search box moves into the row they leave behind, so the grid gets the full keyboard height. Shared with the GIF tool.
- **Stickers per row**, under Tools / Stickers / Layout, 1 to 6, default **3**. The most stickers side by side. Fewer makes each one bigger. The GIF tool has its own **GIFs per row**.
- **Gap between cells**, under Tools / Stickers / Layout, 0 to 12 dp, default **4 dp**. The space around each sticker. Shared with the GIF tool.
- **Panel height**, under Tools / Stickers / Layout, default **Same as keyboard**. The emoji panel's setting, here too because the bar on top of the panel drags it.
- **Remember the last search**, under Tools / Stickers / Layout, default **on**. Reopening the panel lands on the search and category it was left on; off, it opens on trending. Shared with the GIF tool.
- **Hide the search when you scroll**, under Tools / Stickers / Layout, default **off**. Scrolling down the results folds the search box and the chips away, and scrolling up brings them back. Shared with the GIF tool.
- **Panel layout**, under Tools / Stickers / Layout. Opens the sticker panel's layout editor. **Standard** ends the way the emoji panel ends.
- **Providers**, at the top of Tools / Stickers: KLIPY, GIPHY and **My stickers**, in the order the panel asks them. Add, reorder, switch off or set up each one, as in every [provider list](https://wmkeyboard.pages.dev/tools/providers/). KLIPY and GIPHY need an API key unless the build carries one, and their keys are shared with the GIF tool. KLIPY replaced Tenor after Tenor's API was retired.
- **Offer your stickers as you type**, under Tools / Stickers / Suggest while typing, default **on**. Only shown under Stickers. See [Stickers as you type](#stickers-as-you-type). While it's on, two more rows sit under it:
- **Where they show**: Tray, On the strip or One chip, default **Tray**.
- **After you send one**: Remove the typed text or Keep the typed text, default **Remove the typed text**.
- **Hold to see them bigger**, default **on**: hold a sticker in the tray to see it large, and slide to see the others.
- **Send stickers as**, under Tools / Stickers / Sending, Sticker or Image, default **Sticker**. Only shown under Stickers, since it governs what the sticker panel commits. The GIF panel has its own **Send GIFs as** on its own page.
- **How to use them**, in the provider list: Tabs, Mixed or One at a time, default **Tabs**. Tabs gives every provider its own chip, including My stickers. Mixed collapses the online providers into one "Online" chip and keeps My stickers as a chip of its own. GIPHY keeps its own chip in Mixed mode too, since its terms forbid mixing its results with another service's. One at a time shows the first online provider that answers and asks the next one only if it fails.
- **Content filter**, Off/Low/Medium/High, default **High**. Maps to KLIPY's and GIPHY's own content rating.
- **Results for each search**, under Tools / Stickers / Results: a slider from 6 to 48, default **24**. It sets how many stickers one search or the trending view loads. Your own packs ignore it, so **My stickers** always shows everything that matches, however long the list. Shared with the GIF tool.
Both tools are among the ones the setup wizard suggests enabling, and neither needs a particular build. What an API key gates is the online half. The GIF panel has nothing to show without one, while **My stickers** needs no key at all, so the sticker tool never lands on a "needs an API key" notice the way the GIF tool can.
## Details and edge cases
- **The WhatsApp sticker MIME is an unofficial, undocumented contract.** `image/webp.wasticker` appears in no public spec. It's a private convention both WhatsApp and Gboard happen to implement, and it can change without notice, which is why there's a fallback chain rather than a single hardcoded rule.
- **A field that only takes images, not stickers, still gets a normal image.** The sticker-versus-image choice is best-effort on top of whatever the field actually advertises. Nothing is dropped over a MIME mismatch alone. The clipboard fallback only kicks in when every image MIME candidate is rejected.
- **Save and report don't apply to your own stickers.** Long-pressing something from My stickers offers pack management and Copy only. There's nothing to save (you already have it) and nothing to report (nobody served it to you).
- **A source already at spec is left alone.** If a picked image is already a still WebP of 1024 pixels or less on its long side and under 400 KB, it's stored as-is rather than re-encoded.
- **Stickers made before this change stay square.** They were letterboxed into 512×512 when they were added. Open one in the editor and drag the crop frame to the shape you want, then save. Where the photo it was made from is still on file, the editor opens that, so nothing is lost to the old square.
- **One process, one store, but two independent views of it.** The keyboard and the settings app run in the same app process and share the same on-disk pack store, so every mutator saves immediately rather than batching. Each side still caches its own snapshot of the packs. The keyboard checks a cheap size-and-timestamp signal against the manifest and only reloads when that changed, rather than re-parsing it on every keystroke.
- **Content filter doesn't get a restore button.** Every other row on this screen grows [a small restore button](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back) once you move it off its shipped value. The filter is drawn as a bare segmented row under a heading rather than as a full setting row, so there's nowhere to hang one. Undo it by picking High back yourself. The provider list has **Reset to default** instead.
- **Packs need the device unlocked.** Sticker packs live in app-private storage that isn't available before you unlock the device after a restart, so the sticker tool isn't usable from a locked-screen field the way some other tools are.
See also [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) for the tool this one shares its settings and layout with, and [Emoji picker & search](https://wmkeyboard.pages.dev/emoji/picker/) for the other route into expressive content on this keyboard.
---
# The toolbar
URL: https://wmkeyboard.pages.dev/tools/overview/
Section: Tools
Summary: One row, many tools: how the toolbar works and how to make it yours.
WM Keyboard ships 90 tools (panels, sensors, generators, and one-tap actions), but only a handful sit on the toolbar at once. The rest wait one tap away in the toolbox, and you decide which is which by dragging tools between the two.
tools/toolbar-and-toolbox — The toolbar's default row (Emoji, Clipboard, Settings) with the toolbox grid open below it.
## Pinning and reordering
The toolbar is a strip of pinned tools. The toolbox, behind the grid button on the toolbar, holds every other enabled tool that isn't pinned, and a tool never sits in both places at once. Out of the box a phone pins three: **Emoji**, **Clipboard**, and **Settings**. A tablet starts wider, with five on a small tablet (GIF and Voice join the three) and seven on a large one (Translate and Text edit as well); see [Size & position](https://wmkeyboard.pages.dev/typing/size-position/#on-a-tablet). Everything else starts in the toolbox, ranked roughly by how often people reach for it: expressive media first, then everyday text helpers, then keyboard tweaks, with the specialty and sensor tools last.
Open the toolbox and hold-and-drag a tool:
- Drag a toolbox tool up onto the toolbar to pin it.
- Drag a tool around inside the toolbox grid to reorder it there.
- Drag a pinned toolbar tool back down into the toolbox to unpin it.
- Drop a tool on the middle of another tool to put the two in a folder. See [Folders](#folders). The middle is small on purpose: anywhere else on it, the tool takes that place. With a folder open, the star makes its toolbar button run the first tool on a tap and open the folder on a hold.
It's the same drag-to-pin model Gboard uses. The first time you open the toolbox, a hint explains the drag. Dismiss it and it only resurfaces occasionally as a reminder.
Picking **Recommended** rather than **Everything** during onboarding only decides which tools are *enabled*, meaning visible anywhere at all. It leaves the pinned row alone. The row stays at whatever your device shipped with until you drag something onto it yourself. If a tool you expected isn't in the toolbox, open Tools and turn it back on.
A keyboard [mode](https://wmkeyboard.pages.dev/typing/modes/) can carry its own pinned toolbar, separate from this global one. Switching into that mode swaps the whole row. The modes page covers how that works.
## Hold a tool for a second action
Tools / (any tool) / Press and hold on the toolbar
A pinned tool answers three gestures: a tap runs it, a hold that stays put does a second thing, and a hold that drags picks it up. The hold runs when you lift your finger, and only the hold: the tap's action does not run as well.
Out of the box, the tools that come in pairs hold to their partner, the same pairs HeliBoard uses:
| Tool | Hold runs |
|---|---|
| Clipboard | Paste |
| Paste | Clipboard |
| Copy | Cut |
| Undo | Redo |
| Redo | Undo |
| Select word | Select all |
| Select all | Select word |
Every other tool holds to its own settings page. The cursor tools have pairs too (left and right to a word left and right, up and down to page up and down, word left and right to the start and end of the line), but their hold repeats the move, so the pairs only take over once **Hold to repeat on the toolbar** is off; see [Text editing](https://wmkeyboard.pages.dev/tools/text-editing/).
All of it is yours to change. Open any tool's own page and the **Press and hold on the toolbar** row picks another tool for its hold to run: hold Text edit to open the clipboard, say. The row shows what a hold does right now. The picker's first row, **Default**, says what the tool does untouched. **Settings page** gets the settings page back on a tool that has a pair, and **Does nothing** is for a tool whose hold you would rather have do nothing at all: it still buzzes, and nothing opens. On a tool that can [float over other apps](#tools-in-a-floating-box), **In a floating box** opens the tool itself in a box over the app.
The picker fills the screen. The tools sit in the same groups as on the Tools page, with a search field over them that matches a tool's name or its description. Tap a group's heading to fold it away; a folded heading still shows how many tools it holds. The key and swipe action picker in the layout editor works the same way. Each picker opens again with the groups folded and scrolled the way you left them, until the app is closed. The first time, it opens on the current choice.
How long a press has to last before it counts as a hold is **Press and hold delay**, under Appearance / Toolbar. At **System** it follows the touch and hold delay in your phone's accessibility settings; slide it up to 1.5 seconds if holds keep firing when you meant to tap, or down to 100 ms for a quicker hold. It applies to every hold on a tool, in the toolbox as well as on the bar.
The third gesture is optional too. **Rearrange by dragging**, under Appearance / Toolbar, is on by default; switched off, a hold that drifts stays a hold and ends as whatever a stationary one would, and the toolbox is where you rearrange the bar instead.
- It changes the toolbar only. In the toolbox a hold always opens the tool's settings, and the Tools screen still lists every tool, so the settings page never goes out of reach.
- The bound tool runs whether or not it is on the toolbar or even enabled. Binding it is reason enough.
- The row is on every tool's page, pinned or not, so you can set a hold before you pin the tool. It does nothing until the tool is on the bar.
- Cursor tools that repeat on hold, Selection mode while its hold arms the mode, Trackpad while its hold opens the pad, and Voice while its hold picks a [voice typing mode](https://wmkeyboard.pages.dev/tools/voice/#starting-dictation), say so instead of offering a second action. One gesture cannot do both. See **Repeat cursor tools on hold** under [Typing](https://wmkeyboard.pages.dev/reference/settings/typing/).
- Binding a tool to itself is refused, since that is a slow tap.
- Nothing is shown while you hold. The buzz says the hold registered; the tool washes out and the drag notice appears only once your finger moves.
## Tools in a floating box
A few tools can open in a box of their own that floats over whatever app you're in and stays there when the keyboard closes: **Calculator**, **Unit converter**, **Currency converter**, **Clock**, **To-do list**, **Notes** and **Translate**. The box shows the same panel the tool has in the keyboard, with the same sums, timers, tasks and notes, since it's the same tool.
There are two ways to open one:
- Bind a tool's hold to it: on the tool's page, set **Press and hold on the toolbar** to **In a floating box**. Holding the tool on the toolbar then opens it in the box.
- From the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#gestures): its action list has a **Floating boxes** group, so a tap, a hold or a quick menu action can open, say, **Calculator in a box** straight over the app, without the keyboard coming up first.
Drag the box by its title bar to move it, and drag the grip in its bottom right corner to resize it. It opens where you last left it, at the size you last gave it. If the open keyboard would cover it, the box moves up to sit above the keys, and goes back to its place when the keyboard closes. The cross in its title bar closes it. The arrows next to the cross move the tool into the keyboard as its panel, for when you want the keys under it.
To get the box out of the way without closing it, tuck it into the side of the screen, the way the floating button tucks: push it past the left or right edge by its title bar, or tap the line in its title bar to send it to the nearer side. A small tab with the tool's icon stays on that edge. Drag the tab up or down to move it along the edge, and tap it to bring the box back where it was. The tool's button on the toolbar brings it back too. While the box is tucked, the keys type into the app's field.
Only one box is open at a time; opening another tool in it swaps what it shows. While a tool is in the box, its button on the toolbar doesn't open a second copy in the keyboard: the box gives a small bounce to show where the tool already is.
The box never takes the focus away from the app, so the keyboard keeps typing into the app's field. A tool's own text box is the exception: tap a note, a task's name or the text to translate, and the keys type there, as they do with the tool open in the keyboard. If the keyboard is closed, tapping the text box brings it up. **Insert** in the calculator and the converters types the result into the app's field while the keyboard is open, and copies it to the clipboard while it's closed. Translate's **Insert** and **Replace** do the same with the translation.
Translate shares its text box with the keyboard's own search panels, such as GIFs and web search. If you open one of those while Translate is in the box, the box keeps showing its last translation and waits. Its text comes back, ready to type into, once the keyboard is down to its keys again. Text you selected and sent to Translate from the selection bar goes into the box, too.
Tools / Close floating boxes on a tap outside
For a box that comes up for one sum and goes, turn on **Close floating boxes on a tap outside**. A tap anywhere else on the screen then closes it, and the tap still reaches the app underneath. Taps on the keyboard don't count, so typing into the box's text box doesn't close it. The setting is off by default, and a tucked box ignores it.
The box draws over other apps through the same **Display over other apps** permission the floating button uses. The first time you open a box without it, the keyboard explains why and opens Android's page for it.
## Tools on the home screen
Tools / (any tool) / Add to home screen
A tool with a panel can have an icon of its own on your home screen, so the level, the compass or the unit converter can stand in for an app you no longer need. **Add to home screen** on the tool's page asks your launcher to pin it. Tapping the icon opens a blank text box with the keyboard up and that tool already open on it. Whatever the tool types, such as a sum, a translation or a password, lands in the box, and **Copy** and **Share** under it take it from there. A second tool's icon opens in the same screen over a fresh box. The tool opens in WM Keyboard only, so if another keyboard is in use the screen says so and offers to switch. The Clock, Notes and To-do list pin their own screens in the app instead.
## What does this button do?
Inside a tool's panel, most buttons are bare icons: the clipboard's filter and list switch, a refresh arrow, a swap, the speaker in the dictionary. Press and hold any of them and its name pops up above it for a moment. A hold that shows the name doesn't press the button, so it's safe to ask before you tap. Buttons that already have a hold of their own keep it, and a button with its name written on it doesn't need one.
## Two ways to draw the toolbox
Appearance / Toolbar
**Toolbox layout** picks between them:
- **Icons** (the default) is the grid you've seen above: a round icon per tool with its name in small text underneath, 4 to a row. Compact, and the most tools on screen at once.
- **Pills** gives each tool a wide rounded row instead, two to a row. The icon sits on the left in the tool's own color and the name reads across the middle at full size. Tools that *open* something get a small arrow on the right, and the arrow is the reason to use this layout. **Appearance** and **Clipboard** take you somewhere. **Flashlight** and **Undo** just do their thing and leave the keys where they are. The arrow tells you which is which before you tap.
Two settings apply to pills only. **Pills per row** goes from 1 to 3: one gives every name the full width, and three really only fits in landscape. **Fill pills with the tool color** paints the whole pill in the tool's accent color and flips the icon and name to white, or to near-black where the accent is too pale for white to read on. It needs **Colorful tool icons** (under Tools) on, since that's where the colors come from.
Either layout still drags the same way: hold a tool to pick it up, drop it on the toolbar to pin it or elsewhere in the grid to reorder. The tool that follows your finger is drawn in the shape it will land in, and changes between them as you move: a full-width pill with its name over a pill toolbox, narrowing into the toolbar's circle as you carry it up to the bar, and widening again on the way back down. The grid closes up behind it as you go, and the drop keeps whatever arrangement you were looking at. The tool stays where you let go of it rather than flicking home and back.
## Most used or recently used first
The grid follows the order you drag it into until you change **Toolbox order** under Appearance / Toolbox. **Most used** puts the tools you open most often first; **Recently used** puts the one you opened last first; **A to Z** sorts them by name, in the keyboard's language. Every way of running a tool counts: a tap on the toolbar or in the toolbox, a search, a key bound to the tool, a hardware shortcut. Tools you have never opened come after the rest, in your own order, so a fresh install looks the same either way.
The grid sorts itself each time the toolbox opens and holds still while it is open. Tap Flashlight in the toolbox and it stays where it was until the next time you open it. A folder sits where its first tool falls, and the tools inside it keep the folder's own order. Dragging still works, but while the grid is sorted by use a drop rearranges your own order rather than the grid you see; **Your order** brings that arrangement back.
The search has its own **Search order before typing** on the same screen. It decides how the search lists every tool before you type anything, so set it to **Recently used** and the tool you want is often waiting at the top. Once you type, the names that start with what you typed come first, as before.
## Scrolling or pages
By default the toolbox is one grid that scrolls up and down. Turn on **Swipe the toolbox in pages** and it splits into fixed pages you swipe sideways instead, with dots underneath showing where you are. **Tools per page** sets how many each one holds (12 by default, anywhere from 4 to 40). To move a tool onto another page, drag it to the left or right edge of the toolbox and hold it there for a moment: the page turns under your finger, and you can keep going or drop it there.
The trade is muscle memory against reach. Paging keeps every tool at the same spot on the same page every time, so you learn where things are instead of hunting for them mid-scroll. Scrolling gets you to the far end faster. Paging works with both layouts.
Pick a page size that's a multiple of your row size and every page comes out with full rows. Ask for more tools than fit the panel's height and the page will scroll as well as swipe, which rather defeats the point.
## Folders
Launchers with their own shortcut list, a secondary launcher for one, offer **WM Keyboard tool** there and add any tool with its own icon. **Add to home screen** on a tool's page does the same from here.
A long toolbox is easier to scan in groups. Hold a tool, drag it over the middle of another one, and let go when that tool lights up: the two become a folder in that tool's place. The edges of a tool still mean "put it beside this one", so reordering works as before; only the middle files it. Drop more tools on the folder to add them, and drop a toolbar tool there to unpin it straight into the folder.
A new folder is called **Folder**. Tap it to open it in the toolbox, and the header shows what you can do with it:
- Tap the name to rename it. The keys come back under a shorter toolbox, the way they do for the other search boxes on the keyboard, and Enter or the check button keeps the name. Dictionary and Grammar might become "Language", for example.
- Drag the tools inside to change their order in the folder.
- Drag a tool onto the header to take it out of the folder. The header turns into a drop target while you drag.
- The pin button puts the folder on the toolbar, after your pinned tools. A tap on it there opens the toolbox at that folder. Tap the pin again to take it off. You can also drag the whole folder up onto the toolbar to pin it.
- The ungroup button breaks the folder up, and its tools go back to cells of their own.
The toolbox animates all of this. A folder opens by growing out of its cell and settles back into that spot when you leave it. A tool held over another makes that one swell to meet it, and the tool in your hand shrinks a little as if about to drop in. A new folder pops into place, and one that takes a tool gives a small bounce. **Reduce motion** under Accessibility turns every one of these off.
Hold a folder and drag it to move the whole folder around the grid. Back, the arrow in the header, or the system back gesture leaves the folder before it closes the toolbox. A folder left with a single tool is no longer a folder, and that last tool goes back to a cell of its own. Folders are saved with your settings, so a backup and restore brings them back. One set of folders is shared by every [keyboard mode](https://wmkeyboard.pages.dev/typing/modes/); a mode with its own tool order just moves each folder to where its first tool falls.
## Searching the toolbox
While the toolbox is open, the toolbar shows a magnifier at its far end, after your pinned tools. Tap it and the search takes over the whole keyboard, the toolbar included, with a back button of its own at the left of the search box. The keys come back underneath, and you type the name with the keyboard itself. Every tool that matches is listed, including the ones inside folders, with names that start with what you typed first. Tap one to use it, or press Enter to open the first match. Results fade in one after another, and slide to their new places as you keep typing. Close the search with its back button, its X button or the system back gesture, and the toolbox is the way you left it. To take the magnifier off the toolbar, turn off **Toolbox search** under Appearance / Toolbox. To have it next to the back button instead, turn off **Search button at the far end** on the same screen.
## Browsing every tool
Tools
All 91 tools have a settings screen under `Settings → Tools`, each with its own **Enabled** switch plus whatever options it carries.
**Colorful tool icons** sits at the top of that screen and is on by default. It tints each tool's icon with the tool's own accent color, in the toolbox grid and down this settings list. The pinned row on the keyboard itself is the exception: those icons take the theme's toolbar-icon color, so the bar stays one color whatever this is set to, unless you turn on **Color the pinned tools** under [Toolbar](https://wmkeyboard.pages.dev/reference/settings/appearance/). Turn the setting off and the toolbox falls back to that same plain foreground color.
Below that, the list is grouped by what the tools do: **Panels**, **Scanners**, **Online tools**, **Create and convert**, **Keyboard modes**, **Cursor**, **Quick actions**, and **Utilities**. Any tool a future group forgets to claim lands in an **Other** catch-all, so nothing goes invisible.
A row's switch enables or disables the tool everywhere at once: toolbar, toolbox, and hardware leader key. Tapping the row itself opens that tool's own settings.
To keep a tool working but out of the toolbox grid, open its page and turn off **Show in toolbox**. It still runs from the toolbar if pinned, from its keyword, from [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) and from the hardware leader key; the grid just loses one cell. Each tool has its own switch. A small tune icon marks the tools with settings beyond the switch. Most have them. Four don't, because their real options live on other screens: Settings, Sound & haptics, One-handed, and [Resize](https://wmkeyboard.pages.dev/tools/resize/).
Related pages cover most of these tools in depth: [WM Pass](https://wmkeyboard.pages.dev/tools/wm-pass/), [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/), [Snippets & text expansion](https://wmkeyboard.pages.dev/tools/snippets/), [Notes](https://wmkeyboard.pages.dev/tools/notes/), [To-do list](https://wmkeyboard.pages.dev/tools/todo/), [Text editing & cursor tools](https://wmkeyboard.pages.dev/tools/text-editing/), [Trackpad](https://wmkeyboard.pages.dev/tools/trackpad/), [Handwriting](https://wmkeyboard.pages.dev/tools/handwriting/), [Camera tool](https://wmkeyboard.pages.dev/tools/camera/), [Gallery](https://wmkeyboard.pages.dev/tools/gallery/), [Send files](https://wmkeyboard.pages.dev/tools/send/), [Voice typing](https://wmkeyboard.pages.dev/tools/voice/), [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/), [Scanner (OCR, QR, documents)](https://wmkeyboard.pages.dev/tools/scanner/), [Translate](https://wmkeyboard.pages.dev/tools/translate/), [Search & Wikipedia](https://wmkeyboard.pages.dev/tools/search/), [AI tools](https://wmkeyboard.pages.dev/tools/ai/), [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/), [Utility tools](https://wmkeyboard.pages.dev/tools/utilities/), [Internet speed test](https://wmkeyboard.pages.dev/tools/speed-test/), [Media controls](https://wmkeyboard.pages.dev/tools/media/), [Music ID](https://wmkeyboard.pages.dev/tools/music-id/), [Calendar](https://wmkeyboard.pages.dev/tools/calendar/), [App launcher](https://wmkeyboard.pages.dev/tools/app-launcher/), [Resize](https://wmkeyboard.pages.dev/tools/resize/), and [Instruments](https://wmkeyboard.pages.dev/tools/instruments/) (flashlight, compass, level, moon phase, weather). The master tool list is being built out on [Reference → Tools](https://wmkeyboard.pages.dev/reference/settings/tools/).
## Panels and captured keys
Most tools that need input open a panel that captures the keyboard: search boxes, the AI prompt, the typing-test scorer, a plugin's own text field. While that panel is focused, what you type goes into its box rather than into the app behind it. Backspace is where this matters most. Word-deleting the field behind an open panel would edit text you can't currently see, so the keyboard routes deletes into the panel for as long as it's open. Space and Enter stay in the box too: Enter runs the box's own action (search, next match, keep this spelling) rather than dropping a newline into the app behind the panel.
### A captured box is a real text field
Those boxes type like the app's own field, not like a one-way buffer:
- **A caret you can move.** Tap anywhere in the text to put the caret there, and the next letter, backspace or swipe lands at that spot rather than at the end. The spacebar scrub, a hardware keyboard's arrows, Home and End, and a layout's own arrow keys all move it, and Del deletes forward from it. A delete swipe on ⌫ works there as it does in an app's field: it highlights what letting go will delete, by letter or by word, and dragging back gives it back. Long text scrolls to follow the caret instead of trailing off behind an ellipsis.
- **Selection.** Long-press a word to select it. A handle at each end drags the selection wider or narrower, and a bar over it offers **Cut**, **Copy**, **Paste** and **Select all**. Long-press where there is no word, or anywhere in an empty box, and the caret moves there with a **Paste** bar over it (plus **Select all** once there is text); a tap anywhere else puts it away. The toolbar's **Paste** tool and a layout's Paste key paste into the box too, at the caret, for as long as it has the keys. Typing, a swipe, a suggestion or backspace replaces the selected text, as in any field, and a tap puts the caret back. Shift with a hardware keyboard's arrows selects too. The handles and the bar are drawn inside the keyboard, never as a window of their own over the app.
- **Glide typing.** Swipe a word into a search box the same way you swipe one into a message. Each swipe adds a word, spaced off the word or the mark before it (`hello,` then a swipe gives `hello, world`, while an opening bracket keeps the word against it); the last word of a query just waits for its space until the next swipe, so the results are never held back a word. A chained swipe through the spacebar lands one word per leg, exactly as it does in a field. The two numeric boxes, the calculator's expression and the converters' amount, are the exception: there is no word list to decode a stroke against on a keypad.
- **Punctuation spacing.** With **Automatic space after punctuation** on, a mark typed into a box you write sentences in (the AI prompt, chat and reply box, a note's body, the clipboard's edit box, a caption, the calendar's notes, Home Assistant's Assist) is followed by a space, as in a message. A space pressed straight afterwards doesn't double it, and a second mark takes it back, so `?!` and `.)` stay together. Search boxes get no space after a mark: it would hold the results back a word.
- **Voice.** A mic at the right end of the box's suggestion strip dictates into the box, at the caret. See [Dictating into the keyboard's own boxes](https://wmkeyboard.pages.dev/tools/voice/#dictating-into-the-keyboards-own-boxes).
- **Suggestions.** The box gets the suggestion strip in the row directly above the keys, with the same slots, the same size and the same words the field's own strip would offer. Tapping one replaces the word at the caret. The panel gives up that row rather than the keyboard growing one, so opening a search box doesn't move the keys under your thumbs. The emoji panel's search box is the one that reads differently: it's searching the emoji catalogue rather than writing prose, so its strip offers terms that catalogue can answer. See [Searching for an emoji](https://wmkeyboard.pages.dev/emoji/picker/#searching-for-an-emoji).
- **Undo.** Once you've changed the text in one of these boxes, an undo arrow appears at the right end of its suggestion strip, before the mic. Each press takes back one step: a burst of typing counts as one step, and a paste, a picked suggestion or a cut is a step of its own. The strip and its arrow show while suggestions are on. It covers the note editor, the clipboard's edit box, the AI prompt and chat, find and replace, and every search box. The arrow goes away once there's nothing left to undo, and a box that opens on new text starts with no history. After an undo, a redo arrow appears beside it and puts the step back; typing anything new drops what was undone. A swipe or key bound to **Undo** or **Redo** acts on the box the same way.
### Typing in the app with a tool open
The box that has the keys carries a small switch at its right end, the same round button on every tool with a box of its own: the clipboard's search and edit box, Combine, find and replace, notes, the dictionary, the calculator and converters, translate, the AI prompt and chat, a plugin's text field, and the rest. Tap it and the keys go to the app's field behind the keyboard instead, with the tool still open. The button fills in, and the box fades and loses its caret, so you can see at a glance which field is taking the keys. Search the clipboard, then fix the address bar under it without closing the search.
Everything a key can do follows the switch together: letters, swipes, Backspace and forward delete, Space, Enter, the arrow keys and cursor tools, **Undo** and **Redo**, **Paste**, **Copy**, **Cut** and **Select all**, and a hardware keyboard. No action splits off to the other field. The calculator and the converters fold down over the key rows while the switch is on, since their own keypads only type into them.
Tap the switch again, or tap the box itself, and the box takes the keys back with its caret where you left it. Moving to another box in the same tool, or opening another tool, starts that box with the keys. The typing test and the toolbox search have no switch: their boxes are the whole point of the screen.
### Putting the keys down
The keys that come up under a tool's box cover most of the tool: the translation under Translate's box, the clips a search found, the rest of a note. To see it all again, put the keys down. The box keeps its text and its caret, and the tool takes back the room the keys had, so the keyboard stays the same height. Any of these does it:
- The keyboard button at the right end of the box's suggestion strip, after the undo arrow and the mic.
- The system Back button or gesture, with **Back puts a tool's keys down first** on (Appearance / Toolbox, off by default). The first Back then puts the keys down and the next one closes the tool. Off, Back closes the tool at once. The AI chat's message box and a plugin's text field always put the keys away on Back.
- A swipe down on the spacebar, with [**Swipe down to hide the keyboard**](https://wmkeyboard.pages.dev/typing/gestures/) on, and the **Hide keyboard** tool or key. With a tool's keys up, these put down only the keys and leave the tool open.
Tap the box and the keys come back, with the caret where you tapped. Moving to another box, or opening another tool, brings the keys up for that box. A hardware keyboard still types into the box while the keys are down. The typing test and the toolbox's own boxes have no way to put the keys down: their keys are the point of the screen, and Back closes them.
On a short screen, a phone turned sideways most of all, the panel is what gives way. A captured box with the keys back underneath, or a tool that grows above the keyboard for more room (the AI reply panel, an AI chat, or any tool you have dragged taller), keeps the keyboard to at most 80% of the screen's height, and the panel shrinks to fit rather than squashing the keys. If the screen still can't hold both, the panel is measured against what's left once the keys are paid for, so the keys under a captured box always keep their full height. It never shrinks below its own header, so the box and the back arrow always stay in reach.
### Making a tool taller
A tool that takes the keyboard's place (AI, AI reply, translate, the converters, the dictionary and the rest) has the same short bar across its top edge. The calculator, both converters and the calendar all open at the keyboard's own height, so switching between them doesn't move the app above; their keypads and the month grid shrink to fit instead. Drag it up for a taller tool, down to go back to the height it opens at. Where you let go is kept, and it's one height for every such tool. The clipboard and the GIF and sticker panels keep their own bars and heights, described on their pages. With no room to grow, the bar isn't drawn.
### Making a panel over the keys taller
A panel that shrinks so the keys fit under it has a short bar across its top edge: the clipboard while it's [stacked over the keys](https://wmkeyboard.pages.dev/tools/clipboard/#clipboard-and-keys-together) or searched, a [snippet search](https://wmkeyboard.pages.dev/tools/snippets/#opening-the-panel), the dictionary's, the GIF and sticker searches, the web search, a note being written and the rest. Drag the bar up and the panel grows over the app, so more results fit above the keys; the keys stay where they are. Drag it down to go back to the panel's own compact height. Where you let go is kept, and it's one height for every such panel, so the keys sit at the same place whichever one is up. The same 80% ceiling applies, and with no room to grow the bar isn't drawn. The height is also a slider, **Height over the keys**, under Clipboard. With TalkBack on, the bar offers **Make the panel taller** and **Make the panel shorter** as actions.
Nothing typed into a captured box is learned. A search query isn't writing, so it never reaches your personal dictionary, your word pairs or your typing stats.
Sensor and generator tools work differently. Compass, Level, Moon phase, Weather, Calendar, Password generator, Media controls, Camera, and the scanners all replace the key rows outright instead of intercepting keystrokes. There's no letter row to type on while they're open, so there's nothing to intercept.
Calculator, Unit converter and Currency converter fall between the two. Each draws its own keypad where the key rows were, and each also takes a physical keyboard's keystrokes into its own display: digits and operators build the expression or the amount, backspace trims it, and Enter runs `=` in the calculator instead of reaching the field behind. Handwriting is the other in-between case. While ink is waiting to be recognized, backspace throws that ink away rather than deleting text you already committed.
To back out of any open panel:
- Tap the back arrow that replaces the toolbox button while a panel is open.
- Press the system **Back** button or gesture, which closes the panel instead of dismissing the keyboard.
- On a hardware keyboard, press Esc, if [Esc closes the tool](#hardware-shortcuts-and-the-leader-key) is on (it is by default).
## Which tools work before you unlock your phone
**Direct boot** is the window right after a restart, before you've entered your PIN or pattern for the first time. The keyboard can't reach its stored data yet. Some tools work anyway.
WM Keyboard exposes 50 of its 90 tools during that window: everything that's pure arithmetic, a sensor reading, or a control over the keyboard's own on-screen state. That's Emoji, Text editing, Trackpad, Numpad, Symbols, One-handed, Split, Floating, Keep on screen, Resize, Hide keyboard, Appearance, Autocorrect, Sound & haptics, Incognito, Selection actions, English words, Find & replace, Text repeat, Modes, Fancy text, Secondary layout, Language, Undo, Redo, Power saving, Calculator, Unit converter, Password generator, QR generator, Flashlight, Compass, Level, Moon phase, Copy, Cut, and all fourteen cursor tools. Paste is the one clipboard button left out: the clipboard itself is credential-encrypted, so before the first unlock the button would be there and do nothing.
The other 40 sit it out until you unlock. That covers anything backed by a file on disk (Clipboard, Snippets, Notes, To-do list, Camera, Read back, downloaded speech or AI models), anything that needs a credential or the network (Translate, the online searches, AI, the paired computers of KDE Connect, the signed-in homes of the Smart home tool, Send files), anything that reads a content provider behind the lock screen (Calendar), and anything that has to launch a separate activity (Settings, Statistics, the document scanner). None of it is a toggle. The split is automatic, and it only applies before that first unlock.
**Hide toolbar & clipboard on lock screen** under Appearance / Toolbar is a different thing, and it's off by default. It hides the *entire* toolbar rather than filtering it, any time the screen is locked, including long after you've unlocked the phone once since the last restart. Turn it on if you'd rather not have a pinned tool or the suggestion strip visible while you reply to a notification from the lock screen. Everything comes back the moment you unlock, even in the text box the keyboard was open in when the screen turned off, and even when a fingerprint unlocks the phone straight from a dark screen.
## Hardware shortcuts and the leader key
Typing / Physical keyboard
With a physical keyboard attached, double-tap Ctrl, the default leader. Then press a letter to jump straight to a tool without touching the screen. About two dozen tools have a default letter: Emoji is E, Clipboard is C, Voice is V. Press T after the leader to open the full toolbox instead, or ? to expand the on-screen hint into a two-column legend of every binding. [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) has the full mechanics, plus how to rebind the leader and every letter.
The leader is one switch in a broader hardware-keyboard group. Arrow-key navigation inside an open panel and whether Esc closes a panel are separate switches in the same place, each on by default.
## Options
Appearance / Toolbar
- **Show the toolbar**: on by default. Turning it off reclaims its height for the keys, but also hides suggestions and every pinned tool, so Settings warns you before you do it.
- **Swipe down to hide**: off by default. A downward flick anywhere on the toolbar dismisses the keyboard.
- **Only toolbar with hardware keyboard**: off by default. With a physical keyboard attached, drop the on-screen keys and keep just the toolbar strip.
- **Reverse order for RTL languages**: off by default. Mirrors the pinned tool order when the active layout's script runs right-to-left.
- **Tool layout**: Spread out by default. Fixed size packs the pinned tools to the left at one size, Spread out splits the width evenly, and Scroll keeps each tool at a comfortable size and lets the bar scroll sideways.
- **Hide toolbar & clipboard on lock screen**: off by default. See [above](#which-tools-work-before-you-unlock-your-phone).
- **Toolbar top padding** and **Toolbar bottom padding**: 4dp and 0dp by default, each adjustable from 0–24dp. They add space above and below the tools, and the keyboard grows by the same amount.
- **Tool labels**: off by default. Shows each tool's name under its icon. The label is 10sp by default.
- **Reset pinned tools**: restores the toolbar to Emoji, Clipboard, and Settings, after a confirmation. This always resets the *global* row, even while a mode with its own pinned toolbar is active. To change what a mode itself pins, edit that mode instead. On a tablet, reset writes the shipped three and the tablet's own default immediately re-expands it, so you get five or seven back rather than three. It touches the pinned row alone; the grid has its own row below.
- **Reset toolbox order**: puts the toolbox grid back in the order it shipped in, after a confirmation. Only shown once you've dragged something. See [below](#details--edge-cases).
- **Tool circle radius**: 20dp by default (a full circle behind each icon, Gboard-style). Lower values give a rounded square, and 0 removes the background entirely.
- **Tool background shape**: Rounded by default. The outline of that background, from the same set of shapes the keys and popups use. Only shown while the radius above is over 0, since at 0 there's no background to shape.
- **Tool background width**: 38dp by default, adjustable from 38–64dp. 38 is the classic circle. Wider stretches it into a pill, which pairs well with a low corner radius and a scrolling bar.
- **Toolbox layout**: Icons by default. Pills is the wide-row alternative, see [above](#two-ways-to-draw-the-toolbox).
- **Toolbox grid size**: 4 tools per row by default, adjustable from 3–6. Icons layout only.
- **Pills per row**: 2 by default, adjustable from 1–3. Pills layout only.
- **Fill pills with the tool color**: off by default. Pills layout only, and needs Colorful tool icons on.
- **Toolbox order**: Your order by default. A to Z sorts by name. Most used and Recently used rank the grid by use; see [above](#most-used-or-recently-used-first).
- **Search order before typing**: Your order by default. The same three choices, for the search's list before you type.
- **Swipe the toolbox in pages**: off by default. See [above](#scrolling-or-pages).
- **Tools per page**: 12 by default, adjustable from 4–40. Only shown once paging is on.
## Details & edge cases
- A tool that's disabled under Tools disappears from the toolbar, the toolbox, *and* its hardware leader-key letter all at once, since there's a single switch, not three.
- Every tool is in the F-Droid build, though three read with open-source stand-ins for the Google libraries it leaves out. QR scan reads with zxing-cpp. Text scan reads with PaddleOCR on MNN, which covers Chinese, Japanese and Latin script. Handwriting reads Chinese and Japanese with zinnia and nine other scripts with PaddleOCR's text recognisers on MNN; languages in other scripts, Bangla among them, have none. Grammar needs Harper's, which F-Droid compiles from source, so it stays. A build-gated tool gets no settings row at all, so you never meet a switch that won't turn on.
- **Document scanner** is in every build and on every phone. It opens Google's scanner, an activity inside Google Play services, where that can run, and the keyboard's own [built-in scanner](https://wmkeyboard.pages.dev/tools/scanner/#scanning-a-document) everywhere else, F-Droid's build included.
- **Reset toolbox order** puts the grid back in the order it shipped in, after a confirmation. It sits under Appearance / Toolbar right below Reset pinned tools, and only appears once you have actually dragged something. It leaves the pinned row and which tools are enabled alone. **Reset pinned tools** is the mirror of it.
- Toolbar reordering is per-installation, not per-language. Pinning a tool applies everywhere, not only while a particular language is active. A keyboard mode's own toolbar override is the one exception. See [above](#pinning-and-reordering).
- The toolbox shows only tools that are both enabled *and* not already pinned, so enabling a tool that's already pinned won't create a second entry in the toolbox.
- A tool pinned to the toolbar stays a member of its folder but isn't shown inside it. Unpin it and it is back in the folder. A folder whose tools are all pinned or hidden disappears from the grid until one of them comes back.
- The search lists results only. While it is open you can tap a result or hold it for its settings, but nothing can be dragged in a filtered grid.
- With paging on, a drag reorders within the page you're holding it over. The pages don't flip under your finger, so moving a tool to another page means dragging it onto the toolbar first, then off again on the page you want. Dragging up onto the toolbar to pin works from any page.
- The pill layout's arrow follows what the tool actually does, not what kind of tool it is. **Document scanner** gets one because it opens a full-screen scanner, and **Settings** gets one because it opens the settings app, even though neither is a keyboard panel.
- Tool icon colors apply in the toolbox and the settings list, and on the toolbar too with **Color the pinned tools** on, and only when **Colorful tool icons** is on. With it off, those icons use the theme's plain foreground color, whatever per-tool override you've set.
---
# AI tools
URL: https://wmkeyboard.pages.dev/tools/ai/
Section: Tools
Summary: A local LLM running on your device, or bring your own API key: your choice.
The AI tool runs a fixed set of one-tap writing actions against whatever text is in the field you're typing in: rewrite, summarize, translate, and more. Where they run is up to you. Pick a cloud provider with your own API key, a self-hosted Ollama or LM Studio server on your network, or a model that downloads once and answers entirely on the device.
tools/ai-panel-idle — The AI panel's action chips, ready to run against the text in the field.
## Using it
Open **AI** from the [toolbar or the toolbox](https://wmkeyboard.pages.dev/tools/overview/). It isn't pinned by default, so the first time you'll probably reach it through the toolbox. Typing its `ai` keyword on the suggestion strip works too. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press A.
In the settings app, the **AI** row on the Home screen collects everything AI in one place: this tool and [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/), each with its switch, then the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/), the settings assistant and the history. Press **AI writing tools** there to reach the provider, key and model.
The panel opens on a row of action chips. Eleven ship with the keyboard, and the list is yours to change: rename them, rewrite their prompts, turn off the ones you never use, reorder them, or add your own. See [Your own actions](#your-own-actions) below.
- **Rewrite**, **Summarize**, **Translate**, **Improve**, **Formal**, **Shorter**, **Friendly**, **Fix grammar**, **Explain**, **Continue**: each needs text to work on and is greyed out on an empty field.
- **Custom**: the one chip that stays live on an empty field. Tapping it opens an instruction box on the keyboard itself. Whatever you type there is the prompt for that run, so Custom can write brand-new text from nothing instead of transforming what's already in the field.
In a password field the chips that read the field are greyed out, and no action runs at all, Custom included. The text in a password box never goes to a provider, the same rule [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) follows when it reads a field.
If nothing is configured yet, the panel shows a short explanation in place of the chip row. You get an **Open settings** shortcut when no provider is set up, which is where a fresh install starts, or a **Download a model** shortcut when On-device is selected and nothing has come down yet. A model that's downloading right now takes over that empty state with its progress. Once you're past the empty state, a one-line progress strip sits above the chips, so a download you started in Settings stays visible while you carry on typing.
tools/ai-need-setup — The AI panel's empty state, before any provider or local model is set up.
**What gets sent:** selected text wins if you've selected any, and otherwise the whole field goes. **Continue** is the exception. It always reads back from the cursor, since continuing needs to know what came before rather than what comes after, and **How much the AI reads back** sets how far: 500 to 32,000 characters, default 4,000. That limit only applies with nothing selected. A selection is always sent as it is. Tap a chip on a truly empty field with no selection and you get "There is nothing to work on. Type some text first." instead of a request.
While a request runs, the panel names the step it's on instead of showing a bare spinner: Preparing, Connecting, Waiting for the model, and Reasoning for a model that thinks before it answers. A request can spend most of its time waiting on a slow connection or a long field, so the step is worth seeing. **Waiting for the model** is the one you'll meet most. It starts the moment your text is on its way and lasts until the first words come back, which for a cloud provider is nearly the whole run.
Once a result streams in, two buttons commit it. **Replace** swaps the field's text for the result. **Insert** drops it in at the cursor instead. Continue is the one exception to Replace: deleting the text you just asked it to continue would defeat the point, so Replace appends there. A **Delete markdown** checkbox, shown only when the result contains any and checked to start with, removes headings, bullets, quotes, code fences, inline code, links, and bold/italic syntax from what gets committed, and writes LaTeX maths out in Unicode, so `$x^2$` lands as `x²`. Uncheck it to keep the raw markdown. Reasoning output between `` tags is filtered out of both Replace and Insert either way, so only the answer itself lands in your field.
If a model stops mid-answer because it hit the length ceiling, the panel says so and points at the setting to raise. That's worth knowing about: a truncated answer otherwise looks exactly like a finished one, and you'd only find out by reading your own half-rewritten text.
### Seeing what changed
tools/ai-result-changes — A Fix grammar result in the Changes view, with the old spellings struck through.
A finished result gets a **Result** / **Changes** pair of chips. Changes compares the answer against the text the action ran on and marks what the model added and what it took out, the way `git diff` does. It's the one thing the plain result can't show you: after a Fix grammar run, the spelling it corrected is gone from the screen.
Deleted text is struck through, added text is underlined and slightly bolder. Shape rather than colour alone is deliberate, so the view stays readable on a photo-background theme, in a high-contrast theme, and if you're colour-blind. A changed space or paragraph break is drawn with a visible mark, since an empty highlight tells you nothing. Under the text sits a count of how many parts changed. "Parts" because they're words in prose and single characters in a language that doesn't put spaces between words.
Two cases can't be compared, so they don't offer the chips: **Continue**, which only ever read the text before your cursor, and any run that wrote from an empty field, where there's no original to compare against. Very long pairs of texts aren't compared either. The panel shows the plain result and says why. The comparison also waits until the answer has finished streaming, since half an answer reads as "everything after this was deleted".
The chips are on by default. **Open a result on the changes** (off by default) makes the panel start there instead.
tools/ai-result-ready — A streamed result with Replace, Insert, and the strip-markdown checkbox.
### Reporting a result
Every finished result also carries a **Report** button, at the bottom right of the panel next to the Delete markdown checkbox. Tap it if a model produced something offensive, dangerous, or plainly wrong, and it opens a pre-filled email to the developer with a blank line at the top for what went wrong.
Nothing is uploaded when you tap it. The report is a draft in your own mail app, and you can read every line, edit it, or throw it away before sending. What it fills in for you:
- Your complaint, once you type it in the space at the top.
- Which action ran, which provider and model answered, and (for Custom) the instruction you gave.
- The app version, Android version, and device model.
- The text that went in and the text that came out, each trimmed to the first 4,000 characters.
That last one is worth a second look before you hit send, since it quotes whatever was in the field. The button only appears once a result has finished streaming. Half an answer isn't the thing you'd be reporting anyway.
For bugs in the keyboard itself rather than something a model wrote, use the **Feedback** rows in [About](https://wmkeyboard.pages.dev/reference/settings/about/) instead.
## Choosing where it runs
The services the AI can use are an **AI providers** list at the top of the tool's settings, the same kind of [provider list](https://wmkeyboard.pages.dev/tools/providers/) the translate and search tools keep. **Add a provider** offers Anthropic (Claude), OpenAI, Google Gemini, xAI (Grok), DeepSeek, Brave Answers, Privatemode AI, Ollama, LM Studio, an OpenAI-compatible service, and On your device. Each row holds its own key, address and model, set up in the sheet that opens when you tap it, with a **Test** button that sends the service one word and shows whether it answered.
One row answers at a time. Pick it under **In use**, just below the list, or with the [model picker](#options) on the AI panel; the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) and the settings assistant can each send to another row without changing it. A service can be on the list more than once: a second Anthropic key for work, or two Ollama servers, one at home and one at the office. Give each a nickname, and the picker shows it.
Switching a row off takes it out of the picker. With nothing on the list, or every row off, the panel says the AI isn't set up yet, even if keys from before are still stored.
tools/ai-settings-providers — The AI providers list, with a service's key and model in its setup sheet.
### Cloud providers
Claude (Anthropic), OpenAI, Gemini, Grok, and DeepSeek are bring-your-own-key only. Unlike some of the other network tools, WM Keyboard ships no built-in or proxied key for any of them. Add the service and paste your own key into its setup:
- **Claude**: from `console.anthropic.com` → API keys
- **OpenAI**: from `platform.openai.com` → API keys
- **Gemini**: has a free tier at `aistudio.google.com`
- **Grok**: from `console.x.ai` → API keys
- **DeepSeek**: from `platform.deepseek.com` → API keys
Each service's setup also has a **Model** field. Leave it blank and the app falls back to a built-in default, shown right in the field's hint: `claude-sonnet-5-5` for Claude, `gpt-6-luna` for OpenAI, `gemini-3.8-flash` for Gemini, `grok-4.7` for Grok, `deepseek-flash` for DeepSeek. Type a model name here to use something else.
DeepSeek's reasoning models stream their thinking on a separate field. The app folds that into the same `` handling everything else uses, so **Show model reasoning** and the reasoning progress readout behave there exactly as they do elsewhere.
### Brave
**Brave** uses Brave's Answers API, which runs a web search before it writes each answer. That makes it the provider to pick for questions about facts, prices or recent events, and a poor fit for pure writing jobs like Proofread, where the search adds cost and nothing else.
- **API key**: from `api-dashboard.search.brave.com`. The key needs Brave's **Answers** plan. A key that only has the **Search** plan is refused, and the panel shows Brave's own reason.
- **Leave the key blank to reuse the web search key.** If you already gave the [web search tool](https://wmkeyboard.pages.dev/tools/search/) your own Brave key, the AI tool uses that one. The key built into some builds is never used for answers, only for searches. With neither key, the row says it needs one.
- **No Model field.** Brave has one model, so there's nothing to pick.
- **One message per request.** Brave doesn't accept a system prompt or earlier turns, so the app sends the action's instructions, and in a [chat](#chatting-instead-of-transforming) the earlier messages as a transcript, together with your text as one message.
- Brave bills each answer for the search it ran plus the tokens in and out. The usage and citation markers it writes into the stream are removed before you see the answer.
### Privatemode
**Privatemode AI** runs its models inside confidential computing, and its requests are encrypted end to end, so the service operator can't read them. Get a key at `privatemode.ai`, which has a free tier. The row has three fields:
- **API key**: your Privatemode key.
- **Server address**: leave it blank for the keyboard to encrypt each request itself, or give the address of your own `privatemode-proxy`, for example `http://192.168.1.20:8080`. With a proxy the key is optional, since the proxy can hold it.
- **Model**: blank means `openai/gpt-oss-120b`. **List models** fetches the others your key may use.
With no proxy address, the keyboard does what Privatemode's proxy does before a request leaves your phone. It fetches the deployment's published manifest and asks the service to attest itself, and the attested deployment has to run exactly that manifest. It then agrees a secret with Privatemode's secret service, using a fresh post-quantum key pair (ML-KEM-768 with X25519), and checks that the answer is signed by a certificate of the deployment's mesh CA. Each request is encrypted with that secret, and the answer is decrypted as it streams in. An answer that comes back unencrypted, cut off or altered is refused. The secret is renewed every 45 minutes.
The proxy also verifies the hardware report itself, against AMD's and Intel's certificates, and compares the deployment's measurements with the manifest. The keyboard doesn't. So the encryption keeps your text from everything between your phone and the service that holds the attested mesh CA, but if Privatemode's API lied about its attestation, the keyboard wouldn't catch it. That's why the row says **Encrypted on this phone, attestation not checked**, and why nothing on the phone ever says "verified". For the full check, run `privatemode-proxy` on a computer you trust and give its address.
Through a proxy, the row says **Through your Privatemode proxy**, and the request travels from your phone to the proxy as an ordinary OpenAI-style call. Use `https://`, or keep the proxy on a network you trust. In the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/), a lock in the top bar shows while Privatemode answers. Tap it to see which of the two it is.
Pictures aren't sent to Privatemode, and extra headers aren't added to its encrypted requests, because everything it reads outside the encryption is fixed by its protocol.
### Any other OpenAI-compatible service
**OpenAI-compatible service** is the escape hatch, and it reaches far more than the named services do. Anything that speaks OpenAI's chat-completions shape works: OpenRouter, Groq, Together, Mistral, Cerebras, self-hosted vLLM. Add one row for each service you use. Three fields:
- **Server address**: the address up to and including the version segment, for example `https://openrouter.ai/api/v1`. The app adds `/chat/completions` itself. That split exists because services disagree about the prefix (`/v1`, `/openai/v1`, `/api/v1`), so guessing it would break half of them.
- **Model**: required here, unlike the named providers. There's no sensible default to guess for a service the app knows nothing about.
- **API key**: optional. A gateway on your own network often wants none.
An `http://` address works, for a service on your own network, and that traffic is plain HTTP. Keep the server on a network you trust.
### Usage and balance
Each AI service's own screen has a **Usage** group: the requests and tokens this phone sent this month, any per-minute allowance the service reports, and for DeepSeek and OpenRouter a **Check what is left** button that asks for your balance. See [Usage and credit](https://wmkeyboard.pages.dev/tools/providers/#usage-and-credit).
### Extra headers
Some services want more in a request than a key. OpenCode Go, for one, reads a session header and a User-Agent of its own, and a gateway in front of a provider may want its own token. An **Extra headers** group on each service's own screen holds them: press the service's row on the list. The model on your device has none. Tap **Add header**, then fill in a name and a value. Each edit saves as you make it, and the value stays hidden until you tap the eye.
- **Yours are sent last.** A header here replaces the app's own of the same name, whatever its capitals, so a `User-Agent` or `Authorization` you add is the one the service sees.
- **`{session}` stands in for a session id.** Type it anywhere in a value, for example the name `x-opencode-session` with the value `{session}`. One panel action, its lookups included, sends one id, and a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) sends the same id for every message in that conversation.
- **Each kind of service has its own list.** Switching to another kind shows that kind's headers, not the last one's. Two rows of one kind share theirs.
- A name with a space or other character a header can't hold, or a value with a line break, is left out of the request rather than failing it.
- A backup without secrets leaves the headers out, the same as API keys.
### Self-hosted servers
**Ollama** and **LM Studio** point at a server on your own network instead of a cloud API. No key is needed, only an address like `http://192.168.0.10:11434` (Ollama) or `http://192.168.0.10:1234` (LM Studio). Set `OLLAMA_HOST=0.0.0.0` so Ollama listens beyond localhost. Turn on LM Studio's local-server option. Add one row for each server you run. Ollama defaults its model field to `qwen3` when you leave it blank. LM Studio has no default model to fall back to, so it uses whatever the server currently has loaded.
Traffic to a self-hosted server is plain HTTP, and the app allows cleartext traffic app-wide to support it. Keep the server on a network you trust.
### Local models
google
The **On your device** provider runs a model entirely on the phone, no network involved. It's the only provider that needs a download first, and the one that **Add a provider** doesn't offer at all in the F-Droid build.
tools/ai-settings-local-catalog — The on-device model catalog, with tier badges and a size/RAM subtitle per model.
The catalog has two lists. The first is eight models in LiteRT-LM's format, the engine the keyboard has always used and still the default. They're ordered best-first rather than smallest-first:
| Model | Params | Size | Tier | Gated |
|---|---|---|---|---|
| Gemma 4 E2B | 2B | 2.59 GB | Recommended | |
| Gemma 4 E4B | 4B | 3.66 GB | Recommended | |
| Gemma 3 1B | 1B | 584 MB | Untested | Yes |
| Qwen 2.5 1.5B | 1.5B | 1.6 GB | Standard | |
| Qwen 3 0.6B | 0.6B | 347 MB | Experimental | |
| Qwen 2.5 0.5B | 0.5B | 546 MB | Experimental | |
| Gemma 3 270M | 270M | 304 MB | Untested | Yes |
| SmolLM2 135M | 135M | 143 MB | Experimental | |
Each row names the licence its weights are published under, and **Licence and model page** under an undownloaded row opens the repository where the text is. Gemma 3 comes under Google's Gemma Terms of Use; Gemma 4, Qwen and SmolLM2 are Apache-2.0. About, Licences lists the same under downloadable data packs.
Two of the eight are gated and need a Hugging Face account. Paste a token into the **Access token** field under **Hugging Face account**, above the catalog. Then accept the model's license on its Hugging Face page, once per model.
#### GGUF models on llama.cpp
Below them sits a second list, **GGUF models (llama.cpp)**. These run on [llama.cpp](https://github.com/ggml-org/llama.cpp), a second engine that loads any GGUF file, the format most open models are published in. Each row says **Runs on llama.cpp**, and its name ends in "(GGUF)", which keeps it apart from a LiteRT-LM model of the same name in the panel's and the chat's model pickers. Picking one of these models is how you pick the engine: the keyboard routes every request to whichever engine runs the model in use. On the F-Droid build this is the only list: it has llama.cpp but not LiteRT-LM.
| Model | Params | Size | Tier | Licence |
|---|---|---|---|---|
| Qwen 2.5 VL 3B | 3B | 2.77 GB | Untested | Qwen Research License |
| Qwen 3 1.7B | 1.7B | 1.83 GB | Untested | Apache-2.0 |
| Llama 3.2 1B | 1B | 808 MB | Untested | Llama 3.2 Community License |
| Gemma 3 1B | 1B | 806 MB | Untested | Gemma Terms of Use |
| Qwen 3 0.6B | 0.6B | 639 MB | Experimental | Apache-2.0 |
| Qwen 2.5 0.5B | 0.5B | 491 MB | Experimental | Apache-2.0 |
| SmolVLM 500M | 500M | 546 MB | Experimental | Apache-2.0 |
| SmolLM2 360M | 360M | 386 MB | Experimental | Apache-2.0 |
| Gemma 3 270M | 270M | 292 MB | Experimental | Gemma Terms of Use |
None of them is gated, so no Hugging Face token is needed, Gemma 3 included. Each one is fetched from a fixed commit of its repository and checked against a SHA-256 before it counts as downloaded. A file that doesn't match is deleted and the row asks you to download it again. The Qwen 3, Qwen 2.5 and SmolLM2 files come from the makers' own repositories, Gemma, Qwen 2.5 VL and SmolVLM from ggml-org (the llama.cpp project), and Llama 3.2 from bartowski's conversion of Meta's model.
Qwen 2.5 VL 3B and SmolVLM 500M read pictures. Each brings a second file, its projector (the `mmproj` file), which the download fetches first; the sizes above count both. Qwen 2.5 VL reads the text in a chat screenshot well, but needs a phone with 8 GB of RAM, and its licence allows research use only. SmolVLM is small enough for most phones, and tells you what a picture shows, but often gets the words in a screenshot wrong.
What's different about them:
- **GPU.** With **Compute** on **GPU**, a GGUF model runs on the phone's GPU: on a Snapdragon chip (an Adreno GPU) through llama.cpp's OpenCL code, which Qualcomm wrote for these GPUs, and on a Mali, Xclipse or PowerVR GPU through llama.cpp's Vulkan code. A phone with neither runs it on the CPU's big cores, whatever that row says. See [Compute](#compute-and-gguf-models) below.
- **Pictures, for the models that read them.** Qwen 2.5 VL and SmolVLM look at a picture themselves: a screenshot in the [reply tool](https://wmkeyboard.pages.dev/tools/ai-reply/), and the pictures in the newest message of a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#sending-pictures). For every other GGUF model, the reply tool reads a screenshot another way, as it does for any model that can't see.
- **The model's own chat template.** llama.cpp formats each conversation with the template stored in the GGUF file, so a model you import is prompted the way its makers trained it. Text from your field is kept as text: it can't open a turn of its own, even if it contains the template's markers.
- **Stop really stops.** A LiteRT-LM answer runs to its end in the background after you press Stop. A GGUF answer ends at the next word, and what it had written is what the model remembers.
- **Memory is checked first.** Before a model loads, the keyboard compares what it needs (the file, plus room for the context) with the memory free on the phone. If it's short, the context shrinks first. If even that won't fit, you get a message with both figures instead of a crash. A loaded model is unloaded after three minutes with nothing to do, and the keyboard lets go of it earlier when it hides.
llama.cpp itself is an extra part of a few megabytes. The Google Play edition downloads it from Play with the first GGUF model, and the GitHub edition fetches it from the project's own release. Until it's there, the GGUF list shows a banner with a **Download** button. Google Play sometimes wants your OK before it downloads a part like this, for example on mobile data. It can only ask inside the app, not over the keyboard, so the chat says so, and the question appears the next time you open WM Keyboard's settings.
Every model carries an advisory minimum-RAM figure behind the scenes. When your phone's total RAM falls under it, the app appends "May be too large for this device" to that model's subtitle. It never blocks the download and never shows you the raw number. That warning is the only per-device signal the catalog gives you up front. There's no published speed benchmark, because how fast a model answers depends on the phone as much as on the model. What you get instead is a progress readout with elapsed seconds, once a request is actually running.
Tapping **Download** starts a resumable download. Cancel it, lose signal, or background the app, and the next attempt picks the transfer up where it left off rather than restarting. Only one download runs at a time. On a metered connection, a model of 500 MB or more asks for confirmation first.
Don't need the catalog? **Import your own** takes any `.litertlm` or `.task` file, which LiteRT-LM runs, and any GGUF file, which llama.cpp runs. That covers one you converted yourself or downloaded elsewhere in a browser. A GGUF is recognised by its contents, so one that lost its `.gguf` extension on the way still imports and gets the extension back. Anything else is rejected. The same goes for **Import files** under Storage, which also takes a catalog GGUF and checks it against its SHA-256 like a download. An imported GGUF uses the chat template inside it; one with no template is prompted in ChatML, the format most small open models use.
A GGUF vision model you import reads pictures when its projector is imported with it: select the model and its `mmproj` file together, or import one after the other. The projector is not a model, so it gets no row of its own. It pairs with the imported model whose file name it shares, or, when there is only one imported GGUF, with that one. Deleting the model deletes its projector too.
A **Compute** row picks **CPU** or **GPU** for inference, and CPU is the default. GPU is best-effort. If it fails to initialize, runs out of GPU memory partway through an answer, or takes the keyboard down with it, the engine falls back to CPU on its own and keeps that model on the CPU from then on. It tries the GPU again after a system update, an app update or a fresh download of the model. Some GPU drivers keep a failed model's memory after the model is closed, which can leave the whole phone slow. When that happens, the keyboard restarts itself a few seconds after you close it, so the memory is freed. It doesn't do this while a screen of the app is open or a model is downloading.
#### Compute and GGUF models
**GPU** applies to a GGUF model on a phone with an Adreno GPU, the GPU in Snapdragon chips, or with any other GPU whose driver speaks Vulkan 1.2 (Mali, PowerVR, Samsung's Xclipse). On an Adreno it runs llama.cpp's OpenCL code, which Qualcomm tuned for the Snapdragon 8 Gen 1 and newer; older Adreno GPUs that support OpenCL 2.0 run it too, with less gain. On the other GPUs it runs llama.cpp's Vulkan code. How that compares with the CPU depends on the GPU and its driver: public measurements range from much faster on recent Mali and Xclipse GPUs to slower than the CPU on older Mali ones, so try both on your phone. **NPU** runs a GGUF model on the NPU of a Snapdragon 8 Gen 3, 8 Elite or 8 Elite Gen 5 through llama.cpp's Hexagon code, a download of its own of about 6 MB (11 MB on the phone), in place of the GPU build. The NPU works best with models quantized as Q4_0 or Q8_0; the parts of a model it cannot run go to the CPU. It is new in llama.cpp and has not been measured on a phone here, so compare it with **GPU** on yours. On any other phone **NPU** means **GPU** for a GGUF model. Each GPU build of llama.cpp is a download of its own, fetched only on a phone it runs on: the OpenCL build, about 10 MB, on a Snapdragon, and the Vulkan build, about 13 MB to download and 42 MB on the phone because it carries its compiled shaders, on the others. It comes when you pick **GPU** with a GGUF model on the phone, or the first time a GGUF model answers with **GPU** picked. Until it's there, GGUF models run on the CPU. It's loaded the first time a GGUF model runs after the keyboard starts, so if one already ran on the CPU, the GPU takes over after the keyboard restarts, which it does by itself a few seconds after you close it, as above. Failures are handled the same way as for LiteRT-LM models: a model that fails on the GPU moves to the CPU and stays there until the next system or app update, and a GPU driver that takes the keyboard down while starting is not used again until then.
The GPU makes the biggest difference to reading a long prompt, such as a chat screenshot or a long text to rewrite. Writing the answer is limited by memory speed, so it gains less, and for the smallest models the CPU can be as fast. Qualcomm tuned models quantized as Q4_0 first. The catalog's Q4_K_M and Q8_0 files also run on the GPU, and with some Snapdragon 8 Gen 2 and Gen 3 drivers llama.cpp keeps the last layer of a K-quant model on the CPU, where those drivers compile it badly.
On a phone with a Snapdragon 8 Elite (SM8750, the Galaxy S25 series) or 8 Elite Gen 5 (SM8850, the Galaxy S26 Ultra), running Android 12 or later, the row has a third choice, **NPU**, which runs a model on the phone's AI accelerator. The NPU needs a build of the model compiled for that chip, and only Gemma 3 1B and Gemma 3 270M have one. With **NPU** selected, **Download** on those two rows fetches the NPU build (689 MB and 461 MB on an SM8750) instead of the standard file. A model you already have gets a **Get the NPU build** button. Every other model runs as it does with **GPU**. So does a model whose NPU build fails to start, and the app keeps that model off the NPU until the next system or app update. A failure partway through an answer only moves the next try in that session to the GPU, because a prompt longer than the build's window fails the same way. The NPU build runs only on the NPU, so a model whose only file is its NPU build offers **Get the standard build** when Compute is CPU or GPU. The NPU runtime is Qualcomm's QNN. The Google Play edition downloads it from Play the first time you pick **NPU**. The GitHub edition first asks you to accept Qualcomm's licence, then fetches only your chip's files from Qualcomm's own package on Maven Central, about 30 MB. The F-Droid edition has no NPU at all.
A **Context size** row sets how much text the model is loaded with, prompt and answer together, in tokens. It defaults to the model's own value, or 4,096 tokens for a GGUF model, whose own value is often far more than a phone can hold. This is the only length lever on-device. Unlike a cloud API there's no per-response ceiling to set, so the window is what bounds an answer. A larger window costs memory, and changing it reloads the model.
## Your own actions
Tools / AI writing tools / Actions on the panel
The eleven chips on the panel are a list you own. The screen lists them in panel order, with a checkbox to turn one off, an edit button, and a **New action** row at the bottom. Drag the handles in **Order of the actions** to rearrange them. The panel follows that order exactly.
Any of these can also be a button on the [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/#language) bar: pick them under Advanced, Selection actions, AI buttons, and one tap runs the action on whatever is selected, with the result landing here ready to apply. Formal, Shorter and Friendly were added with that in mind.
An action that ships with the keyboard is never deleted, only changed. Editing one saves your version under the same identity, so **Reset** in its editor drops your version and the original comes straight back. Turning one off leaves it on this screen, greyed on the panel and one checkbox away from returning. Only actions you wrote yourself get a **Delete**.
The editor holds a name, the prompt, and a handful of behaviour switches:
- **Type the prompt at every run**: the keyboard asks you for the prompt instead of using a saved one. This is what makes **Custom** work the way it does.
- **Start from the saved prompt**: only shown under the switch above, and it brings the prompt field back. The instruction box then opens with that text already in it, so you adjust a template rather than retype it. Useful for something you run often with a small variation each time: "reply to this politely, and mention that…". The saved text wins every time the box opens, not only the first, since a template you see once isn't a template. Leave it off and the box reopens on whatever you typed last.
- **Works with an empty field**: with nothing in the field, the action writes new text instead of changing text.
- **Read the text before the cursor**: the action reads only what's in front of the cursor rather than the whole field. A selection always wins over this.
- **Add the result to the end**: Replace appends rather than overwriting. Pair it with the switch above and you have **Continue**. Keeping them separate is what makes "append but read the whole field" something you can build.
- **Answer with the text only**: on for everything except **Explain**, whose answer is prose about your text rather than a replacement for it. Hidden once **Write the whole prompt** is on, since a raw prompt has nothing left for the switch to act on.
### About the prompt field
You write the *task*, not the whole prompt. The app wraps what you write in a fixed frame. That frame says who the model is, and it says your field's text is material to work on and never instructions to obey. Unless you turn it off, it also says to answer with the result and nothing else.
That's on purpose. The middle part is what stops a model from treating text in your field as commands aimed at it, and that text is often an email you're replying to or a message someone sent you. A text box shouldn't be able to delete that rule by accident. **The prompt that the app sends** below the editor shows you the assembled result, so it's visible without being editable.
Write `{target}` anywhere in a prompt and it becomes the translate target language, which is how the shipped **Translate** action works. Because the token is stored rather than the language, changing the target language updates every prompt that mentions it.
**Write the whole prompt** is the deliberate way out for anyone who wants to write the safety wording themselves. The app then sends your text exactly as you typed it.
Prompts you'd already customised carry over automatically, and the app checks its own work. A prompt it recognises is unpicked back into a task. Anything it doesn't recognise is kept verbatim under this switch rather than guessed at.
## Letting the model look things up
Tools / AI writing tools / What the model can do
A model answers from what it was trained on, which is a fixed moment in the past. Turn one of these on and it can stop guessing: before it answers, it asks the keyboard to go and find out.
- **Let the model search the web**: on by default, but the row only appears once you've set up a search service. The model writes its own search (usually a better one than the question you asked), reads the results, and answers from them. Searches go through whatever backend the [search tool](https://wmkeyboard.pages.dev/tools/search/) already uses (your SearXNG instance, your Tavily key, or Brave) and count against that service's quota like any other search.
- **Let the model read a web page**: off by default. The model gives one address and gets the text of that page back. It needs no search service, so it works on its own: paste a link, ask "what does this say?", and the model fetches it.
- **Lookups per answer**: 1 to 5, default 3. How many times the model may go and look something up before it has to answer with what it has. Each round is another request carrying everything found so far, so this is the setting that decides what a curious model costs you.
Both tools work in the panel's actions and in [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/). While one runs, the progress readout says what it's doing and on what: **Searching the web for "…"** with the model's own query, or **Reading example.com…** with the site it opened.
### Why page reading is the one that's off
**Let the model search the web** sends a query the model wrote. **Let the model read a web page** opens an address, and that address can have come from the text you were typing: a link in a document someone sent you, say. That makes it the one tool here that something in your own field could point somewhere, so it waits until you ask for it.
When it does run, the page is fetched as plain text and cut short at about 6,000 characters. No scripts run, nothing is sent *to* the page, and only `http://` and `https://` addresses are opened at all. A model that invents a `file://` or `content://` address gets a refusal, not your storage.
### What your provider needs to support
Most providers do tool calling properly, as part of their API: Claude, OpenAI, Gemini, Grok, DeepSeek, Ollama, LM Studio and any other OpenAI-compatible service. Two can't:
- **Brave** takes a single message with no system prompt and has no tool field at all.
- **On-device models** have no tool API in the local runtime.
For those two the keyboard falls back to explaining the tools to the model in words and watching its answer for a reply in the agreed format. The reader is deliberately forgiving about that format, because local models mangle it: `tool_call` is a special token in several chat templates, so the template rewrites the very thing the keyboard asked for, and a marker can come back with its brackets moved, doubled, or missing altogether. All of those are read, and anything that looks like one is hidden from the answer whether or not it turns out to be a call, because machinery the model wrote for the keyboard is never yours to read.
It works, and a capable local model will use it, but a small one still ignores the format sometimes, or asks you to narrow a request it could have searched as written. The settings group says so when you're on one of those providers. If lookups matter to you, a provider with real tool support is far more reliable.
Thinking models add one more rule. Before a model calls a tool it may reason, and several providers make the keyboard hand that reasoning back, untouched, along with the tool's answer: Gemini 3 attaches a signature to every call, Claude signs its thinking blocks, DeepSeek wants its reasoning text, and Gemini reached through OpenRouter or an OpenAI-compatible address hides the same signature in other fields. The keyboard keeps each model's turn exactly as it arrived and sends it back that way. Some providers reject the request if anything is missing.
### Seeing what it looked up
An answer that used a tool says so. A line sits over the result, in the panel and in every chat answer: **Searched the web for "…"** or **Read example.com** for a single lookup, or **Used tools 3 times** when there were several. No line means the model answered from what it already knew.
Tap the line to unfold it. Each lookup is listed with what came back:
- **A search** shows the query and every result the model was handed, by title and site. **No results** if the search came back empty.
- **A page read** shows the page's title and its full address, so you can check it's the page you think it is.
- **A lookup that failed** is marked as failed, with the reason: **Web search is not set up**, a network error, the model asking for something that isn't a web address, and so on. The model is told too, and usually says it couldn't check.
Tap a result to open it in your browser, or press and hold to copy its address. Only `http://` and `https://` addresses ever open. Anything else the model wrote can be copied but not opened. The lines aren't part of what **Replace** or **Insert** puts in your field.
In [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) the record is saved with the answer, so reopening a conversation later still shows what each answer was built on. The panel's actions keep it only while the result is on screen.
Every search and every page fetch is also a row in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/), filed under **AI tool use** rather than **Web search**: you didn't run that search, the model did, and the log should say which.
## The keyboard's own tools
Tools / AI writing tools / Keyboard tools
Besides searching and reading pages, the model can use the keyboard's own tools, the same ones on your toolbar, through the same services and with the same settings. Ask "what's 18% of 2,450 in euros?" and it works the sum out on the calculator and converts it at a live rate instead of guessing either.
Each tool has its own **Run**, **Ask** or **Off**, the same three choices an [MCP server's tools](#run-ask-or-off) have:
- **Run**: the model uses it whenever it needs to.
- **Ask**: the model's request stops and waits for you, with what it is about to do and the values it will send. See [Allowing a call](#allowing-a-call).
- **Off**: the model isn't told the tool exists.
| Tool | What it does | Starts on |
|---|---|---|
| **Image search** | Finds pictures through the [search tool's](https://wmkeyboard.pages.dev/tools/search/) service. Only listed once one is set up. | Run |
| **Weather** | Today's and tomorrow's weather for any place, or the place saved in the [weather tool](https://wmkeyboard.pages.dev/tools/instruments/#weather) when the model names none. | Run |
| **Wikipedia** | Reads the summary of the best-matching article, in the Wikipedia language the [encyclopedia tool](https://wmkeyboard.pages.dev/tools/search/#reading-wikipedia) uses. | Run |
| **Dictionary** | Looks a word up in the [dictionary tool's](https://wmkeyboard.pages.dev/tools/search/#looking-up-definitions) sources, in your order. | Run |
| **Translate** | Translates with the [translate tool's](https://wmkeyboard.pages.dev/tools/translate/) engine: on your device, online, or on your device first, as set there. | Run |
| **Currency converter** | Converts money at live rates from the [currency tool's](https://wmkeyboard.pages.dev/tools/utilities/#converting-units-and-currencies) sources, coins included. | Run |
| **Unit converter** | Converts lengths, weights, temperatures and other measurements. | Run |
| **Calculator** | Works an expression out exactly, on your phone, with the [calculator's](https://wmkeyboard.pages.dev/tools/utilities/#doing-the-math) degree and precision settings. | Run |
| **WM Keyboard docs** | Searches this documentation for questions about the keyboard itself ("how do I import a Chinese dictionary?") and reads the best-matching sections, with their links. Sends only the search words, to this site. See [Using the docs with AI](https://wmkeyboard.pages.dev/reference/ai-access/). | Run |
| **Read your calendar** | Lists your events for the days the model asks about, up to a month at a time. Needs the [calendar tool's](https://wmkeyboard.pages.dev/tools/calendar/) access. | Ask |
| **Read your notes** | Searches your [notes](https://wmkeyboard.pages.dev/tools/notes/) and reads up to five that match. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, notes that say the same thing in other words follow under "Close in meaning". A locked note's text stays hidden. | Ask |
| **Read your snippets** | Searches your [snippets](https://wmkeyboard.pages.dev/tools/snippets/) by their name, trigger, tags and text, and reads up to five that match, or the five newest when the model asks for none in particular: each one's name, how it is typed and its text, cut short. A [template](https://wmkeyboard.pages.dev/tools/snippets/#template-variables) or [pattern](https://wmkeyboard.pages.dev/tools/snippets/#pattern-triggers) is read as written, `{date}` and `$1` included, and never expanded. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, snippets that say the same thing in other words follow under "Close in meaning". | Ask |
| **Search your clipboard** | Finds the clips in your [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/) that mean what the model asks about, in any language, and reads up to eight short excerpts. Passwords, one-time codes and deleted clips are left out. Only listed while [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) is on and its model is downloaded, and it only finds what the keyboard has already indexed, which happens when the clipboard panel opens. | Ask |
| **Your location** | Reads where your phone is, rounded to the precision the [location tool](https://wmkeyboard.pages.dev/tools/location/) shares at, with the address if you have that on. | Ask |
| **Add to your calendar** | Adds an event, see [Adding calendar events](#adding-calendar-events). | Ask |
| **Save a note** | Adds a new note. | Ask |
| **Set an alarm** | Sets an alarm in the [clock](https://wmkeyboard.pages.dev/tools/clock/), once or on repeat days. | Ask |
| **Start a timer** | Starts a countdown in the [clock](https://wmkeyboard.pages.dev/tools/clock/). | Ask |
| **Open an app** | Opens an installed app by its name, from the [app launcher's](https://wmkeyboard.pages.dev/tools/app-launcher/) list. | Ask |
| **Control media** | Plays, pauses or skips what is playing, or says what it is. Needs the [media tool's](https://wmkeyboard.pages.dev/tools/media/) notification access. | Ask |
| **Send to your computer** | Sends text or a link to a computer paired through [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/). Only listed while KDE Connect is on. | Ask |
| **Read your home** | Lists what is on or open across every home of the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/), or whatever matches a home, room, device or sensor name. Only listed while a home is set up. | Ask |
| **Control your home** | Works one device on any home of the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/): lights, fans, temperature, scenes, locks. The model finds the device with **Read your home** first. With a Home Assistant on the list it can also send one plain sentence to Assist. | Ask |
| **Read text in a picture** | For a model that can't see pictures: reads the text in one you [sent in the chat](https://wmkeyboard.pages.dev/tools/ai-chat/#sending-pictures), with ML Kit on your device google, or the [scan text tool's](https://wmkeyboard.pages.dev/tools/scanner/) online engine if you picked that. | Run |
| **Search by picture** | Uploads a picture you sent to the [camera tool's](https://wmkeyboard.pages.dev/tools/camera/) search site (Google Lens unless you changed it) and gives you the link to the results. The model can't read that page itself. | Off |
The tools that read what's on your phone, and the ones that change something, start on **Ask**. A web page the model reads could carry instructions of its own, and **Ask** means nothing it says reaches your calendar or your notes without you seeing the request first. None of the tools can delete or edit anything: they only read, add or start. **Always allow** on the request card switches that tool to **Run**.
The calendar tools, and the alarm, are told today's date and your time zone, so "tomorrow at five" lands on the right day.
Every tool that is on is described to the model in every request, whether it gets used or not. That costs a little each time, and a small model on your device copes best with a short list, so turn off what you don't want it reaching for.
While one runs, the line over the answer says what it is doing: **Checking the weather in Dhaka…**, **Converting 12 USD → EUR…**. Unfolded, a Wikipedia lookup lists the article it read, and an image search the pages its pictures came from.
### Adding calendar events
Tools / AI writing tools / Keyboard tools / How events are added
**Add to your calendar** works one of two ways:
- **Open the app**, the default: your calendar app opens with the event filled in, and nothing is saved until you press its own Save. It needs no permission. The model is told the event isn't saved yet, so it says so.
- **Add directly**: the keyboard writes the event into your calendar itself. Picking it asks for permission to add calendar events, with the usual explanation first, then lets you choose which calendar they go in. The first writable one, primary first, is used until you pick.
## MCP servers
Tools / AI writing tools / MCP servers
MCP (the Model Context Protocol) is a standard way for a server to offer tools to a model. Add a server here and the model can use its tools in the panel's actions and in [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/), the same way it uses the two lookups above: to read your notes, search a wiki, add a calendar event, or whatever the server does. The tools run on that server, not on your phone.
### Adding a server
Press the **+** button. The editor asks for:
- **Name**: what the list calls it. Leave it blank and the host name is used.
- **Address**: the server's `https://` or `http://` address, for example `https://example.com/mcp`.
- **Type**: **Auto**, **Streamable HTTP** or **SSE**. Auto works for almost every server. It tries Streamable HTTP, the current transport, and falls back to the older SSE one when the server answers like an old server. Pick SSE for an old server that Auto can't reach. Its address often ends in `/sse`.
- **Headers**: sent with every request to this server. This is where a token goes. A typical one is the name `Authorization` with the value `Bearer` followed by a space and your token. Values are hidden until you tap the eye.
**Save** stores the server. **Connect** saves it too, then asks the server for its tools and reports back: "Connected to *server name*: 12 tools", or the reason it couldn't. A server you just added says **Not connected** in the list until a Connect has worked, and the model is offered nothing from it until then.
Change the address later and the tool list no longer describes the server, so the editor asks you to connect again.
### Pasting a JSON config
Most desktop apps that use MCP keep their servers in a JSON file. Copy that file's text, then press **Paste a JSON config** on the server list. The keyboard reads the formats in common use: Claude Desktop and Cursor's `mcpServers`, VS Code's `servers`, the inner map on its own, or a single server. It shows what it found before anything is saved, then **Add 2 servers** adds them and connects each one.
```json
{
"mcpServers": {
"notes": {
"url": "https://notes.example.com/mcp",
"headers": { "Authorization": "Bearer your-token" }
},
"files": {
"command": "npx",
"args": ["-y", "some-file-server"]
}
}
}
```
Here **notes** is added. **files** is listed as skipped: it starts a program on a computer (the `command` line), and a phone has nothing to start it with. A server with no address, or one that isn't `http` or `https`, is skipped too, with the reason beside it.
### Run, Ask or Off
Once a server is connected, its editor lists every tool, with the server's own description and badges for what the server says about it: **Read only**, or **Can change or delete data**. Each tool has three choices:
- **Run**: the tool runs when the model calls it, without asking you.
- **Ask**: the answer stops and waits for you to allow the call.
- **Off**: the model isn't told the tool exists.
A new tool starts on **Run** if the server marks it read only, and on **Ask** otherwise. That default matters because a tool can send mail or delete a file, and text in the field you're editing could try to talk a model into calling one. Your own choice always wins over the server's marks.
The list row for each server sums it up ("12 tools: 3 run, 7 ask, 2 off") with a switch that turns the whole server off without deleting it.
### Allowing a call
When the model calls a tool set to **Ask**, an approval card appears in the panel or in the chat. It names the tool and the server and shows exactly what the model wrote as the tool's input. Three buttons:
- **Deny**: the call doesn't happen. The model is told you said no and answers without it.
- **Allow once**: this call goes ahead.
- **Always allow**: this call goes ahead, and the tool is set to **Run** from now on.
### What is sent, and where
Nothing goes to a server until the model calls one of its tools, or you press Connect. A call sends the tool's name and the values the model wrote for it straight to your server, with your headers. It doesn't go through any WM Keyboard service. Whatever the server sends back is handed to the model. Every request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under **MCP servers**.
### Limits
- **No sign-in yet.** A server that wants you to log in through OAuth can't be used. If it gives out tokens or API keys, add one as a header instead.
- **Only `http` and `https` servers.** Local servers that start as a program (`command`, `npx`, `uvx`) need a computer to run on.
- **Every tool costs prompt space.** The name, description and input format of each tool that isn't **Off** go with every request, so 30 tools can cost more than the text you asked about. The server list warns you past 20. Turn off the tools you don't need.
- **Text only.** Images, audio and links a tool sends back aren't shown to the model. It is told that something was there.
## Suggestions in the strip
Tools / AI writing tools
Off by default. Turn on **AI suggestions** and the provider you set up here offers one short continuation of what you're typing, as a chip in front of the word suggestions. The chip carries a sparkle and an accent outline, so it never passes for a dictionary word. The word suggestions stay where they are, and a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) still wins the strip when one comes up.
A request goes only after a word ends (a space or a punctuation mark) and you stop typing for about 0.6 seconds. In Chinese and Japanese, which put no space between words, a committed Han or kana character counts as a word end too, so the chip can follow 你好 with nothing typed after it. On a pinyin, kana or other conversion layout it waits until the reading is committed and the candidates have left the strip. Any key you press cancels it. The answer has 1.5 seconds to arrive; after that the chip doesn't appear and nothing tells you, since a late suggestion is for text you've already moved past. The chip shows an answer only if it is one line of at most 48 characters that doesn't just repeat your text.
The request asks the model not to think first, where the provider has a way to say so. A model that reasons anyway, such as DeepSeek's default or OpenAI's reasoning models, gets up to 512 tokens and 5 seconds instead, since its reasoning comes out of the same budget as the answer.
It normally asks once a word has ended. **Ask while typing a word** (off by default, because it sends many more requests) also asks mid-word. While a Chinese or Japanese reading is still unconverted it asks a different question: what the reading most likely spells in your sentence. The chip then shows those characters, and tapping it puts them in place of the reading, like picking a candidate. If the model takes the reading for Latin text there is no chip, and shape codes such as Wubi, Cangjie and strokes aren't sent at all. **Ask after a word ends** can then be turned off. Each has its own **Wait before asking** and **Give up after** time, and a model that always thinks first is skipped mid-word, though a reading waits for it, up to the same 5 seconds. Thai, Lao, Khmer, Burmese, Tibetan, Javanese and Balinese count as spaceless, like Chinese and Japanese: their letters can end a word, and answers in them stop at 16 characters.
A conversion you tap is learned like a picked candidate, unless learning is off, you're in incognito or it's a password field, so the same reading ranks it higher next time. On the built-in Pinyin engine that holds even for a word the dictionary lacks, as long as you typed full pinyin and the word has one character per syllable. On RIME and Mozc it's learned only if the engine lists the word too.
Tap a continuation to insert it after your text, with a space in front where one is missing (never next to Chinese or Japanese). Since 0.6.6 the word it ends on is learned once, as if you'd typed it, so a word the model found for you counts toward your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) the way your own do. When the continuation finishes a word you'd started, the whole word is learned. Only that word: the rest is the model's phrasing, so no other word and no word pair comes from it. Nothing is learned with learning off, in incognito or in a password field, and nothing you've learned is ever sent to the provider. Hold it to hide AI suggestions until you move to a different field. **Show after the words**, off by default, keeps the chip at the end of the strip instead of in front of the words, so a chip arriving late never moves a word you're about to tap.
What each request carries:
- **Text to send**: the last 120 characters before the cursor by default, cut forward to a whole word. The slider goes from 40 to 500.
- **Send only the last word**: sends that word and nothing else. Less goes out, and the suggestions get worse.
- A short system prompt asking for a continuation in the same language, with no explanation and no quotes.
No request is made at all in a password field, in a field that turns suggestions off, in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), when no provider is set up, or while [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds cloud AI back. With an **On-device** model the text stays on the phone. A phone model can't be stopped part way, so no new request starts while one is still running, or while the AI panel is using the model. The first request also has to load the model, which can take longer than the 1.5 seconds.
## Chatting instead of transforming
Tools / AI writing tools / Chat with the model
The panel's actions all do the same shape of thing: take the text in a field, hand it back changed. When you want an actual conversation instead, tap **Chat** on the panel's header. The panel swaps its actions for a conversation with any model you've already set up here: ask something, then **Insert** the answer into what you're writing. **Actions** on the same header swaps back, and the panel reopens on whichever you used last.
The same chats open full-screen in the settings app, at the path above or from a launcher shortcut of their own. Both places share one list of saved conversations. See [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/).
Two more ways in work from any app. The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen), set to **Ask AI about this screen**, opens a chat as a sheet over the screen you're on, with that screen's text attached when screen reading is on. **Ask AI** in the text selection menu opens the same sheet with the selected text; **Ask AI in the selection menu** below takes it out of that menu. The phone's assistant gesture can open it too, with **Use as the phone's assistant** below. **WM Keyboard** in the same menu runs your actions on the selected text directly (Explain, Summarize, Shorter and the rest) and can put the answer in place of the selection; see [In any app's selection menu](https://wmkeyboard.pages.dev/smart/selection-actions/#in-any-apps-selection-menu).
## Memory
AI / Memory
The AI can keep short facts about you between chats: where you live, what you do, how you like answers. A chat saves them, with an **Undo** under the answer each time, and the panel's actions and AI reply are handed them too, so a reply or a fresh piece of writing can use them. Actions only read memory, never save to it, and are told to leave the facts out unless the task needs them. A model on your phone gets memory while it's on; a cloud or self-hosted service gets it only after you turn on **Use with cloud models**. Incognito fields get none. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) for the whole of it.
## History
Tools / AI writing tools / History
Off by default. Turn it on and the keyboard keeps a record of each run: what you sent, what came back, which action and model, how long it took, how much of that was reasoning, and whether you used the answer. The screen searches across the input, the answer and the model name. It filters by action, and expands a row so you can copy either side or delete it.
Failed runs are recorded too, with the error the panel showed, since "it keeps failing" is the most likely reason to open the screen at all.
Four things have to be true before anything is written down, and any one of them being false is the end of it:
1. You turned the history on.
2. The device has been unlocked at least once since it restarted.
3. The field isn't a password box.
4. [Incognito](https://wmkeyboard.pages.dev/privacy/incognito/) isn't in force, from either the setting or a field that asked for it, like a private browser tab.
API keys are never part of a record. It carries the model's name and nothing about the connection.
**Number of records to keep** defaults to 100 and tops out at 500. The oldest falls off once it's full, and each side of a record is capped at 4,000 characters. **Delete all history** clears the lot, and so does turning the setting off. An off switch that quietly leaves the log on disk isn't an off switch.
## Options
Tools / AI writing tools
Like every tool, AI has an **Enabled** toggle (on by default) and, if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are on, a per-tool icon color override. Below that:
- **Keyword**: the word that offers this tool on the suggestion strip when you type it alone. Defaults to `ai`.
- **Extra headers**: on each service's own screen, none to start with, see [Extra headers](#extra-headers).
- **AI providers**: the services and models the AI can use, see [Choosing where it runs](#choosing-where-it-runs). Empty on a fresh install, which lands on the empty-state screen until you add one. Until you edit it, the list holds every service your earlier settings had a key or an address for.
- **In use**: which row answers, shown once the list has two. The model picker on the panel changes it too.
- **Max response length**: a token ceiling, roughly ¾ of a word each, picked from steps rather than a slider: 1k through 128k, plus **Provider maximum**. Default 8,192. A model the app takes for a reasoning model gets 4× this ceiling on its own, capped at 131,072, because reasoning tokens come out of the same budget before the answer starts. That guess is a substring match on the model ID, for strings like "thinking", "qwen3", "grok-4" or "deepseek-v4". The On-device provider swaps this row for **Context size**.
- **Translate action's target language**: free text, defaults to **English**.
- **Show model reasoning**: off by default. Turning it on streams a reasoning model's raw `` passages instead of the step-by-step progress readout. It's for the panel's actions only: the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/#reasoning-and-tool-calls) always keeps reasoning in a folded step over the answer.
- **Model picker on the panel**: on by default. Adds a horizontally-scrolling row to the panel itself for switching between every configured provider and downloaded local model in one tap. Under it sits the name of the model in use: press it and the keyboard asks the provider what it offers, then lists those to pick from. **Choose from the list** beside the Model field on a provider's page does the same in settings. It's hidden automatically whenever there are fewer than two to choose from.
- **Show a changes view**: on by default, see [Seeing what changed](#seeing-what-changed). **Open a result on the changes** below it is off.
- **AI suggestions** (under **Suggestion strip**): off by default. With it on, **Send only the last word** (off) and **Text to send** (120 characters) set what each request carries. See [Suggestions in the strip](#suggestions-in-the-strip).
- **Let the model search the web**: on by default, and only shown once a search service is set up. **Let the model read a web page** below it is off, and **Lookups per answer** defaults to 3. See [Letting the model look things up](#letting-the-model-look-things-up).
- **Keyboard tools**: Run, Ask or Off for each of the keyboard's own tools the model can use, see [The keyboard's own tools](#the-keyboards-own-tools). The information tools start on Run.
- **MCP servers**: the servers whose tools the model can use, see [MCP servers](#mcp-servers). None are set up to start with.
- **Chat with the model**: opens the conversation screen, see [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/).
- **Enter sends the message**: off by default. In the chat on the keyboard, Enter starts a new line and the send button sends. Turn this on to send with Enter instead.
- **Auto-rename chats**: on by default. The model that answered names each chat from your first message and its reply, in your language, and can rename it later when you ask and accept. See [Naming chats](https://wmkeyboard.pages.dev/tools/ai-chat/#naming-chats).
- **Ask AI in the selection menu**: on by default. Puts **Ask AI** in other apps' menu for selected text. Turn it off to remove the entry; turning the AI tool off removes it as well.
- **Use as the phone's assistant**: off by default. Lists WM Keyboard among Android's digital assistant apps; once you pick it there, the assistant gesture opens a menu of chat actions over the app you're in. **Choose the phone's assistant**, under it, opens Android's page for the pick. See [Using it as the phone's assistant](https://wmkeyboard.pages.dev/tools/ai-chat/#using-it-as-the-phones-assistant).
- **Actions on the panel**: the whole action list, see [Your own actions](#your-own-actions).
- **History**: off by default, see [History](#history).
### On the History screen
Tools / AI writing tools / History
One more row lives on the History screen, under a **Carry-on actions** heading. Up to 0.6.3 that heading read **Downloading models**, left over from a Wi-Fi switch that has since moved: model downloads on a metered connection are set in [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/), on the **Downloads** row, which starts at **Ask each time**: the size-keyed prompt (500 MB for a language model, 150 MB for a Whisper one) still fires on top, so a smaller model asks too rather than coming down without a word. Set the row to **Turn off** and no model downloads on mobile data at all.
- **How much the AI reads back**: 4,000 characters by default, anywhere from 500 to 32,000. This is how far back from the cursor a carry-on action such as **Continue** reads. More characters give the model more of your writing to follow, and cost more tokens and more time on every run. It has no effect when you've selected something, because a selection is always sent as it is.
### What the On-device provider adds
google
Adding **On your device** to the list puts the whole model manager on that row's own screen: press **On your device** to open it. [Local models](#local-models) covers the catalog, **Compute** and **Context size**. Three more things sit on that screen and nowhere else:
- **Hugging Face account**, holding an **Access token** field. Blank by default, stored the same way an API key is, and only the gated models need it. Under the field, **Get a token** opens `huggingface.co/settings/tokens` in your browser.
- **Import your own**, an **Import model file** button that opens the system file picker. It takes `.litertlm` and `.task` files only, and anything else is rejected with a message under the button.
- **Free up**, labelled with a size (like "Free up 584 MB"). It only appears when there are orphaned files, meaning model directories left behind by an app update that dropped a model from the catalog. No row can reach those files any more, so the button is the only way to delete them. They're reported rather than deleted for you, on the grounds that it's a multi-gigabyte download you already paid for.
Above that sits a line reporting how much storage your models take in total, partial downloads included. That figure is usually bigger than the one beside **Your models**, which only sums the finished ones you can see.
The first time a model loads, the engine also builds working files from it, about the model's own size again, and keeps them so later loads are fast. They count as a cache: **Settings → About → Storage** lists them as **AI model working files** under **Caches**, and **Free up** there clears them, at the cost of one slower load. Deleting a model deletes its working files with it.
### About "Provider maximum"
This one is worth understanding, because the old default was the reason a long **Improve** used to come back with only the first part rewritten. **Provider maximum** leaves the ceiling out of the request entirely and lets the service apply its own, which for a modern model is far more than any number you'd pick by hand. It's the right choice for long text.
Anthropic is the one exception: its API requires the field, so the app asks for a large number there instead. If the model you picked allows less, Anthropic rejects the request and names its real limit in the error. The app retries once at that number. Nothing has streamed at that point, so all you see is the answer.
Ollama had never been sent a limit at all before this. It gets one now, and **Provider maximum** there means what it always did.
## What leaves your phone
The panel itself sends nothing until you actually tap an action chip: opening the tool, or typing in the field behind it, sends nothing anywhere on its own.
- **On-device**: the text you run an action on never leaves the phone. Response length is bounded by the local model's own context window instead of a server-side limit.
- **Privatemode**: the text is encrypted on your phone before it's sent, or by your own proxy. See [Privatemode](#privatemode) for what the phone does and doesn't check.
- **Any other provider** (cloud or self-hosted): the text you run an action on is sent to that provider, only at the moment you tap the action. API keys and server addresses are stored on the device, not in any WM Keyboard service. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for how this fits the keyboard's broader per-tool approach to anything that leaves the device.
- **AI suggestions, when [Suggestions in the strip](#suggestions-in-the-strip) is on**: every time you stop typing after a word, the last 120 characters before the cursor (or the number you set, or only the last word) go to the same provider, with no tap. Never from a password field, a field that turns suggestions off, or incognito. Off by default, and an on-device model keeps it on the phone.
- **Dictation, when [Tidy dictation with AI](https://wmkeyboard.pages.dev/tools/voice/#tidying-dictation-with-ai) is on**: each phrase you dictate goes to the same provider as soon as it lands, with no tap, to have its filler words taken out. Its text only, never the audio. Off by default, and an on-device model keeps it on the phone like any other action.
- **Report**: builds a draft in your mail app and hands it over. The app has no reporting server and sends nothing itself, so a report only exists if you send the mail. API keys are never part of it.
- **AI memory**: with an on-device model, goes nowhere. With a cloud or self-hosted provider, the saved facts picked for a run go with it only once you've turned on **Use with cloud models**, which starts off. Incognito fields never send them. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/).
- **History**: goes nowhere. It's a file in the app's private storage, off unless you turn it on, never written from a password field or in incognito, and deleted when you turn it off. It's deliberately left out of the backup file too, so it can't ride along in something you export and mail to yourself. There's no section for it to travel in, unlike the learned dictionary, which is a section of its own that you tick or leave alone. See [Your data](https://wmkeyboard.pages.dev/privacy/data/).
- **A lookup the model asked for**: goes to the service that answers it, and only when the model actually asks. A search carries the query the model wrote, not your field text, to whichever search backend you set up. A page fetch carries nothing but the request for that page. Both show in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under **AI tool use**, and both are off entirely while [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is holding web search back.
- **Search by picture**: uploads the picture to the search site picked in the camera tool, and only when the model calls it, which it can't until you turn the tool on.
- **One of the keyboard's own tools**: goes where that tool always sends it, and only when the model calls it. The weather goes to Open-Meteo, a translation to the translate tool's service (or nowhere, on your device), a rate lookup to the currency tool's sources, and so on. The calculator and the unit converter send nothing. Each request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under the tool it belongs to. What the tool hands back goes to your AI provider with the rest of the conversation: your events, a note, your location. That is why the tools that read your things start on **Ask**.
- **A tool on an MCP server**: goes to the server you added, and only when the model calls that tool or you press Connect. A call carries the tool's name and the values the model wrote for it, with your headers. See [MCP servers](#mcp-servers).
- **The changes view**: is computed on your phone. Comparing two pieces of text is arithmetic, not a request, so nothing is sent to build it.
## Details & edge cases
- **Not every row here can be reset.** The **Enabled** switch and four of the AI switches (Show model reasoning, Model picker on the panel, Show a changes view, Open a result on the changes) grow a restore button once you move them off the default listed above, and so does the Translate target-language field. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). **Keyword** does it differently: change the word and a **Reset to defaults** row appears under the field, naming the word it would put back. Max response length and Context size are drawn as chip rows rather than settings rows, so neither has a button. The AI providers list has a **Reset to default** row of its own once you change it, which puts it back to what your earlier settings make.
- **Offline, an on-device model can stand in.** With [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) on and a [local model](#local-models) downloaded, an AI action set to a cloud provider or a server runs on the phone when there is no connection, or when the server does not answer. A short message says so the first time. AI chat always uses the provider you picked.
- **AI isn't pinned to the toolbar or the onboarding wizard's starter set by default.** You add it yourself from the toolbox once you want it, the same as most non-essential tools.
- **Not available on the lock screen.** AI needs credentials the direct-boot-safe mirror of your data deliberately doesn't carry, so it's one of the tools that only works after you've unlocked the device once since restart.
- **The Writing keyboard mode pins AI first** on its toolbar for apps it recognizes as writing-heavy (see [Keyboard modes](https://wmkeyboard.pages.dev/typing/modes/)). The pin shows in every build, same as the tool itself. Only the On-device provider is missing from the F-Droid build.
- **A reasoning model's guess is a substring match** against the model ID, on strings like "thinking", "-r1", "qwen3", "gpt-5", "gpt-6", "grok-4", "deepseek-v4". It can be wrong in either direction. A false positive only costs a larger token ceiling. A false negative risks a response cut off mid-thought.
- **Custom has no stored default prompt.** Every other action's prompt can be edited and still has a built-in fallback. Custom's instruction only ever exists for the run you type it for.
- **On-device answers never claim to have been cut off.** The local engine reports no stop reason, so there's nothing to read, and the app says nothing rather than guessing. Every other provider does report it.
- **The changes view can't spot moved text.** A paragraph the model relocated reads as a deletion where it was and an insertion where it went. Detecting moves properly needs a second pass that git only does on request. The actions that move text, Summarize and Rewrite, reword it at the same time, so that pass would fire rarely and mislead when it did.
- **Comparisons drop to whole characters for scripts without spaces** (Chinese, Japanese, Thai, and others). Word-sized pieces would make an entire sentence a single highlight there. Mixed text uses characters throughout, which is noisier but never wrong.
- **Local models are advisory about RAM, never a hard gate.** A phone under a model's stated minimum can still download and run it. You get a warning in the catalog first.
- **A model that will not stop looking things up still answers.** When it has used up its **Lookups per answer**, the last request is sent with no tools offered at all, so the model has to write something with what it found. It isn't shown an error.
- **The round limit is a ceiling, not a target.** Most answers use none. A model only calls a tool when it decides it needs to, and nothing is sent anywhere on a run where it doesn't.
- **A failed lookup is handed to the model, not to you.** A search with no backend, a page that 404s, an address that isn't one: the model is told in a sentence and carries on, usually by saying it couldn't check. A broken page doesn't fail the whole answer.
- **What a lookup found isn't kept in a chat's transcript.** Only the model's answer is saved. Ask a follow-up and the model has its own earlier reply to go on, not the search results behind it, so it may look again, which is usually what you'd want anyway.
- **Tools are off on a local model mid-conversation until the chat restarts.** A local chat session carries its instructions from the moment it opened, so turning a tool on part-way through takes effect on the next conversation rather than the next message.
- **No shared or built-in key for any cloud provider.** Claude, OpenAI, Gemini, Grok, DeepSeek and Brave each need your own API key. There's no free built-in quota here, the way some of the other network tools offer one.
- **A model download dies with the app's process.** It resumes rather than restarting, but it isn't a foreground service, so backgrounding the app for long enough can stop it. That hasn't changed. The panel just shows you it's happening now.
---
# AI chat
URL: https://wmkeyboard.pages.dev/tools/ai-chat/
Section: Tools
Summary: A full back-and-forth conversation with whichever model you set up, on the keyboard's AI panel or in the settings app, as one shared list of chats.
The [AI tool](https://wmkeyboard.pages.dev/tools/ai/) runs one-shot actions against the text in a field. Chat is the other half: a normal back-and-forth conversation with the same model, kept on your device.
You can chat in two places, and they're the same chats. The keyboard's AI panel has a **Chat** mode, for asking something and dropping the answer straight into whatever you're writing. The settings app has a full-screen version, for longer conversations. Start one on the keyboard, carry it on in the app, come back: there's one list, and an answer that's still streaming in shows up on both.
## Chatting on the keyboard
Open the AI tool from the toolbar. The panel's header has two chips, **Actions** and **Chat**. Tap **Chat**. The panel remembers which one you left it on, so if chat is what you use, the AI button opens chat from then on.
tools/ai-chat-keyboard — Chat mode on the AI panel: the conversation, the newest answer's buttons, and the composer.
Tap the message box and the keys come up underneath, with the last few messages still in view above it. The keyboard gets taller while you type so the conversation doesn't disappear. On a short screen the conversation gives up room first: the keys under the message box always keep their full height. Tap the conversation, or press Back, and the keys step aside again so you can read. Your half-typed message stays where it was, even if you close the panel.
Glide typing, suggestions and the caret all work in the message box the way they do in any of the keyboard's own fields. Nothing you type there reaches the app behind the keyboard.
Your own messages sit in bubbles on the right. Answers have no bubble: each one runs the full width of the panel, as long as it is, so a table or a block of code gets all the room there is.
### Getting an answer into your text
The newest answer always shows its buttons, a row of plain icons under it. Tap any older message to bring out its own. Long-press an icon to see its name.
- **Insert** puts the answer in the field at the cursor, with markdown markers stripped, since a text field won't render them. A table keeps one line per row, its cells between bars.
- **Copy** copies the answer as written. Long-pressing a bubble copies it too, in one move.
- **Write again** throws the newest answer away and asks for another.
- **Share** sends the answer to another app as a Markdown file. See [Sharing an answer](#sharing-an-answer).
- **Edit**, on your own newest message, pulls it back into the message box along with the answer it got, so you can fix the question and send it again.
- A code block gets its own **Copy** and **Insert**, which take just the code, exactly as written.
Answers are drawn with their formatting: headings, lists, bold, inline code and code blocks. While an answer is still streaming, a code block waits for its closing fence before it offers its buttons.
Maths written in LaTeX is drawn as maths. A formula on its own line, between `$$` signs, inside `\[` and `\]`, in a ` ```math ` block or as a bare `\begin{aligned}`, is set the way a textbook sets it: fractions stacked, roots under their bar, limits over and under a sum, matrices in brackets that grow with them. A wide one scrolls sideways: its cut-off edge fades out and a thin bar under it shows how much more there is, as on a wide table or code block. A formula inside a sentence, between single `$` signs or `\(` and `\)`, sits on the line: `$x^2$` is a small raised 2, and a fraction or a matrix is drawn in place at the size of the text around it. A dollar amount is left alone, so "it costs $5 or $10" stays a sentence. When an answer is inserted into a field, which can't draw any of this, the maths is written out in Unicode instead: `x²`, `√(x+1)`, `(a+b)/c`.
### The message box
The message box is one rounded box. What you write is at the top; along its bottom edge sit its controls: the **Reasoning** switch at the start (see [The Reasoning switch](#the-reasoning-switch)), and the mode button, **+** and the send arrow at the end (see [Modes and answer formats](#modes-and-answer-formats)). While an answer streams, the send arrow becomes **Stop**.
### The + menu
The **+** button in the message box opens a row of icons over it. Long-press an icon to see its name.
- **Pictures from the gallery** opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/), or the system photo picker in the Play Store build. Pick several at once.
- **A file** opens the same file manager, or the system file picker in the Play Store build, for a text file, a recording, a video or a PDF. See [Sending files](#sending-files).
- **Take a photo** opens your camera app, and the photo you take is added. The first time, it asks for the camera permission.
- **Copied picture** takes the newest picture in your [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/).
- **Newest screenshot** takes the last screenshot on the phone, whenever you took it. The first time, it asks for access to your photos, and the screenshot is added once you come back from allowing it.
- **Send the text of the field** quotes what you're working on. With text selected in the field, it takes the selection; otherwise it takes the whole field. A line above the message box says which, and how long it is. The model gets your message followed by that text, clearly marked off, and the bubble notes that text went with it. Tap it again to drop the text. It's greyed out over an empty field, and it never reads a password field.
- **Paste** pastes your latest clip at the caret.
The photo picker, the file picker and the camera are system screens, so the keyboard steps aside while one is open and picks up where it was when you come back. The file manager, Copied picture and Newest screenshot don't leave the keyboard at all.
Up to four pictures and four files can go with one message. They wait above the message box with a cross on each to take it off, and a message can be pictures or files alone. Sent, they show at the top of your bubble, on the keyboard and in the app alike. What the model does with them is the same as in the app: see [Sending pictures](#sending-pictures) and [Sending files](#sending-files).
While [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on, or over a password field, the menu offers only the field's text and Paste. Play Store builds leave out **Newest screenshot**, since they don't ask for the photos permission a screenshot needs.
An empty chat offers a few openers: **Reply to this** and **Explain this** quote the field for you, **Help me write** starts the sentence. They only fill the message box. Nothing is sent until you send it.
### Asking about another tool's text
Wikipedia, Dictionary, Vocabulary and Grammar check can hand their text to the chat. Each has an **Ask AI** chip (on Grammar check, a sparkle on each issue card), and in Wikipedia, Dictionary and Vocabulary a long press on the text selects a passage with **Ask AI** on the bar over it. Either way the AI panel opens in chat mode on a new conversation, with the text attached the way the paperclip attaches the field's, and the line above the message box names where it came from, for example "About Wikipedia: Cat". The keys come up under the message box, and three openers stay on screen while you type: **Explain simply**, **Summarise** and **Hard words**. The text goes with your first message only.
The chips show only while the AI tool is on, and not on the lock screen.
### Switching chats
The chip in the header with the chat's title opens your list of chats: tap one to open it, **New chat** to start fresh, **Delete** twice to remove one. Swiping a chat to the left shows a red delete button behind it, which removes it in one tap.
The pencil on a chat renames it. The name turns into a field with the keys under it; type the new name and press Enter or the tick, or the cross or Back to keep the old one.
To clear out several chats at once, long-press one. That starts picking: each tap ticks a chat on or off, and the row above the list shows how many are picked, a **Select all** button, and **Delete**, which deletes them all on its second tap. The cross, or Back, stops picking. The plus button starts a new chat from anywhere. The arrow button opens the current chat in the settings app, which is the place to go when you'd like a full screen or selectable text.
### The Enter key
By default Enter starts a new line, and the send button sends. If you'd rather Enter sent the message, turn on Tools / AI writing tools / Enter sends the message.
### When chat isn't offered
On the lock screen the panel shows only its actions. Chats are stored encrypted behind your unlock, and a transcript isn't something to draw over a lock screen.
A chat started while [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on is never saved. You can read it and carry it on until the keyboard's process ends, and then it's gone.
## Chatting in the app
Tools / AI writing tools / Chat with the model
It sits in a group headed **Chat** on the AI tool's settings screen, under the reasoning and panel options. There's a faster way in too: long-press the app icon on your launcher and pick **AI chat**, which lands you on the conversation list without going through Home.
The **AI** screen on the settings home list has a round chat button in its bottom corner that opens the same list. If you'd like that button on every settings screen, turn on AI / Chat button on every screen. On a screen that already has a button of its own, such as the plus that adds something, the chat button sits above it in a smaller size. It stays off the chat screens and the Settings assistant, which are chats already.
tools/ai-chat-list — The conversation list, newest first, with the New chat button at the top.
The list is your saved conversations, most recently updated first. Each row is the chat's title and how long ago you touched it, with a pencil that renames it and a delete button that asks first and names the chat it's about to remove. Swiping a row to the left shows the same delete button behind it.
Long-press a chat to start picking several. Taps then tick chats on and off, and a bar at the bottom shows how many are picked, with **Select all** and **Delete**. Delete asks once for the lot. Back, or the cross on the bar, stops picking. **New chat** is both a button at the top of the list and a plus in the top bar.
Nothing saved yet? You skip the empty list entirely and land in a new chat, because an empty list with one button on it isn't worth a screen. A chat is only written down on your first send, so opening one and backing straight out leaves no row behind.
### Naming chats
Until it has a better name, a chat is called after the first line of your first message, cut to 48 characters on a word boundary.
With Tools / AI writing tools / Auto-rename chats on, the default, the model that answered names the chat once its first answer is in: a few words on what it's about, in the language you wrote your question in, even when the answer came back in another one. It's one more short request to the same model, with your first message and the answer, and it isn't made when [Data Saver](https://wmkeyboard.pages.dev/privacy/data-saver/) would stop or question a send. If it fails, the first-line name stays.
You can also ask the model to rename a chat ("call this chat Trip to Sylhet", "give this a better name"). It proposes a name with its **Rename chat** tool, and a card under the answer asks **Rename this chat to "…"?** with **Accept** and **Decline**. Nothing changes until you accept. On the keyboard, closing the keyboard while the card is up counts as Decline. The tool is offered only while Auto-rename chats is on.
Rename a chat with its pencil, on the keyboard or here, to give it a name of your own, up to 80 characters. A name you typed, or one you accepted, is never replaced by an automatic one.
### Sending pictures
The **+** button in the message box opens a sheet with **Pictures from the gallery**, **A file**, **Take a photo** and **Paste**. The gallery is the system photo picker, which needs no permission; **Take a photo** asks for the camera the first time. Pick up to four pictures for one message; they wait above the message box, each with a cross to take it off again, and a message can be pictures alone. In a sent message they show above its text.
What the model does with them depends on what it can take:
- **A cloud or self-hosted model that reads pictures** (Claude, OpenAI, Gemini, Grok, Ollama, LM Studio, any OpenAI-compatible service) is sent the pictures with your newest message. Older pictures aren't sent again with every reply after it; the model remembers what it said about them.
- **An on-device GGUF model that reads pictures** ([Qwen 2.5 VL and SmolVLM](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp), or one you imported with its `mmproj` file) is given the pictures of your newest message itself, on the phone. Older pictures stay numbered marks, as below.
- **Any other on-device model, or a provider with no place for pictures** (Brave and DeepSeek among them) can't take one. It is told each one is there, numbered, and can use **Read text in a picture** from the [keyboard's own tools](https://wmkeyboard.pages.dev/tools/ai/#the-keyboards-own-tools) to read what it says.
Either way, with **Search by picture** turned on the model can look a picture up on the web through the [camera tool's](https://wmkeyboard.pages.dev/tools/camera/) search site, and hands you the link to the results.
The keyboard's chat has the same menu, plus the clipboard's picture and your newest screenshot: see [The + menu](#the--menu).
### Sending files
**A file**, in the **+** menu, opens the [file manager](https://wmkeyboard.pages.dev/tools/files/) on the keyboard and the system file picker in the app or in the Play Store build. Up to four files go with one message, each shown as a chip with its name.
- **A text file** (plain text, Markdown, CSV, JSON, source code and the like) is read when you pick it, and its text goes with your message, marked off with the file's name, to any model. Up to 20,000 characters of it are kept; the rest is cut.
- **A recording, a video or a PDF** is copied into the chat, up to 14 MB. Gemini is sent the file itself with your newest message. Every other model is told a file of that name and type is there, but can't open it.
- **A picture** picked this way joins the message's pictures, as if you'd picked it from the gallery.
Anything else, such as a ZIP or an app, is refused with a line saying why.
## The Settings assistant
The **Settings assistant** is a chat about the keyboard itself. Open it from the assistant button at the top of the settings search screen, which carries over whatever you had typed into search, or from Tools / AI writing tools / Settings assistant. Describe what you want in your own words, such as "I keep triggering long presses" or "set up a mode for typing codes", or tap one of the three questions an empty conversation suggests. They're drawn at random from a list of common ones each time you open it, and the shuffle button deals three more. It searches the same index the search screen uses and explains the settings it finds, with formatting, lists and links drawn as they are in the chat.
It never takes you anywhere on its own. A setting it names in its answer is a link: tap it and a sheet slides up with the row's icon, what it does, the path of screens down to it and, for the settings it can read, the value it has now. A row it wants you to look at gets the same card in the conversation. **Open** on either takes you there, with the row flashing on its screen as a search result does, and back brings you to the conversation where you left it. It only ever names and links rows that exist; a link to a row it never searched for reads as plain text, and a row it makes up is refused.
For a question a settings row can't answer, such as which dictionary files Chinese can import, it searches [this documentation](https://wmkeyboard.pages.dev/reference/ai-access/) and answers from the sections it finds, with links to them. It only sends the search words.
For a short list of common settings it can also propose a change: autocorrect, suggestions, automatic capitals, the automatic space after punctuation and after a picked suggestion, glide typing, two-thumb typing, the number row, key vibration, key sound, typing speed on the spacebar and the long-press delay. A proposal is a card in the conversation with the setting's current and proposed value, and nothing changes until you press **Apply**. Apply checks the saved setting again first, and if it has changed since the proposal was made, it refuses and asks you to ask again. If the setting can't be saved, the card says so and keeps its **Apply**. Each proposal applies once. **Undo** on an applied card puts the old value back, unless you have changed the setting by hand in the meantime, in which case it leaves your change alone. Anything else it can only open, for you to change yourself.
It runs on any model the [AI chat](#picking-the-model) can use: a cloud or self-hosted provider you have set up under AI, or an on-device model you have downloaded. The one answering is shown under the title; tap it to pick another, or to set up a new provider. Like the chat's model chips, that choice is the assistant's alone and leaves the keyboard's own provider as it is, and it lasts until the app is closed. Until you pick, it prefers the keyboard's own provider when that is a cloud or self-hosted one, then the first of those, and only then an on-device model, since a cloud model calls the assistant's tools far more dependably.
On an on-device model the assistant searches the settings for your question itself before the model starts, so the model has the matching rows in front of it even if it never asks for a search, and it skips the documentation search. If its answer still names no setting, the closest match is shown as a card anyway. Answers are slower than on a cloud model, since each one is worked out on the phone, but nothing leaves it.
Don't want it? Turn off AI / Offer the Settings assistant. The button on the search screen, both of its rows and its search result go, and it sends nothing anywhere. A question it was still answering is stopped, and its answer never arrives. Turn it back on from the same row, which search still finds. Your providers and keys stay as they are.
The conversation is kept only while the app is running and is never saved. With a cloud or self-hosted provider, an empty conversation names it and says what goes to it. What it sends to the provider is your messages, its own answers, the setting titles, places and the few values above, and any documentation it looked up; no key, address or other secret is readable through it, and the app lock covers it like the rest of AI.
## Chatting over another app
Two ways in open the chat as a sheet over whatever app you're in, with the app still showing above it. The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen)'s **Ask AI about this screen** brings the text on screen along when its screen reading is on. **Ask AI** in any app's text selection menu brings the selected text. Either way it waits above the message box, where **View** shows all of it and **Delete** drops it, and the first message you send carries it. The conversation lands in the same list as the rest.
If you don't want **Ask AI** in the selection menu, turn off **Ask AI in the selection menu** in the [AI tool's options](https://wmkeyboard.pages.dev/tools/ai/#options). Turning the AI tool itself off takes it out too. Either way **Look up** and the other apps' entries move up into the space it leaves.
## Using it as the phone's assistant
Tools / AI writing tools / Use as the phone's assistant
WM Keyboard can be the app Android opens for the assistant gesture: a long press on the home or power button, a swipe up from a bottom corner, or a headset's assistant button, whichever your phone uses. Turn on **Use as the phone's assistant** on the AI tool's page, then pick WM Keyboard under **Choose the phone's assistant**, the row that appears under it. That opens Android's own page, usually **Default apps › Digital assistant app**. The switch alone changes nothing; until you pick WM Keyboard there, the phone keeps the assistant it had. It is off by default, and it goes with the AI tool: turn the tool off and WM Keyboard leaves the list.
The gesture then opens a short menu at the bottom of the screen, over the app you were in:
- **Ask about this screen** opens the chat with the screen's text attached, as the floating button's **Ask AI about this screen** does.
- **Explain this screen** and **Translate this screen** do the same with a question already in the message box ("Explain what is on this screen.", or "Translate this into" your phone's language). Change it or send it as it is.
- **Ask by voice** listens with your [voice typing](https://wmkeyboard.pages.dev/tools/voice/) engines, then opens the chat with what you said in the message box, and the screen attached when there is one.
- **New chat** opens the chat with nothing attached.
Every item ends in the same chat sheet as the other ways in, with the AI tool's models, its [tools](https://wmkeyboard.pages.dev/tools/ai/) and its history, so a question that needs a tool (set a timer, add a calendar event, look something up) works the way it does anywhere else in the chat. Nothing is sent until you send it.
The three screen items only appear when Android hands the assistant the screen's text, which it does when **Use text from screen** is on in its assistant settings (behind the gear next to **Digital assistant app**). Without it, a line under the menu says so, with a button to that page. Password fields are never read, and at most 8,000 characters come along.
Android gives every assistant a speech recognizer of its own and makes it the phone's while the assistant is picked. WM Keyboard's is the one [Voice input for other apps](https://wmkeyboard.pages.dev/tools/voice/#voice-input-for-other-apps) uses, so while WM Keyboard is the assistant, apps that ask Android's speech recognizer are heard by your voice typing engines too. With **Phone's recognizer** as the engine, their requests go on to the recognizer you had before. Turning on the assistant doesn't put WM Keyboard's voice input window in front of other apps' microphone buttons; that is still **Voice input for other apps**' own switch.
## Modes and answer formats
A message can ask for more than an answer. Each message goes out in a **mode**, and its answer comes back in a **format**. Both stay picked from one message to the next until you change them, on the keyboard and in the app alike.
- **On the keyboard**, the button with the sliders in the message box opens a row of modes and a row of formats over it. While either is something other than plain chat, the button wears the mode's icon and a line beside it names what's picked, so a research run never starts by surprise.
- **In the app**, the message box has a **Mode** chip and a **Format** chip next to **Reasoning**; each opens a menu. The chips scroll sideways on a narrow screen.
| Mode | What it does | Format it uses on Auto |
|---|---|---|
| **Chat** | The chat as it always was | Whatever the model writes |
| **Deep research** | Plans sub-questions, searches the web, reads pages and writes a report with numbered sources. See [Deep research](#deep-research) | Markdown report |
| **Summarize** | Sums up what you give it. Links in your message (up to three) are read first, so every model gets the pages, on-device ones included; quoted text and files are summed up as they are | Bullet points |
| **Compare** | Compares the things you name point by point and ends with a verdict when there is one. A model with web search looks up facts that may have changed | Table |
The formats are **Auto format**, **Plain text**, **Markdown**, **Bullet points**, **Table**, **JSON** and **Code**. Auto leaves the shape to the mode. Any other format is an instruction added to your message, not to the model's standing prompt, so it applies to that message and the answers that follow it, and it doesn't throw away an on-device model's session. Plain text is the one to pick when the answer is going straight into a field.
**Edit** on your own message brings its mode and format back with it, and **Retry** and **Write again** run it the way it first ran.
### Deep research
Deep research answers a question from the web the way a research agent does, in steps you can watch. It needs two things: a cloud or server model that can call tools (any provider except **On your device** and Brave), and a [web search provider](https://wmkeyboard.pages.dev/tools/search/) (SearXNG, Tavily or Brave). With either missing, a line above the message box says which, and the send button waits.
1. **Plan.** The model breaks your question into sub-questions, from the general (what it is, why it matters) to the specific (details, numbers, recent changes), and gives each one to three searches. The plan appears as a checklist at the top of the answer.
2. **Gather.** One sub-question at a time, its searches run and the best results are read, no more than two pages from one site. Each search and each page is a step on the chain, as a tool call is, and its sub-question is ticked once it's covered. A page that can't be read, or is a PDF or a video, is skipped.
3. **Look again.** On Standard and Deep, the model reads short notes from the pages so far and either names what is still missing as new searches, which join the checklist, or says it has enough.
4. **Write.** The report streams in: a short direct answer, a section for each sub-question, a conclusion. Every fact carries the number of the source it came from, like `[3]`. The list of **Sources** under the report is added by the keyboard, not written by the model, and holds only the pages the report cites, each a link. A source the report never cited isn't listed.
The model doing every step is the one picked in the chat. The plan and the look-again ask it not to reason, to save time; the report follows the **Reasoning** switch.
Tools / AI writing tools / Deep research depth sets how far a report goes:
| Depth | Sub-questions | Searches in the first round | Looks again | Pages read |
|---|---|---|---|---|
| Quick | up to 3 | 3 | never | 4 |
| Standard (default) | up to 4 | 6 | once | 8 |
| Deep | up to 6 | 9 | twice | 14 |
Each page is a request to its site, and up to 6,000 characters of it go to the model, with about 48,000 characters of page text in all for the report. Deeper reports take longer and cost more tokens. A Standard report on a fast cloud model takes about half a minute.
Turn on Tools / AI writing tools / Review the research plan first to see the plan before anything is searched. A card under the plan asks **Research these questions?** with **Start** and **Cancel**. Cancel ends the run with a **Retry**; rephrase the question if the plan missed the point. With the setting off, the plan runs as soon as it's made, and **Stop** ends a run at its next step, keeping the steps so far.
[Data Saver](https://wmkeyboard.pages.dev/privacy/data-saver/) covers a research run twice: as a cloud AI request and as a web search. When it would hold either, the run is held, and **Retry** is the yes. When data saving is on and allows the run, it reads half as many pages.
### Suggested next questions
Under the newest answer from a cloud or server model, up to three chips suggest what you might ask next. A tap puts the question in the message box to send or change; nothing is sent until you do. The model writes them at the end of the same answer, so they cost a few dozen tokens and no extra request, and they're taken out of the answer before it's shown, copied or inserted. On-device models are never asked for them. Turn them off with Tools / AI writing tools / Suggest follow-up questions.
### Sharing an answer
The **Share** icon under an answer hands it to another app. In the app it offers a **Markdown file**, a **PDF** or the **Text**; on the keyboard it shares the Markdown file. The file opens with the chat's title and your question, then the answer as written, sources and all. The PDF is laid out on A4 pages, with headings, lists, code and tables set apart. Files are made in the app's cache and shared from there.
## Picking the model
A scrolling row of chips above the transcript lists everything you can talk to right now: models you've downloaded on-device first (when **On your device** is on the AI providers list), then every row of [that list](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs) that is on and set up. Set up means the thing it needs is filled in: an API key for the cloud services, an address for Ollama or LM Studio, and both an address and a model name for an OpenAI-compatible service. Two rows of one service show their nicknames. With fewer than two to choose from, the row doesn't appear at all.
tools/ai-chat-conversation — Model chips over a conversation, with an answer streaming in.
Two things worth knowing about that row:
- **It doesn't touch the keyboard's own setting.** Chatting to Gemini while the AI tool is set to Claude leaves the tool on Claude. The chip is the chat's choice and nothing else reads it.
- **It's frozen while an answer is coming in.** The chips grey out until the answer lands or you stop it, since swapping models halfway through a reply has no sensible meaning.
Your pick is remembered for next time, and each conversation also remembers the last model that answered in it.
## Sending, stopping, retrying
Type in the box and hit send. The app's message box is laid out like the keyboard's: what you write at the top, with the **Reasoning** switch, **+** and send along its bottom edge. While the answer streams, the send button becomes **Stop**.
### The Reasoning switch
**Reasoning**, at the bottom left of the message box, turns a model's reasoning on or off for your next messages. It's one setting for both chats, on by default, and it only shows for a model it does something for:
| Model | On | Off |
|---|---|---|
| Claude | Claude thinks as much as it decides the question needs, and a summary of its thinking shows in the [Reasoning step](#reasoning-and-tool-calls). Models before Claude 4.6 get a thinking budget of half the maximum response length | No thinking. Claude Sonnet 5.5 is set to think only between tool calls, its own way of turning thinking off. Claude Opus 5.5, Fable and Mythos can't stop thinking, so they're set to the lowest effort |
| Gemini | Gemini's thoughts come back and show in the Reasoning step | Thinking off on Gemini 2.5 Flash and Flash-Lite. Gemini 2.5 Pro and Gemini 3 can't stop thinking, so they're set to the least they allow |
| Ollama | The model's own default, which for a thinking model is to think | No thinking |
| OpenAI reasoning models (GPT-5, GPT-6, o-series) | The model's own default | Low reasoning effort, the least every one of them accepts |
| On-device reasoning models (Qwen 3 and the like) | The model reasons, as before | `/no_think` is added to your message, which those models read as "answer straight away" |
Other providers have no field for it that they all agree on, so the switch isn't there for them, and neither is it for models with no reasoning to switch: Claude 3 models before 3.7, and Gemini 1.5 and 2.0.
If a reasoning model spends its whole answer reasoning and writes no reply, usually because it reached the maximum response length, the turn shows as failed with a **Retry**, and what it reasoned is still in its Reasoning step.
- **Stop** keeps what arrived. The partial answer stays in the transcript, labelled "Stopped", rather than vanishing as if you'd never asked. A LiteRT-LM model's generation is a native call that can't truly be interrupted. It finishes quietly in the background instead, and the model's own memory of the conversation is kept in step with what you can see. A [GGUF model](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp) stops at once, and remembers exactly the partial answer.
- **A failed turn** shows the error in its own bubble with a **Retry** button. Only the newest failed message gets one, because retrying something from the middle of a transcript would answer out of order. Retry re-sends the message the failure was answering, so nothing is duplicated.
- **Leaving doesn't cancel anything.** Navigate away mid-answer and the generation carries on. Come back and it's either still streaming or already in the transcript.
Answers run the full width of the screen with no bubble around them, and your own messages sit in bubbles on the right, as on the keyboard. Every message has a **Copy** icon under it, and the text itself is selectable, which the keyboard's version can't offer. The newest answer adds **Write again**, and your own newest message adds **Edit**, both exactly as on the keyboard. Answers render the same formatting on both, and a code block has a **Copy** of its own. There's no Insert here, because there's no field behind this screen to insert into.
A Play Store build adds a **Report** icon, a flag, under every finished answer. It follows the same route the panel offers under [Reporting a result](https://wmkeyboard.pages.dev/tools/ai/#reporting-a-result): a draft opens in your own mail app holding the provider, the model, the message the answer replied to, and the answer itself, with a blank line at the top for what went wrong. Nothing is uploaded when you tap it, so a report only exists once you send the mail. Your own messages carry no Report button, and neither does a failed turn, because neither one is a generated answer. With no mail app installed, a message says so instead. GitHub and F-Droid builds show no Report button here at all, since Play's AI-generated-content policy is the only reason for it. The keyboard panel's own Report button has no such restriction.
### Reasoning and tool calls
What happened before an answer sits over it as a short chain of steps, joined by a line that runs down into the answer. Each step is folded to one line until you tap it. This works the same on the keyboard and in the app.
- **Reasoning** is what a reasoning model thought before it answered. While it's thinking the step reads **Reasoning…** with a spinner. Tap it to read the reasoning, drawn with its formatting, maths included, and dimmed so it never reads as the answer. A model that reasons again after each tool call has all of it gathered here.
- **A tool call** gets a step of its own, one for each time the model used a tool (see [Letting the model look things up](https://wmkeyboard.pages.dev/tools/ai/#letting-the-model-look-things-up)). Its line says what it did, such as **Searched the web for "…"** or **Reading example.com…**, over the tool's own name, such as `web_search call`. Tap it to see the arguments the model wrote and what came back: for a search or a page, the results by title and address, where a tap opens one in your browser and a long press copies the address; for any other tool, the text the model was handed. A tap on the arguments or the output copies them. A call that failed says why.
The steps are saved with the answer, so they're still there when you come back to the chat, and they stay even on a turn that failed or that you stopped. A lookup cut off by **Stop** is marked "Stopped". A call that has to ask first shows its question under its step, open, whether or not the step is.
The **Show model reasoning** setting is for the panel's actions; the chat always keeps reasoning, folded.
An on-device model gets a live session per conversation, seeded with the saved transcript, so it keeps the context of what you've already said. A GGUF model is given the whole conversation in its own chat template on every turn, and only the new message costs time, because the turns before it are still in its memory. When a conversation outgrows the context, its oldest exchanges are left out first. Switching conversation, model, [compute backend or context size](https://wmkeyboard.pages.dev/tools/ai/#local-models) closes that session and opens a fresh one, seeded the same way.
## Nothing set up yet
With no downloaded model and no configured provider, the chat screen says **Nothing to chat with yet** and offers one button back to the AI tool settings. That's the same setup you'd do for the keyboard panel: see [Choosing where it runs](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs).
On the F-Droid build the on-device models are the [GGUF ones](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp) only: the LiteRT-LM list needs Google's runtime, which that build leaves out. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're not sure which one you have.
## Memory
A chat can remember things about you for the chats after it. When you mention something that will stay true, like where you live or how you like answers, the model saves it as one short fact, and a line under the answer says **Saved to memory** with an **Undo** beside it. Ask it to remember or forget something and it does. Every saved fact is on AI / Memory, where you can edit it or delete it.
An on-device model reads and saves memory while it's on. A cloud or self-hosted service only does once you turn on **Use with cloud models** there. A chat started in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) never touches memory. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) for what's saved, what's refused and where it's kept.
## What's kept, and where
Conversations live in one file in the app's private storage, in the same folder as the [AI history](https://wmkeyboard.pages.dev/tools/ai/#history). It shows up as **AI chats** on the storage screen under About / Storage, so you can at least see what it costs you. [Your data](https://wmkeyboard.pages.dev/privacy/data/) covers what else is in there.
Two rows decide whether that file exists at all. Both sit on the History screen, under Tools / AI writing tools / History, in a group headed **Chats**:
- **Keep your conversations** is on by default. Turn it off and chats live only until you close the app, and whatever is already saved is deleted with it. That's the same contract the AI history switch has.
- **Delete all chats** clears every saved conversation after a confirmation, without turning the feature off.
Three caps keep the file from growing without limit:
| What | Cap | What happens past it |
|---|---|---|
| Conversations | 50 | The one you touched longest ago drops off |
| Messages per conversation | 200 | The oldest messages drop off the front |
| Characters per message | 20,000 | The rest is cut |
Text you attached from a field is saved with its message, under the same 20,000-character cap, because the conversation wouldn't make sense to the model without it on the next turn.
Pictures are kept as files next to the conversations, scaled down to 1,280 pixels on the long side first, and are deleted when nothing refers to them any more: when the conversation is deleted, for example. With **Keep your conversations** off they go in the app's cache instead. Recordings, videos and PDFs you sent are kept the same way, unscaled; a text file's text is saved inside its message.
Deleting a conversation from the list removes it for good. There's no undo, which is why the dialog names the chat first.
## What leaves your phone
Exactly what the AI tool sends, and only when you hit send:
- **An on-device model** sends nothing. The whole conversation stays on the phone.
- **Any other provider** gets the conversation so far, because that's what makes a reply follow on from the last one. Your saved [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) goes with it only if you've turned on **Use with cloud models**. Pictures you sent go with your newest message, to a provider that takes them. That includes any field text you attached with the paperclip, and only the text you attached: the keyboard's chat never reads the field on its own. A fixed instruction rides along with it: answer in plain text, and keep it as short as the question allows. Your API keys stay on the device.
- **Deep research and Summarize** also go to the web. Deep research sends the searches its plan made to your [search provider](https://wmkeyboard.pages.dev/tools/search/), then fetches each page it reads straight from its site, and the pages' text goes to your AI provider with your question. Summarize fetches the links in your message the same way. Nothing goes to a page but an ordinary request for it.
- **The assistant's menu** reads the screen's text only when Android sends it, and keeps it on the phone until you send a message that carries it. Its **Ask by voice** hears with your voice typing engine, which keeps the audio on the phone or sends it where that engine sends it.
- **The transcript itself** goes nowhere. It's deliberately left out of settings backups, the same as the [AI history](https://wmkeyboard.pages.dev/tools/ai/#history) and for the same reason: your own conversations shouldn't ride along in a file you export and mail to yourself. See [Your data](https://wmkeyboard.pages.dev/privacy/data/).
## Details & edge cases
- **Max response length applies here too.** For a cloud or self-hosted model, the AI tool's ceiling bounds a chat reply the same way it bounds a panel result, reasoning-model multiplier included. See [About "Provider maximum"](https://wmkeyboard.pages.dev/tools/ai/#about-provider-maximum). On-device, the **Context size** row is what bounds it, exactly as on the panel.
- **The chat prompt isn't one of your actions.** The panel's [action prompts](https://wmkeyboard.pages.dev/tools/ai/#your-own-actions) are for transforming text in a field, which is a different job. Chat has its own fixed prompt and nothing on this screen edits it.
- **Nothing here is written to the AI history.** That log records panel runs. A conversation is only in the chat file.
- **The launcher shortcut is one of four.** Long-pressing the app icon offers Tools, Languages, AI chat and Themes, and nothing else. The `wmkeyboard://settings/` scheme behind those four reaches three more screens (Typing, Appearance and Search) that no shortcut points at. See [Deep links & launcher shortcuts](https://wmkeyboard.pages.dev/reference/deep-links/).
- **Deleting a chat mid-answer is handled.** If a generation finishes for a conversation you've already deleted, the answer is dropped rather than resurrecting the row.
- **Reasoning is saved apart from the answer.** It's kept with the message for its folded step, under the same 20,000-character cap, but never sent back to the model on later turns, and **Insert** and **Copy** take the answer alone.
- **Tool calls are saved short.** A saved answer keeps each call's arguments and the first 4,000 characters of what came back, the titles and addresses of search results, and why a call failed. A follow-up question doesn't see any of it again: the model only has its own earlier answer to go on.
Related: [AI tools](https://wmkeyboard.pages.dev/tools/ai/) covers the providers, the on-device model catalog, the panel's actions, and the history.
---
# AI memory
URL: https://wmkeyboard.pages.dev/tools/ai-memory/
Section: Tools
Summary: Short facts about you that the AI saves in a chat and uses again in later chats and AI actions. You can read, edit and delete every one, and they stay on your phone.
AI / Memory
Without memory, every chat starts from nothing: you tell the AI where you live, what you do and how you like answers, and the next chat has forgotten all of it. AI memory keeps those facts. When you mention something about yourself that will still be true next week, the model in the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) can save it as one short line, such as "Lives in Dhaka" or "Prefers short answers". Later chats are handed the saved lines, and so are the keyboard's [AI actions](https://wmkeyboard.pages.dev/tools/ai/) and [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/).
Every fact is on one screen, where you can read it, change it or delete it. Nothing in memory comes from anywhere else, so that list is everything the AI remembers about you.
## How a fact gets saved
The model decides, while it answers. It's told to save only what is about you and will stay true: your name, where you live, your work, your preferences, the people and projects you mention often. It also saves what you ask it to remember ("remember that I'm vegetarian"), and deletes what you ask it to forget. If a saved fact has changed, it rewrites that one instead of adding a second.
A save shows under the answer as a line saying **Saved to memory** with the fact, or **Memory updated** when an old fact was reworded. **Undo** on that line takes it back: a new fact is deleted, and a reworded one gets its old words back. The line then says **Taken back**. It works the same on the keyboard's chat and in the app.
Saving the same fact twice doesn't make two lines. A fact that says no more than one already saved is left alone, and one that says more ("likes green tea" after "likes tea") replaces the old wording under the same entry.
Some things are never kept, whatever the model tries. A line that looks like a password with its value, a PIN, a one-time code, a payment card number, or an API key or token is refused, and the model is told why. So is anything longer than 300 characters, since a memory is one fact and not a note.
## Which models get it
AI / Memory / Use with cloud models
- **A model on your phone** always gets memory while **Remember things about me** is on. Nothing leaves the phone.
- **A cloud or self-hosted service** gets it only when you also turn on **Use with cloud models**, which is off to start with. With it on, the facts picked for a request go to that service with the request, and its chats can save new facts. With it off, a chat with that service neither sees nor saves memory.
The facts are the most personal thing the keyboard keeps about you, gathered over many chats. Sending them along with every request is a choice to make yourself, which is why the cloud switch starts off. Memory itself starts on, because until you flip that second switch it only ever reaches a model running on your phone.
## What a chat or an action is handed
While you have up to 30 facts, and they're short, every request gets all of them. Past that, each request gets the 20 that fit it best: the ones closest in meaning to what you asked, when [search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) is on and its model is downloaded, or the ones sharing words with it otherwise, then the most recently saved.
A chat picks its facts once, on its first message, and keeps them for the rest of the conversation. That keeps an on-device model's session and a service's prompt cache from starting over on every message. A change you make on the memory screen reaches the next message of a chat already open.
AI actions and AI reply read memory but never save to it. The text they work on is the field's, which may be someone else's message or a document, so nothing in it is taken as a fact about you. They're also told to use the facts only where the task calls for them, so **Fix grammar** doesn't sign your name to the text.
## Where it doesn't apply
- **Incognito.** A field in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), from the setting or from an app asking for it, neither reads nor saves memory. A chat started in incognito is a private one, and it never touches memory either, even after incognito ends.
- **Before you unlock.** Memory is kept in storage that only opens once you've unlocked the phone after it started. Until then the AI has no memory at all.
- **Password fields.** The AI tool doesn't run in one.
## The memory screen
AI / Memory
- **Remember things about me** turns memory on or off. Off, no chat or action reads or saves it. The facts you already have stay until you delete them.
- **Use with cloud models** is described above.
- **Add memory** opens a box for a fact of your own, with an optional category such as "work" or "food". The same checks apply as to the model's saves, so a password is refused here too.
- Each saved fact is a row, newest first, with where it came from (**Added by you** or **From a chat**) and when it last changed. Tap a row to edit it. Swipe it left for **Edit** and **Delete**, or use the delete button on the row.
- **Delete all memories** clears the list after asking. The [app lock](https://wmkeyboard.pages.dev/reference/settings/app-lock/) can put a fingerprint in front of it, and in front of the whole screen.
## Where it's kept
The facts are one file in the app's private storage. They go nowhere unless you send them:
- **Backups** carry them only when you tick **AI memory** in what goes in a backup, which is off to start with. See [Backup & restore](https://wmkeyboard.pages.dev/privacy/backup/).
- **Sync** never carries them. AI memory isn't one of the sections a sync can pick.
- **Search by meaning** keeps a small index of the facts next to its others, so it can find the closest ones. **Delete all memories** deletes it too.
## Limits
| What | Limit |
|---|---|
| Facts kept | 500. Past that the model is told memory is full, and a fact you add is refused. |
| Length of one fact | 300 characters |
| Facts sent whole | Up to 30, and 3,000 characters together |
| Facts sent past that | The 20 that fit the request best |
---
# AI reply
URL: https://wmkeyboard.pages.dev/tools/ai-reply/
Section: Tools
Summary: Draft an answer to a message somebody sent you: paste it, type it, or pick a screenshot of the chat, then choose a tone.
The [AI tool](https://wmkeyboard.pages.dev/tools/ai/) works on text you have already written. **AI reply** works the other way round: it reads what somebody else sent you and drafts answers for you to pick from. It is for the moments when you know what you want to say and not how to say it, or when you do not know what to say at all.
Nothing is ever sent on your behalf. The tool writes drafts; tapping one puts it in the message box, exactly as if you had typed it, and you still press send yourself.
## Getting their message in
A keyboard can read the box you are typing in. It cannot read the conversation above it, because those messages belong to the chat app, and no keyboard can reach into them. So the first thing the panel asks for is their message, and there are four ways to hand it over:
- **Copy it.** Long-press their message in the chat app and copy it. Anything you copied in the two minutes before opening the panel is already there when it opens, and anything you copy while it's open is added as you copy it. This works whether or not the clipboard keeps a history; without one, only the latest copy is picked up on opening. **Paste copied** adds the most recent copy by hand.
- **Screenshot** shows the recent screenshots on your phone as a grid of pictures, newest first, each marked with how long ago it was taken. Tap the one of the chat.
- **Type** opens a box on the keyboard. Type or paste the message, press Enter, and it is added.
- **Use the selection** works in the few apps that let you select the conversation text itself.
With Shizuku or root there is a fifth way, in the F-Droid and GitHub builds only. Turn on Privacy / Permissions / Shizuku or root / AI reply reads the chat and the messages on screen are added as the panel opens, with bubbles on the right marked as yours and the rest as theirs. See [Shizuku or root](https://wmkeyboard.pages.dev/reference/settings/permissions/#shizuku-or-root).
### Several messages at once
A reply usually needs more than one line of the conversation. While the reply panel is open, anything you copy is added to it automatically, so you can switch to the chat app, copy three messages one after another, come back, and all three are waiting. Screenshots can be picked up the same way; see below.
The gathered messages are drawn like the chat they came from: theirs on the left, yours on the right in the theme's accent colour. Each carries a **Them** or **Me** label; tap the label to flip it. This matters more than it looks: nothing the keyboard can see says who wrote a copied line, so a message of your own that is read as theirs produces a reply to yourself.
Tap a message's text to edit it in the box on the keyboard; **Save** puts it back, and saving it empty removes it. Swipe a message sideways, or tap its **×**, to drop it, or tap **Clear** to start over. Between two messages sits a small **Join** button: when one message arrived as two pieces, tap it to make them one again.
A message with no real word in it is outlined in red and marked **No real words?**. That is usually what reading a screenshot makes of a sticker, a profile picture or a line cut off at the edge. Only words in Latin letters are judged, against the dictionaries your keyboard has loaded. Messages in other scripts, emoji and numbers are never marked. **Remove unclear** at the front of the row clears them all at once.
A screenshot sent as a picture has no **Them** or **Me** label. It holds both sides of the conversation, and the model reads who said what from where each bubble sits.
### Screenshots
A screenshot is the fastest way to hand over a whole conversation, because the bubbles' own left-and-right alignment says who said what.
How it is read depends on your AI provider, set under Tools / AI reply / How screenshots are read:
- **Automatic** (the default) sends the picture to providers that accept pictures (Claude, OpenAI, Gemini, xAI, Ollama, LM Studio and OpenAI-compatible servers) and reads it on the device otherwise. With an [on-device model](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs), Gemma 4 E2B and E4B look at the picture themselves, without it leaving the phone, and so do the GGUF models [Qwen 2.5 VL 3B and SmolVLM 500M](https://wmkeyboard.pages.dev/tools/ai/#gguf-models-on-llamacpp). A model you imported is tried the same way (a GGUF one sees only with its `mmproj` file beside it); if it turns out not to read pictures, the screenshot is read on the phone instead and the drafts are written from that, with a note in the panel saying so.
- **Send the picture** always sends it. The model reads the alignment itself, so no labelling is needed.
- **Read on this device** never sends the picture. The text is read on the phone and arrives as separate messages you can relabel. google: the on-device reader is not in the F-Droid build.
Once a screenshot is in the conversation, a **Screenshot goes as** row above the tones switches it between **Picture** and **Text** for this reply. Text reads it on the phone straight away, so you can check and relabel every line. Picture puts the lines read from that screenshot back together into the picture. Picture only shows when the chosen model can take one. Your choice is remembered as the setting above: Picture as **Automatic**, so switching later to a model that can't read pictures falls back to text instead of failing, and Text as **Read on this device**. If you switch to a model that can't read pictures after adding one, the screenshot is read on the phone when you pick a tone.
When a screenshot is read on the device, the parts that are not the conversation are left out: the status bar and the chat app's top bar (the contact's name, "Active now"), message times and dates, "Seen" and "Delivered" marks, the message box's own placeholder, and the app's own notices inside the thread, such as "Replied to you", reaction banners, "This message was deleted", "Photo" and "Voice message" labels, call records, unread and encryption notices, and group events. A screenshot taken while the keyboard was on screen has the keyboard in it, and everything from the keyboard's top edge down is skipped. The keyboard remembers where its top edge was at each moment, so a screenshot taken with the plain keyboard up is cut at the plain keyboard's top, even when you pick it later from the taller reply panel. Lines of one bubble that wraps are put back together; when that misses one, use **Join**.
The keyboard cannot take a screenshot for you: capturing the screen needs a system permission dialog, and an input method that opens one loses the box it was typing into. Take it the usual way, with the hardware buttons, and pick it with **Screenshot**. To skip that step, turn on Tools / AI reply / Add screenshots automatically. It's off by default. With it on, a screenshot taken while the panel is open is added on its own, and so is the newest one from the two minutes before you open it. This works whatever **Screenshots in the clipboard** under Clipboard is set to, and a screenshot caught for a reply is not added to the clipboard unless that setting is on. It needs the same permission to read your images as the **Screenshot** button.
Reading screenshots from the gallery needs permission to read your images. Tapping **Screenshot** without it asks for it straight away, and the grid opens once you allow it. If you said no, the panel shows an **Allow** button to ask again.
## Picking a tone
Once there is something to reply to, the tones appear at the bottom of the panel under **Next: pick how to reply**, in one row you can swipe sideways. They stay there however long the conversation gets, and the conversation above them scrolls to the newest message as you add one. One tap writes the drafts:
| Tone | What it does |
| --- | --- |
| **Casual** | A relaxed text, the way friends message each other |
| **Quick** | Two to six words, or a single emoji when it fits |
| **Keep it going** | Answers, then asks one easy question back so the chat continues |
| **Sounds good** | Says yes and confirms any plan, time or place |
| **Say no** | Declines clearly and kindly, without repeated apologies |
| **Funny** | Playful banter that still answers what they said |
| **Excited** | Hyped or congratulating, the way a friend texts back |
| **Supportive** | Shows you understood how they feel, without unasked-for advice |
| **Apologise** | A short, sincere apology without excuses |
| **Polite** | Courteous and professional, for someone you do not know well |
| **Flirty** | Warm and lightly flirty |
| **Custom…** | Asks what the reply should say, then writes it in that direction |
**Custom…** turns the box on the keyboard into an instruction box: type something like "say I'm running late, suggest 8:30" and tap **Write**.
Tones are editable, and you can write your own under Tools / AI reply / Tones. Reorder them, turn some off, change what one does, or add a new one with **New tone**. A tone you write can also ask for its instruction each time, the way **Custom…** does. Changing a shipped tone keeps a **Reset** to bring the original back.
The drafts take the place of the conversation, and each one appears as soon as the model has finished writing it, rather than all three at the end. Tap one to put it in the message box.
### Starting from your own words
If you have already typed something in the message box when you open the panel, such as "cant fri, busy", the drafts say that, in the tone you pick, instead of inventing a different answer. The panel shows **From your draft** above the tones so you know it is being used. The draft you pick replaces what you typed. If you typed more in the meantime, it is added at the cursor instead and your text is left alone. The tone they were written in stays highlighted; tap another under **Try another tone** for a different set, or **Messages** to go back and fix a message first.
## What it writes
Under Tools / AI reply:
- **Replies per run**: how many alternatives one run writes, up to five. A small on-device model is asked for one, because asked for three it writes the same reply three times. The recommended on-device models, Gemma 4 E2B and E4B, get the full number.
- **Length**: short, medium or long.
- **Emoji**: match the other person's use of them, allow them, or avoid them entirely.
- **About you**: anything the drafts should know about how you write. This is the single setting that most changes whether a reply sounds like you.
- **Reply language**: blank follows the conversation, including its script, so Bengali written in Latin letters is answered the same way.
- **Conversation sent**: how much of the gathered conversation one run sends. When it will not all fit, the oldest messages go first.
The provider, API key and model are the AI tool's own, under Tools / AI. There is only one set of them. When more than one is set up, the panel shows the same **Model** row as the AI tool at the top, with your downloaded on-device models and every cloud provider you have a key for. Picking one there picks it for the AI tool too. The row is hidden while drafts are being written, and turns off with **Model picker on the panel** under Tools / AI.
## Clearing up afterwards
Feeding this tool leaves other people's messages in your clipboard history and screenshots of your conversations in your gallery. So when you tap a draft, the copies and screenshots that reply was built from are taken back out again. It is on by default, it is a checkbox in the panel if you want to keep one for a particular reply, and it is a setting under Tools / AI reply / Clear what it used.
Clipboard entries are the keyboard's own, so those always go. A screenshot in your gallery is yours: from Android 11 an app may only delete pictures it created itself, so Android shows its own dialog asking whether WM Keyboard may delete it. The reply is already in the message box by then, so answering the dialog loses nothing. On Android 10 and older, or when the dialog can't be shown, the panel says plainly that the screenshot is still in your gallery.
## Privacy
- Nothing leaves the device until you tap a tone. Gathering messages, labelling them and reading a screenshot on the device are all local.
- Their message is treated as data, never as instructions. A line in a conversation that tells the model to ignore its instructions is answered as part of the conversation, which is what someone sending it would deserve.
- The tool refuses to run in a password field.
- Drafted replies are not kept in the [AI history](https://wmkeyboard.pages.dev/tools/ai/#history), even when the history is on. A record of a rewrite is your own writing; a record of a reply is somebody else's message. Turn it on under Tools / AI reply / Keep drafted replies in the AI history if you want them kept.
- On a metered connection, [data saving](https://wmkeyboard.pages.dev/privacy/data-saver/) holds the request the same way it holds any other cloud AI run.
- Choosing an on-device model keeps the whole thing on the phone, screenshots included. Gemma 4, Qwen 2.5 VL and SmolVLM read the picture themselves; other on-device models get the text read off it on the phone.
- The draft you had started in the message box is sent along with the conversation, since the drafts are written from it.
---
# App launcher
URL: https://wmkeyboard.pages.dev/tools/app-launcher/
Section: Tools
Summary: Open any app, or a specific screen inside one, straight from the keyboard. Full screen, in a floating window, in split screen, or two at once as a pair.
The app launcher puts your app drawer inside the keyboard: a searchable grid of every launchable app, your work apps included, sorted into folders if you like, with the ones you pinned and the ones you opened recently in a row on top. Tap an app to jump to it. Hold one for a menu that opens it in a floating window or in split screen instead, pairs it with a second app, or takes you inside it to open a specific screen, like a settings page or a compose view, without going through the app's own navigation first.
It's handy mid-conversation: someone asks for a booking reference, you hop to the airline app, copy it, and the keyboard is still where you left it when you come back.
tools/app-launcher-grid — The app grid with a pinned row on top and search in the header.
## Opening the panel
Open **Apps** from the [toolbar, or the toolbox](https://wmkeyboard.pages.dev/tools/overview/) if you haven't pinned it. Typing `apps` or `launch` offers the tool on the suggestion strip too. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press J.
## The grid
- **Search** sits in the header. Tap it and the keys come back under a shortened panel, filtering live over app names and package names as you type.
- **Split screen pairs**, if you saved any, get their own row on top. See [split screen pairs](#split-screen-pairs) below.
- **Folders** come first in the grid, before the apps that are in none. Tap one to open it. See [folders](#folders) below.
- **Work apps** sit in the grid with a briefcase on their icon. See [work apps](#work-apps) below.
- **Pinned and recent apps** lead the grid in their own row. Pin and unpin from the menu a hold opens, from the grid or from the shortcut row alike. Recents track the last apps you opened from this tool, ten by default and adjustable from 4 to 20, and they never leave your device.
- **Tap** launches the app, the way **Open apps** in the options says: normally by default. The panel closes itself, since you are about to leave the app you were typing in anyway.
- **Hold** opens the app's menu, described next.
- **Hidden apps** stay out of the grid and the shortcut row. Hold an app, open its page from the menu, and tap the eye to hide it. Search still finds hidden apps, shown dimmed, so you can open one or show it again from its page.
## The hold menu
Hold any app, in the grid or in the shortcut row, and a small menu opens over the panel. Tap outside it to close it.
- **Open**: a normal, full-screen launch.
- **Open in a floating window**: opens the app in a floating window the way your phone's maker does it: ColorOS's floating window on OPPO, OnePlus and realme, the pop-up view on Samsung, the small window on Xiaomi and vivo, and so on. See [floating windows](#floating-windows-and-split-screen) for how the keyboard finds the way that works, and what happens on a phone with none.
- **Open in split screen**: puts the app beside the one you are typing in.
- **Pair in split screen with…**: starts a [pair](#split-screen-pairs). A banner asks for the second app, and your next tap picks it. Tap **Cancel**, or the first app again, to back out.
- **Pin app** or **Unpin app**.
- **Put on the toolbar** or **Take off the toolbar**: gives the app a button of its own on the keyboard's toolbar. See [Apps on the toolbar](#apps-on-the-toolbar).
- **Add to folder**, or **Take out of folder** for an app that is in one. See [folders](#folders).
- **Screens, hide and App info…**: the app's page, below. With **Open screens inside apps** off it reads **Hide and App info…**.
The two split screen items show wherever the keyboard has a way into split screen: Android 12L (API 32) or later, Huawei's own split screen, the [split screen helper](#split-screen-helper), or [Shizuku](#shizuku-and-root). They hide once the [check](#how-the-keyboard-finds-what-works) has found that none of them works on your phone, and they never show for a work app.
## Apps on the toolbar
The apps you open all the time can sit on the toolbar itself, one tap from the keys with no panel to open first. Hold the app in the panel and pick **Put on the toolbar**. Its icon joins the toolbar after your pinned tools and [folders](https://wmkeyboard.pages.dev/tools/overview/), in the panel's icon shape, with its name under it when the toolbar shows names. A tap opens the app the way **Open apps** below says, normally unless you picked a floating window or split screen. Hold the app in the panel again and pick **Take off the toolbar** to remove it.
The toolbar takes up to six apps, in the order you put them there, and says so when it's full. An app you uninstall drops off the toolbar on its own. To take every app off at once, use **Apps on the toolbar** in the options below. With **Tools in landscape** standing the tools in a [column down one side](https://wmkeyboard.pages.dev/typing/size-position/#what-landscape-starts-at), the apps follow them there in the same order.
## Folders
Folders group apps the way a home screen does: a "Banking" folder for the bank apps, a "Travel" one for the airline and the maps. The grid shows each folder as a tile with up to four of its apps' icons and its name under it, ahead of the loose apps. An app lives in one folder at most.
- **Make one**: hold an app, pick **Add to folder**, then **New folder**. The folder opens with its name box up and the keys under it; type a name and press Enter or the check. Leave it empty and the folder keeps the name "Folder".
- **Fill it**: hold another app, pick **Add to folder**, and tap the folder. An app already in a folder moves to the new one.
- **Take an app out**: hold it, in the folder or in the grid, and pick **Take out of folder**. A folder left with no apps goes away.
- **Rename, reorder or remove**: open the folder and use the bar above its apps. Tap the name to type a new one, the arrows move the folder one place earlier or later in the grid, and the bin removes it. Holding a folder's tile offers the same in a menu. Removing a folder puts its apps back in the grid; it never uninstalls or hides anything.
Back, on the panel or the system's, leaves a name box, then the folder, then the panel. Search ignores folders: it finds an app wherever you filed it.
### Folders from your home screen
The folders on your home screen belong to the home screen app, and Android gives no other app a way to read them, so the keyboard can't copy them over. What it can read is the category each app declares for itself on the Play Store, such as Games, Social or Maps. **Add to folder** → **Sort all apps by category** makes a folder per category, named the way your phone names it in your language, for every app that is in no folder yet. Apps you filed by hand stay where they are, a category joins a folder of the same name if you have one, and a category with a single app gets no folder of its own. Many apps declare no category at all; those stay loose. Categories need Android 8 or later.
Folders are kept on this device only. They name apps that are installed here, so they don't travel with [settings sync](https://wmkeyboard.pages.dev/reference/settings/backup/) the way your other choices do; they do go into backups.
## Work apps
If your phone has a work profile, its apps show in the grid next to your personal ones, each with a small briefcase on its icon. An app installed in both profiles shows twice, once per profile, and each copy has its own pin, hidden state, folder and recent entry. Tapping a work app opens it in the work profile.
When work apps are paused, from the quick settings tile or the work tab of your home screen, the work apps show dimmed. Tapping one asks Android to open it, and Android answers with its own "Turn on work apps?" prompt. Only the home screen app may switch the profile back on directly, so the keyboard leaves that decision to the system.
A work app opens normally or in a floating window. Split screen and pairs aren't offered for work apps, because the system call that opens an app in another profile has no way to ask for split screen. A work app's page lists the screens the work profile shows to launchers, and **App info** opens the work profile's own settings page for it.
The keyboard needs no extra permission for any of this. Android lets any app list and open the apps of the other profiles of the same person, through the same launcher list the home screen uses. Android 15's private space is the exception: only the default home screen app can see it, so its apps never show here. Turn **Work apps** off in the options to keep the grid to your personal apps.
## An app's page
The last item of the hold menu opens the app's page: the app's icon, name and package, a pin toggle, a hide toggle, an **App info** shortcut to the system settings page, and, with **Open screens inside apps** on, every screen (activity) the app declares.
Screens the app allows others to open are listed first. Tap one to open it directly. Android blocks the rest for anyone but the app itself, and those stay hidden unless you turn on **Show private screens**. Then they show dimmed with a lock, so you can explore what an app contains without wondering why a tap did nothing.
This works without any special permission. The keyboard sees the same launcher list the home screen does, and for those apps it can read the full screen table.
## Floating windows and split screen
Every phone maker opens apps floating or side by side its own way, and Android gives no single way that works everywhere. The keyboard carries the ways the main Android skins use, learned from the launchers, docks and split screen apps that already do this, and finds out which one your phone accepts.
### How the keyboard finds what works
The first time you open an app floating, or in split screen, the keyboard checks. It opens a small test window of its own, **Checking how this phone opens apps…**, the way it would open your app, and looks at where Android put it: a window smaller than the screen counts as floating, half the screen as split. It goes down the list for your phone until one works, keeps that one, and then opens your app. This takes a second or two, once. After that every floating or split launch goes straight the way that worked.
**Check what works on this phone**, in the options, runs the same check on demand and shows the result, for example "Floating: ColorOS floating window. Split screen: Android split screen". It also copies a line with your phone's model, Android version and result to the clipboard, which helps a lot in a bug report from a phone the keyboard hasn't met yet. The keyboard checks again by itself after a system update, or when you turn on the split screen helper, Shizuku or root, since any of those can change the answer.
If nothing works, the app opens normally and a short message says so. On a phone where the check found no floating window, the hold menu says "Opens full screen on this phone" under that item.
### What it tries, by phone
| Phone | Floating window | Split screen |
|---|---|---|
| Stock Android 12L and later (Pixel, Nothing, Motorola, LineageOS and others) | Only with [Android's freeform windows](#androids-freeform-windows) on, [Shizuku or root](#shizuku-and-root), or the [emulated window](#emulated-window-experimental) | Android's split screen |
| OPPO, OnePlus, realme (ColorOS, OxygenOS, realme UI) | ColorOS's floating window, the same one the Smart Sidebar opens | Android's split screen, then the [split screen helper](#split-screen-helper) |
| Samsung (One UI) | Pop-up view | Android's split screen |
| Xiaomi, Redmi, POCO (MIUI, HyperOS) | Xiaomi's small window, asked for the way its own sidebar does, then sized for it | Android's split screen, then the helper |
| Huawei and Honor | Their floating window | Their own split screen, then Android's |
| vivo and iQOO | vivo's small window, then Android's freeform window | Android's split screen, then the helper |
| Meizu, Nubia and RedMagic | Their own floating window | Android's split screen |
| Chromebooks, Samsung DeX, tablets in desktop mode | A normal resizable window | Android's split screen |
Android's split screen means one thing on paper and three in practice. On Android 12L and early 13 the keyboard asks for it directly. From Android 13's first quarterly update, Android ignores that request unless it comes from a screen of an app, and a keyboard has none, so the keyboard opens an invisible screen of its own for a split second, which makes the request and closes. Android sometimes needs the two apps opened in the other order, so the check tries both.
### Split screen helper
Some phones still open the app full screen. Those phones may still have split screen behind the **Split screen** item in the Recents menu, and an accessibility service is the only way another app can press that switch. **Split screen helper** is that service. With it on, opening an app in split screen first puts the app you're typing in into split screen, then the app you picked opens in the free half a little over half a second later. A pair waits for its first app to appear, then presses the switch.
Before pressing the switch, the service checks where the app windows sit on screen, to see whether split screen is already up, because pressing it then would close split screen instead. While a pair opens it also waits for the first app's window, by the app's name alone. It never reads what is in any window, does nothing at any other time and keeps nothing. Turn it on from the **Split screen helper** row on this page, which explains the service first and then opens Android's accessibility settings, or from [Permissions](https://wmkeyboard.pages.dev/reference/settings/permissions/).
Stock Android dropped that switch in 12L, so the helper isn't offered on Pixels and similar phones from 12L on; it is on every other phone, and the check uses it only where your phone still lists the switch. It isn't in the Google Play build. On Android 7 to 12, where a keyboard can't start split screen by itself, turning it on is what brings the split screen options in.
### Shizuku and root
[Shizuku](https://shizuku.rikka.app/) is a free app that lets other apps run a few system commands with the rights a computer has over adb, without rooting the phone. With Shizuku running and WM Keyboard allowed in it, the keyboard gains two more ways in:
- **Floating** through Android's own window command, sized afterwards to about 70% of the screen. This is the way that works on HyperOS 3, which turns down floating requests from apps.
- **Split screen** through the command Android's split screen itself answers to, on Android 14 and later: the keyboard opens the app, brings yours back, and moves the app into the second half. It won't do it while split screen is already up.
The **Shizuku** row in the options walks you through it: get Shizuku, start it, then allow WM Keyboard. On a rooted phone, **Use root** does the same through your root manager instead, which asks the first time. Root is never touched until you turn that on. Neither runs anything but those few window commands, and the settings ones below.
### Android's freeform windows
Most phones have floating windows built into Android but switched off, behind the developer option **Enable freeform windows**. With it on and the phone restarted, the plain Android floating window works on any skin. The **Android freeform windows** row turns it on (and **force activities to be resizable** with it, so apps that ask for full screen still float). Writing that setting needs a permission only a computer or Shizuku can grant: with Shizuku or root the keyboard grants it to itself; otherwise the row copies the one `adb shell pm grant` command to run from a computer. Either way the phone needs a restart afterwards. On a phone that has freeform windows built in, the row says so and does nothing.
### Emulated window (experimental)
Some phones have no floating window at all: stock Android phones, most Motorolas, HyperOS 3. For those, **When a floating window isn't possible** offers a stand-in. **Open normally and say why** is the default. **Emulated window (experimental)** draws the app inside a window of the keyboard's own: the app runs on a second, invisible screen, its picture is shown in a small window you can drag by its title bar, and your touches are passed through to it, the way Mi-FreeForm does it. The title bar has Back, **Open full screen**, which puts the app back on the main screen, and Close.
It needs Shizuku, which starts the app on that second screen and passes your touches in, and **Display over other apps**, which the keyboard asks for when you pick it. It is a stand-in and behaves like one: typing inside the window may bring the keyboard up on the main screen or not at all, apps that block screenshots show black, and some apps refuse to open on a second screen. Closing the window puts the app back among your recent apps.
## Split screen pairs
A pair is two apps you often use side by side, like a video and your notes, or a chat and a translator. Tap the pair's chip and the two apps open side by side. The first app ends up on top (or on the left), the second below (or on the right).
To make one, hold the first app, pick **Pair in split screen with…**, then tap the second app. The chip shows both icons, overlapped, and both names until you give it one. Hold a chip to open it or remove it.
Tools / App launcher / Split screen pairs lists every pair. Tap one to name it, and use its buttons to swap its sides, move it up or down the row, or delete it. A pair whose app you uninstalled leaves the row but stays in the list, so it comes back if you reinstall the app.
The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#gestures) can open a pair too, from a gesture or its quick menu, with **Open an app pair**.
Pairs need split screen, so they only show where split screen does. From Android 13's first update the keyboard's invisible screen opens both apps itself, a third of a second apart. Where the keyboard opens them directly, the second app waits about two thirds of a second, and just over a second on Android 16, which refuses a second split request for a second after split screen opens. With the [split screen helper](#split-screen-helper), the keyboard waits for the first app to appear (at most about 2.2 seconds on Android 15 and on Xiaomi, Redmi and POCO phones), then presses the switch, then opens the second app.
## Options
Tools / App launcher
The screen opens with the two rows every tool has, plus the keyword group that only the tools you can summon by typing carry. The app launcher is one of them:
- **Enabled**: which way this starts depends on how you answered the setup wizard. The app launcher is only in the "show me everything" starter set, so the two simpler answers leave it off until you turn it on here.
- **Icon colour**: only shown while [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) is on globally (it is by default). It overrides this tool's icon colour, a deep purple out of the box.
- **Trigger words**: "apps, launch" to begin with, under a **Keyword shortcut** heading. A **Match capitals** toggle sits below it, off by default, and a **Reset to defaults** row turns up once you've edited the words.
Then the launcher's own group:
- **Open apps**: what a tap on an app does. **Normally** by default, **Floating**, or **Split screen** (where the phone has a way into it). The hold menu offers all of them whatever you pick.
- **Split screen helper**: says whether the [split screen helper](#split-screen-helper) is on, and turns it on. Not on stock Android 12L or later, or in the Google Play build.
- **Check what works on this phone**: runs the [check](#how-the-keyboard-finds-what-works) and shows how this phone floats and splits apps. Copies the result for a bug report.
- **When a floating window isn't possible**: **Open normally and say why** by default, or **Emulated window (experimental)**. See [Emulated window](#emulated-window-experimental).
- **Shizuku**: Shizuku's state on this phone, and the next step: get it, start it, or allow WM Keyboard. See [Shizuku and root](#shizuku-and-root).
- **Use root**: off by default, and only on a rooted phone. Lets the window commands go through your root manager where Shizuku isn't running.
- **Android freeform windows**: turns on Android's own floating windows, which takes a restart. See [Android's freeform windows](#androids-freeform-windows).
- **Split screen pairs**: says how many pairs you have and opens their list. Only where split screen works.
- **Sort order**: **A to Z** by default, or **Recent first** to lead with the apps you opened most recently.
- **App names**: on by default. Off leaves bare icons with no label underneath.
- **Apps per row**: **Auto** by default, which fits as many apps as the keyboard width holds. Pick a number from 3 to 8 to fix it. Icons that don't fit their cell shrink to fit.
- **Icon size**: 42 dp by default, anywhere from 30 to 60. On Auto, bigger icons also mean fewer apps per row. The pinned and recent row keeps its own size.
- **Icon shape**: **Circle** by default, **Rounded square**, or **As on the phone** to keep the shape your home screen gives each icon.
- **Recent apps**: on by default. Turning it off stops the tracking and deletes the stored list.
- **How many recent apps**: 10 by default, anywhere from 4 to 20. Lowering it drops the extra entries straight away rather than hiding them. Pick a multiple of your grid width and the row fills cleanly with no gap at the end.
- **Open screens inside apps**: on by default. Off leaves the app's page as its pin, hide and **App info** actions, without the screen list. The hold menu still reaches the page either way, since hiding an app has no other door.
- **Show private screens**: off by default, and only shown while **Open screens inside apps** above is on. There's no screen list to add it to with that switch off. Lists the screens other apps may not open, dimmed. They're debugging fare, and a tap on one does nothing.
- **Apps on the toolbar**: says how many apps have a button on the toolbar. Tap it to take all of them off at once. See [Apps on the toolbar](#apps-on-the-toolbar).
- **Hidden apps**: says how many apps you hid. Tap it to bring all of them back to the grid at once. One at a time goes through the app's own page, which a search reaches even while the app is hidden.
- **Work apps**: on by default. Lists the apps of your work profile too. Off keeps the grid to your personal profile. Without a work profile it changes nothing.
- **Open the only match**: off by default. When a search leaves a single app, it opens once you stop typing for a moment, without a tap. A key typed in that moment restarts the wait, so a name you are still spelling doesn't open half-way. It waits while you are choosing the second app of a pair.
- **App folders**: says how many folders you have. Tap it to remove all of them at once and put their apps back in the grid. Folders are made and changed in the panel itself.
## Privacy notes
- The app list is read from the system on your device and never leaves it.
- Recents, pins, hidden apps, folders and split screen pairs are stored as plain package names in the keyboard's own settings, nowhere else. A work app's entry adds the work profile's number to its package name.
- The tool is unavailable before the first unlock after a reboot, like every tool that starts other apps.
- The check's answer is kept on this device only and never synced: it is a fact about your phone, not a preference.
- Through Shizuku or root the keyboard runs only window commands: starting an app in a window mode, resizing or moving its task, reading the task list to find it, and the two freeform settings. The emulated window adds a second screen of the keyboard's own and the touches you make on it.
The app launcher is the **App launcher** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool and the split-screen pairs screen leave the keyboard and the app until the bundle is switched back on.
---
# Offline voice (Asr4All)
URL: https://wmkeyboard.pages.dev/tools/asr4all/
Section: Tools
Summary: English dictation on your phone that writes the words while you speak, with punctuation and capitals.
Offline Asr4All is a dictation engine that runs entirely on your phone and listens as you speak. Where [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/) record a phrase and transcribe it once you stop, Asr4All decodes the audio while it arrives: the words appear in the field, underlined, and under the microphone as you say them, about a second and a half behind you, and when you stop the phrase is typed almost at once, because nearly all of it is already done. A second pass inside the model adds punctuation and capital letters, and repairs the mistakes a fast first pass makes. Once a model is downloaded, no audio leaves the phone.
It is English only. While a layout in another language is in use, dictation uses the system recognizer instead, the same way Whistle hands over its other languages.
## Asr4All, Whistle or Whisper
| | Asr4All | Whistle | Whisper |
|---|---|---|---|
| Languages | English | English, German, French, Spanish, Italian, Dutch, Polish | all 99 of Whisper's |
| Words while you speak | yes | no | while it transcribes, on its whisper.cpp models |
| Punctuation and capitals | from its own second pass | from the model | from the model |
| Download | 38, 72 or 123 MB | 16.9 MB | about 40 MB to 1.5 GB |
| Longest phrase | two minutes | 30 seconds | 30 seconds, two minutes on whisper.cpp models |
| Words to listen for | no | yes | on whisper.cpp models |
| Devices | every phone, every build | 64-bit ARM phones, not the F-Droid build | every phone; the F-Droid build has the whisper.cpp models only |
Pick Asr4All for English when you want to see the words as you go and get the phrase the moment you stop.
## Setting it up
Voice typing
Add **Asr4All** to the **Recognition engine** list and move it to the top, then press its row to open its screen and download a size from the **Asr4All models** group there:
- **Small** (38.6 MB): quickest, and the model makers' choice for older and low-power phones.
- **Medium** (72.5 MB): more accurate, still comfortable on most phones.
- **Large** (123 MB): the most accurate.
There is no separate switch for which one dictates: the largest size on the phone does. To go back to a smaller one, delete the larger. Each download comes from Hugging Face at a fixed revision, and both of its files are checked against their published SHA-256 before they count. A size that is part-way down resumes where it stopped.
## How it hears
The model is a streaming recogniser. Every 0.64 seconds of audio it commits the next 16 frames of text, each 40 ms long, looking 0.76 seconds further ahead to be sure of them, and carries what it has heard so far into the next step. So the cost is the same for every second you speak, however long the phrase runs, and a phrase can run to two minutes before the countdown under the microphone ends it.
The words go into the field as they are recognised, underlined and without punctuation, the way the system recognizer's do, and the line under the microphone shows them too. When you stop, the second pass settles the last few words, and the finished phrase replaces the underlined one with its full stops, commas, question marks and capitals, then goes through the same spacing, spoken punctuation and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)). A key typed in that last moment keeps the words as they stood and drops the second pass.
In **Type while you speak** (see [Typing while you dictate](https://wmkeyboard.pages.dev/tools/voice/#typing-while-you-dictate)), nothing goes into the field until the phrase is finished, so there the words are on the line under the microphone only.
**Where a phrase ends.** The model has no ear for pauses of its own. With **Stop after a pause** set, the **Phrase end detector** on the Voice typing screen listens for it: by default loudness, the way Whisper's clips end, and Silero instead once ONNX Runtime and Silero's 1.8 MB file are on the phone. FireRedVAD, a model of your own and **Smart Turn** work here too; see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase).
On a desktop, its output matched the model makers' own reference decoder word for word, punctuation included. Speed and accuracy on a phone are not yet measured by this project.
## Details & edge cases
**Not for other languages.** A dictation that listens for several languages (see [Language and engine](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine)) is Asr4All's only when every one of them is English; otherwise it goes to the system recognizer.
**No words to listen for.** The model has a way to keep words a biasing decoder put there, but the decoder itself is not published, so **Words to listen for** has nothing to feed and does not show while Asr4All is the engine.
**The runtime is built in.** Asr4All runs on [ExecuTorch](https://github.com/pytorch/executorch), built for these models alone, so it adds about 3.8 MB on a 64-bit ARM phone, shared with [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) and [Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/), where the general-purpose library would add over 10 MB.
**The licence.** The model weights come with their own licence, which allows dictation and personal use and forbids surveillance, profiling and similar uses. Its text is in the app's About screen, with the other credits.
**Every build.** The runtime is in every build; F-Droid compiles it from source. A build made without it does not offer the engine.
---
# Calendar
URL: https://wmkeyboard.pages.dev/tools/calendar/
Section: Tools
Summary: A month view of your device calendar where you add, change and delete events without leaving the keyboard, with up to two of eight non-Gregorian calendars alongside Gregorian.
The Calendar tool opens a full month grid inside the keyboard, or the same month as an agenda down a day rail. Browse any month, and up to two non-Gregorian calendars ride alongside the Gregorian dates over the grid and inside each day cell. Tap a day to see its date across every calendar you've picked, plus whatever's on your device calendar for that day as a row of cards. You can also add an event, change or delete one (repeating ones included), invite guests, and type an event into the app you're writing in, all without leaving the keyboard.
tools/calendar-month-grid — The Calendar panel: month grid with Bengali day numbers, today's events as cards below.
## Browsing the month
Open **Calendar** from the toolbar, or from the toolbox if you haven't pinned it. The header carries the month and year as its title, a **Today** chip, a **Month | Agenda** switch, a round **+** for a new event and the tool's settings. Swipe sideways across the grid to step a month: the grid slides out and the next one slides in. Tap the title to drop a month picker instead, with the year between two arrows and the twelve months as chips, so a date far away is two taps rather than a dozen swipes. **Today** jumps straight back and reselects today. The weekday row along the top tints your weekend in the accent color, and which days those are is a setting that starts off from your phone's region (see [Options](#options)).
Tapping a day cell doesn't leave the panel. It updates the summary and the cards below the grid. The selected day sits on an accent disc that springs across the grid to whichever day you tap next, today's cell has a thin accent ring, and a small dot sits under every day that has something on. On the selected day the dots take the colors of its events' calendars. Press and hold a day to start a new event on it.
If either of your two chosen calendars has day numbers that don't line up one-to-one with the Gregorian grid, its day number rides inside the cell as a small second number next to the Gregorian one. Bengali dates render in Bengali numerals (০–৯). The first day of a Chinese lunar month shows the month name instead of a bare "1", since that's the only cue in the grid that a lunar month just turned over.
Below the grid, the selected day shows its full Gregorian date, weekday and a relative label (Today, Tomorrow, Yesterday, "in N days," or "N days ago") on one line, and the same date in whichever alternate calendars you've picked under it.
## Today's events
The bottom of the panel reads events straight from your device calendar for whichever day is selected. Until you grant calendar access, it shows a one-line prompt with an **Allow** button instead of any event data. Once granted, it queries in the background every time you change the selected day: a blank row while loading, "No events" with a **New event** button if the day is clear, or a row of event cards you swipe through sideways otherwise. The cards pop in one after another each time you pick a day.
Each card is tinted with its calendar's color and carries a stripe of it down the left edge (the accent color where the calendar doesn't provide one). The time runs along the top as a start and end, "9:00 – 10:00 AM", with an **All day** badge in its place for all-day events and a small repeat mark on the right for recurring ones; then the title, then the place if the event has one. Recurring events are expanded per occurrence, so a weekly meeting shows up on every day it actually lands on rather than only its first occurrence. Events in calendars you've hidden in your calendar app stay hidden here too.
Tap a card to open the event. The event's own face shows it as a larger card in the same color with when and the title, then how it repeats, the place, notes, guests (with who said yes, no or maybe) and reminders as lines under it; the calendar it's in is named in the header. Along the bottom runs a bar: **Edit** as the main button when you can change the event, with **Insert as text**, **Open in calendar app** and **Delete** as round buttons beside it. For an event you can't change, **Insert as text** takes the main spot.
tools/calendar-day-events — A day with events: tinted cards, each with its time, title and place.
## Agenda
The **Agenda** side of the header's switch shows the same month as a list. A rail down the left names each day that has something on (**Today**, **Tomorrow**, **Yesterday**, or the date), with a dot on the rail per day and the accent dot on today, and the day's events sit beside it as the same tinted cards. The day you've selected is the one whose cards are loaded, with a ring around its dot; a day you haven't opened yet shows a quiet card with its date that loads it when tapped, and once a day has been opened its cards stay while you move around the month. Swiping sideways moves a month here too. A month with nothing on offers **New event** in the middle, and until calendar access is granted the agenda shows the **Allow** prompt in its place.
## Adding an event
Tap the round **+** in the header, or press and hold a day in the grid. The composer opens on the selected day with the title already taking your typing, and the panel folds down so the keys show underneath; while you type, the folded panel shows the event's preview card so you keep your bearings. **Done** (or Enter) moves you from the title to the place.
The composer is a preview card over a tray of chips. The card is the event as it will look in the month, tinted with its calendar's color: when it is, its title (or **Title** as a hint until you type one), its place, and a line in the accent color when it overlaps something already in your calendar ("Overlaps Standup"). It updates as you change anything. A tap on the card sends the keys back to the title.
Under it, each piece of the event is a chip that reads what it holds, or its hint while empty: the when, **Add a place**, **Add guest** (or how many guests), how it repeats, the reminders, which calendar (with its color dot), **Add notes**, and **Busy** or **Free**. Tapping a text chip sends the keys to it. Tapping any other unfolds its editor under the tray, one at a time, and the open chip is drawn selected:
- **When** holds **All day**, which turns the times off so the start and end name whole days; **Starts** and **Ends**, each with a date chip that opens a small month and a time chip that opens the same spinning drums the Clock tool uses; and the length chips (15 min up to 3 h), which set the end for you. Moving the start moves the end with it, so the length stays the same. If the end lands before the start, the **Ends** label and the card's time turn the accent color and the main button refuses until it's fixed. When the event's time zone isn't the phone's, a chip names the zone the times are in.
- **Busy** or **Free** flips on a tap.
- **Repeat**, **Reminders**, the calendar and guests each have their own editor; see below.
A new event starts with the length and reminder you set in [Options](#options), in your default calendar. The bar along the bottom holds **Add to calendar** as the main button (**Save changes** when you're editing), with **Insert as text** and **Open in calendar app** as round buttons beside it. The event goes straight into your calendar, the panel goes back to the month with that day selected, and an **Undo** bar takes it back out again if you change your mind. **Cancel** in the header throws the draft away.
If you close the panel halfway through, the draft is kept. Open the tool again and a **Resume** line at the top brings it back.
### Reminders
The reminders chip lists what's set, or **No reminder** while there are none. Tapping it unfolds the choices: at the start, 5, 10, 15 or 30 minutes, 1 or 2 hours, 1, 2 or 7 days before, with the ones already set drawn selected. A tap adds or removes one. Some calendar accounts limit how many reminders an event can carry, and the remaining choices grey out once you reach that limit.
### Repeating events
The repeat chip unfolds a set of chips worded for the event's own day: daily, weekly on that weekday, every weekday (Monday to Friday), monthly on that date, monthly on its weekday ("the third Monday"), monthly on the last such weekday when it falls in the last week, and yearly. **More options** opens the rest: repeat every 2 weeks or every 3 months, pick several weekdays, and end it never, on a date, or after a number of times.
Move a weekly event to another day and its repeat day moves too, so a Monday meeting dragged to Tuesday keeps landing on Tuesdays. A repeat rule written by another app that this editor can't show (several dates a month, say) appears as **Custom repeat** and is saved back exactly as it was unless you pick a different rule.
### Guests
The guests chip unfolds the guest list and **Add guest**, which opens a box for a name or an email address. With contacts allowed, typing a name lists the matching people with their addresses, and a tap adds one. Without contacts, type the whole address and press Enter. Each guest is a chip with a cross to take them off again.
Guests are written to the event the same way your calendar app writes them, which means your calendar account (Google Calendar, for one) may email them an invitation when it next syncs. The composer says so under the guest list. A calendar that only lives on your phone sends nothing.
### Insert as text
The **Insert as text** button types the event into the app you're writing in, as one line: the title, the date and time in your own format, and the place, like "Standup, Mon 19 Oct, 9:00 – 10:00 AM · Room 4". It's on the bar of the event's face and on the composer's, so you can share a plan before or after saving it.
## Changing and deleting events
Open an event and tap **Edit**. The composer opens with everything filled in, including its guests, reminders and repeat rule, and its main button reads **Save changes**, which writes them back to the same event. **Delete** on the event's bar removes it, with **Undo** afterwards.
Events you can't change show a note instead of Edit and Delete: one you were invited to by someone else (unless they let guests edit), or one in a read-only calendar such as holidays or a shared calendar you can only view.
### Repeating events
Changing or deleting one occurrence of a repeating event first asks which ones you mean:
- **This event** changes or removes just that day. The rest of the series is left alone.
- **This and following** ends the series the day before and, for a change, starts a new series from that day with your edits. A series that was set to run a number of times keeps the right total between the two halves.
- **All events** changes the whole series. A new time or a new day moves the series' first occurrence by the same amount, so a series that began in March still begins in March.
If you changed the repeat rule itself, **This event** isn't offered, since a single day can't repeat. **Undo** can put back a single changed or removed day, a series that was cut short, and a deleted event.
## Calendar permission
The keyboard asks for calendar access in two steps, and never at install or first launch. Reading comes first: tap **Allow** in the panel and you can see your events. Writing comes the first time you save or delete an event, and is asked for together with reading. Your event waits while the dialog is up, and is saved as soon as you come back with the grant. Refuse it and the composer says so and offers **Open in calendar app** instead, which hands the event to your calendar app's own editor to save there.
An on-screen keyboard service can't request runtime permissions directly. So each of these opens an invisible trampoline activity that shows a short explanation of what the access is for, then the system permission dialog, then closes itself the instant you answer. The [camera tool](https://wmkeyboard.pages.dev/tools/camera/) uses the same pattern for its own permission. If you revoke calendar access in system Settings and come back to the keyboard, the panel notices as soon as it regains focus and drops back to the Allow prompt.
Guest suggestions ask for contacts access the same way, from the **Suggest from contacts** link under the guest box. The tool reads names and addresses only while that box is open and keeps nothing afterwards.
The tool writes only what you save, change or delete yourself. Nothing is changed in the background.
Calendar is left out of the keyboard's direct-boot-safe tool set. It has to query a content provider that isn't available before your first unlock after a restart. Open the toolbox on a still-locked device and Calendar isn't listed there at all, so there's no broken panel to walk into. The arithmetic and sensor tools (the calculator, the unit converter, the compass, the level, the moon phase) are still there, but anything that reads the calendar provider is not.
## Options
Tools / Calendar
Like every tool, Calendar opens with a **Turn on** switch and, while [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are on globally, an **Icon colour** override (a soft red by default). Under those sits the **Keyword shortcut** group: **Trigger words**, pre-filled with "calendar, schedule", a **Match capitals** toggle off by default, and a **Reset to defaults** row once you've edited the words. Then the tool's own settings:
tools/calendar-options — Calendar options: First and second calendar pickers, and the Hijri day adjustment slider.
- **First calendar**: the default depends on your phone's region. That's the Bengali calendar in Bangladesh, era years in Japan, Hindu (Saka) in India, Hijri across much of the Arab world, and **None** where no second calendar is in common use. Choose from Bengali, Hijri, Chinese, Hebrew, Hindu (Saka), Persian (Solar Hijri), Buddhist (Thai solar), Japanese (era years), or None. Its day number is the one that also shows up inside each day cell. Buddhist and Japanese days line up one-to-one with Gregorian, so if you pick either the cell falls back to Second calendar's day number instead, where that one has its own.
- **Second calendar**: the same nine choices, also region-defaulted. Bangladesh gets Hijri alongside the Bengali calendar, Japan gets the Chinese calendar alongside era years, and most places get **None**. It shows in the month header and under the selected day, alongside First calendar.
- **Weekend**: which weekday initials the month grid tints. Seven presets: Saturday & Sunday, Friday & Saturday, Thursday & Friday, Friday, Saturday, Sunday, or no weekend at all. The default comes from your phone's region: Friday and Saturday across much of the Middle East, Friday in Bangladesh, Thursday and Friday in Iran and Afghanistan, Saturday in Nepal, Sunday alone in India, Mexico and the Philippines, and Saturday and Sunday everywhere else.
- **Hijri day adjustment**: a slider from −2 to +2 days, default 0. It only appears when First or Second calendar is set to Hijri. The tool computes Hijri dates with the tabular (arithmetic) calendar rather than real moon sighting, so real Islamic months can start a day or two off from the tables. Use this to match your local authority's sighting-based calendar.
Under **New events**:
- **Save events**: **In the keyboard** (the default) writes new events straight into your calendar. **In the calendar app** makes Save open your calendar app's editor with the event filled in, for you to save there; it also makes the "Add to calendar" [selection action](https://wmkeyboard.pages.dev/smart/selection-actions/) go to the calendar app. Changing and deleting events always happens in the keyboard.
- **Default calendar**: which calendar a new event starts in. **Primary calendar** picks the first one you can write to, primary calendars first. Until calendar access is allowed, the row offers to ask for it.
- **Event length**: 15 minutes up to 4 hours, default 1 hour.
- **Reminder**: none, at the start, or 5 minutes up to 1 day before, default 10 minutes.
Accuracy varies by calendar: Chinese dates are computed astronomically (real new-moon and solar-longitude calculations), so they're exact for the modern era. Hebrew, Persian, Hindu (Saka), Buddhist, and Japanese are all exact arithmetic conversions. Hijri is the only one that's an approximation, which is why it's the only one with an adjustment slider.
The same First calendar / Second calendar / Weekend pickers also appear on the first-run setup page during onboarding, if you've kept Calendar enabled: anything you set there is the same setting, just asked up front. All three already open on the region's guess, so that page is where you see it and correct it.
The calendar and the clock are the **Calendar and clock** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools and the clock's settings screen leave the keyboard and the app until the bundle is switched back on.
## Details & edge cases
- Insert as text types an event in Gregorian dates only. The alternate calendars are shown in the panel but not inserted.
- Selecting a date or time in your text and using **Add to calendar** from the [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) opens this composer with the selection as the title and the date and time already set. A time zone named in the selection ("5pm EST") is kept, and the composer shows which zone the times are in.
- Opened from a date [smart chip](https://wmkeyboard.pages.dev/smart/chips/) ("next friday"), the panel lands on that day, and the header's **+** starts a new event there.
- The month grid, the agenda and the composer play their motion (the disc's spring, the cards popping in, the grid's slide) only while the keyboard's animations are on; with reduced motion everything lands in place at once.
- Calendar isn't pinned to the toolbar out of the box, and it's only in the "show me everything" starter set that setup lands, not the minimal or the recommended one. Pick either of those two simpler answers and Calendar starts switched off, so you'll want to turn it on yourself under Settings → Tools.
- Reopening Calendar from the toolbar while the panel is already open closes it, same as most other panel tools.
- **The region defaults are a guess, and only ever a starting one.** They come from the same permission-free signals as [suggested languages](https://wmkeyboard.pages.dev/languages/overview/#suggested-languages): the SIM's country, then the network's, then your locale's region. Those are consulted only until you pick something. Once you have, your pick is what sticks, wherever the phone travels.
- Nothing about the Calendar tool touches the network. Your calendar account syncs the events you add or change the way it always does. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what the keyboard itself sends.
---
# Camera tool
URL: https://wmkeyboard.pages.dev/tools/camera/
Section: Tools
Summary: A viewfinder inside the keyboard for quick photo capture and insertion.
The Camera tool swaps the key rows for a live viewfinder, so you can snap a photo and send it into whatever you're typing in without ever leaving the keyboard. all: it's built on plain CameraX, not ML Kit, so it ships in every build.
## Taking a photo
Open **Camera** from the [toolbar, or the toolbox](https://wmkeyboard.pages.dev/tools/overview/) if you haven't pinned it. Camera isn't in the toolbar's default pinned set on a phone, where only Emoji, Clipboard, and Settings are. So the first time, you'll probably reach it through the toolbox or by typing its trigger keyword. See [smart suggestion chips](https://wmkeyboard.pages.dev/smart/chips/).
The viewfinder fills the width of the panel and behaves like any camera app:
- **Tap anywhere to focus.** A tap starts focus-and-metering at that point.
- **Pinch to zoom.** A zoom pill ("1.2x" style) appears top-center once you're past 1.05x, and the pinch is clamped to whatever range the current lens supports.
- **Flash**: a chip on the bottom-left, shown only if the active lens reports a flash unit. Tapping it cycles Off → Auto → On.
- **Self-timer**: the next chip over, cycling Off → 3s → 10s → Off. Once it's running, a large countdown digit appears at the center of the frame, with a feedback tick each second, before the shutter fires on its own.
- **Front/back switch**: bottom-right, and only shown if the device has both a front and a back camera. [Start with the selfie camera](#options) below decides which one the tool opens on.
- **Pick a photo**: the picture button beside it opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/), or Android's photo picker in the Play Store build, and the photo you choose lands on the confirm step as if you had just taken it. It's there while the confirm step has something to do with a photo besides sending it: a server under [Translate photos](#translating-a-photo), the [Search button](#search-by-photo-options), or a camera opened from web search.
- **Shutter**: the button in the middle. Tap it to take the photo, or let the self-timer run out.
### Scanner modes
The camera is also the way into the [scanners](https://wmkeyboard.pages.dev/tools/scanner/). A rail of round buttons down the right edge of the viewfinder switches between **Photo**, **QR and barcode scanner**, **Text scan (OCR)** and **Document scanner**, with the mode you're in lit. The same rail is on the QR scanner and on Text scan, so from any of them you reach the others in one tap. Document starts the document scanner, which opens over the keyboard as it does from its own tool. That means one Camera button on the toolbar is enough; the scanners can stay in the toolbox.
The separate tools still work. Each one opens the camera straight in its own mode, so a QR button you've pinned keeps doing what it did. A scanner you've turned off under [Tools](https://wmkeyboard.pages.dev/reference/settings/tools/) leaves the rail too. The rail isn't shown on a camera opened from web search or on a clip's picture, where there's only one thing to do. Turn it off with **Switch modes in the camera** under the options below.
**Spot QR codes in photo mode** makes the camera behave like a phone's own camera app. Point it at a QR code or a barcode in photo mode and a chip appears at the top of the viewfinder with what the code says. Tap the chip and the camera switches to the QR mode, which reads it with all its usual buttons. Taking a photo works the same as ever meanwhile. It's off by default, because reading frames uses more battery, and it needs the QR scanner's reader on the phone; where that's still a download, the QR mode offers it.
### What you see is what you get
There's no manual crop step, no drag handles, and no filters, whatever the word "crop" suggests elsewhere. The viewfinder is taller than the visible panel, because it spills upward to fill the panel's width at the sensor's aspect ratio. The moment you tap the shutter, WM Keyboard slices the raw capture down to exactly the region that was on screen. What you framed is what ends up in the photo.
If you'd rather keep everything the sensor saw, turn on **Send the whole photo** in [Options](#options). The photo is then the full 4:3 frame, top and bottom strips included, and the confirm step fits all of it inside the panel instead of drawing it where the viewfinder had it.
The capture itself stays modest. Photos are capped at the longest edge **Photo size** names, which is 1000, 1600 (the default), 2400 or 3200 px, rather than the sensor's full resolution. A photo headed straight into a chat message doesn't need 12+ megapixels, and a smaller capture processes faster. Each shot is written to the app's private storage as a JPEG before the confirm step appears.
## Confirm, retake, or send
After the shutter fires, the frozen photo is drawn at the exact spot and size the live preview had it. The confirm screen looks identical to the moment you took the shot, apart from a **Retake** and **Send** button pair underneath.
- **Retake** discards the file and goes straight back to a live viewfinder.
- **Send** hands the photo off (see [Where the photo goes](#where-the-photo-goes) below) and closes the panel back to your keys.
- **Search** is a third button, off by default. Turn on **Search button** under [Search by photo](#search-by-photo-options) and it sits after Send. It looks up what is in the photo instead of sending it. See [Searching with a photo](#searching-with-a-photo) below.
- **Translate** appears once a provider is set up under [Translate photos](#translating-a-photo). It replaces the photo with a copy whose text is translated.
If you took the photo with the front camera and [Mirror selfies](#options) is on, the saved file is flipped horizontally to match what you saw while framing. Camera sensors record selfies un-mirrored by default: text reads the right way round, but the shot looks flipped against the live preview. Shutter sound and haptic feedback on the controls, timer ticks, and shutter are each their own setting. See [Options](#options).
## Translating a photo
Pointed the camera at a menu or a sign in another language? Add a [Rens](https://git.maid.zone/stuff/rens) server under Tools / Camera / Translate photos, and the confirm step gets a **Translate** button. Rens is a free front end to Google Lens: your own server, or [a public one](https://git.maid.zone/stuff/rens#instances).
- **What it does.** The photo goes to the server, which has Google Lens translate the text in it, and comes back as a new picture with the translated text drawn over the original. It replaces the photo on screen, so **Send** sends the translated one. **Retake** starts again.
- **Keeping it without sending.** A translated photo has two round buttons at the top right: **Copy** puts the picture on the clipboard, and **Save to the gallery** writes it to `Pictures/WM Keyboard`, whether or not [Save to the gallery](#options) is on.
- **A photo you already have.** Tap **Pick a photo** in the viewfinder and choose one, then **Translate**. A picture you copied is quicker still: press and hold it in the [clipboard panel](https://wmkeyboard.pages.dev/tools/clipboard/) and tap **Translate**. The camera opens on that picture and translates it at once, without needing the camera permission, and its back button returns to the clipboard.
- **Only some of the text.** Lens translates every word it finds. To translate one line, read the photo with [Text scan](https://wmkeyboard.pages.dev/tools/scanner/#scanning-text-ocr) instead, keep the words you want, and tap **Translate** there.
- **Which language.** The language the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool translates into. Lens detects the language of the photo.
- **More than one server.** **Translate photos** is a [provider list](https://wmkeyboard.pages.dev/tools/providers/): the next server is asked when one fails. With **How to use them** on **Choose in the panel**, a chip at the top right of the photo picks the server.
- **Privacy.** Rens strips the photo's metadata and renames it before it passes the photo on, so Google sees the server and not your phone. The server still sees the photo.
- **Data saver.** It counts as a web search. On a connection where data saver asks first, the first press of Translate says so and the second sends the photo. Each translation shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Search by photo**.
## Putting the place on a photo
With the [Location tool](https://wmkeyboard.pages.dev/tools/location/) on, the viewfinder's control rail carries a pin next to the timer. Tap it and the photo is stamped: a band along the bottom with the place on one line and the coordinates, the time and the Plus code under it, white on a dark fade. A line over the viewfinder shows what the band will say before you shoot, or that the keyboard is still finding you. The stamp is in the picture itself, so every chat app shows it, and so do screenshots and forwards. The pin remembers whether it was on.
The stamp follows the Location tool's settings: its [precision](https://wmkeyboard.pages.dev/tools/location/#precision), so a city-level stamp says only the city, and its address lookup. The Location panel's camera button opens the camera with the stamp on for that one photo.
The confirm step has **Add the place as text** above Retake and Send. On, Send types the place into the box first, in the Location tool's default format, and then sends the photo. Many chat apps take the text in the box as the photo's caption; others send it as a message of its own. That switch is remembered too.
Either one asks Android for location access the first time, the same way the Location tool does. Nothing is read while both are off.
## Where the photo goes
Tapping **Send** inserts the photo the same way clipboard images, GIFs, and stickers do: through Android's `commitContent` API, matched against whatever image types the field you're typing into says it accepts. If the field doesn't advertise support for any compatible image type, the photo is copied to the system clipboard instead, with a toast:
> This app does not accept images here. The keyboard copied the image. Paste it instead.
Either way, the send closes the panel and hands you back the keys.
## Searching with a photo
Saw a plant or an insect and want to know what it is? **Search** looks up what's in the photo: similar pictures, the name of the thing, and pages about it. There are two ways to get the button:
- **From web search.** The [Web search](https://wmkeyboard.pages.dev/tools/search/#web-and-image-search) panel has a camera button beside its search bar. It opens this camera, and after the shutter the confirm step offers **Retake** and **Search**, with no Send. Back returns you to the search panel. This works whatever the setting below says.
- **On the camera itself.** Turn on **Search button** under [Search by photo](#search-by-photo-options), and every photo you take gets Search next to Retake and Send.
Where Search takes the photo is up to **Search with**:
- **Google Lens**, the default. The photo opens in Lens, inside the Google app, which shows its matches right away. If the Google app isn't installed, which is the case on most phones without Google's apps, the share sheet opens instead.
- **Web**. The keyboard sends the photo to the search site you pick under **Search site** and opens that site's results page in your browser. You don't need the Google app for this. The sites are **Google Lens** (on the web), **Bing**, **Yandex**, **TinEye**, and **Custom**. Before the upload, the photo is scaled down to at most 1280 px on its longest side, which is about what the sites work at anyway. The keyboard shows "Sending the photo to the search site" while it uploads.
- **Share sheet**. Android's own share sheet opens every time, so you pick the app: Lens, a plant identifier, a shopping app, anything that takes an image.
The panel closes once the search is on its way. If it can't start, the photo stays on screen so you can try again or send it after all. That covers a failed upload, no app to take it, and a data saver block.
None of the search sites has a public way to upload a photo. **Web** does what each site's own "search by image" button does, so a site can stop working when it changes how that works. You'll then see "The search site did not send a results page", and picking a different site is the fix.
**Custom** is for a server of your own, or any service you'd rather use. Two more fields appear when you pick it:
- **Upload address**: where the keyboard posts the photo, as multipart form data.
- **Photo field name**: the form field the photo goes in. `image` by default.
The server answers with a redirect to its results page, with the results address as the whole reply, or with JSON like `{"url": "…"}`. A relative address counts from the upload address. The keyboard opens only `http` and `https` addresses, whatever the server sends back.
Only **Web** uses the keyboard's own network connection, so it's the only route that answers to [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/): it counts as a web search. On a connection where data saver asks first, the first press of Search shows "This connection is metered. To send the photo, press Search again." and the second press sends it. Each upload appears in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Search by photo**. Lens and the share sheet give the photo to another app, and the keyboard itself sends nothing.
By default that's the *only* copy that persists anywhere. The private file used to hand the image off is scratch storage, not a saved photo. On each panel open, anything beyond the four most recent capture files is deleted, which only matters if the app was killed mid-confirm. Want a permanent copy? Turn on **Save to the gallery**, off by default, and every photo you send or search with is also written to `Pictures/WM Keyboard` in your device's shared gallery, once, even if you search with it and then send it. See [your data on device](https://wmkeyboard.pages.dev/privacy/data/) for how WM Keyboard's on-device storage is organized more generally. Capture, crop, save, and send are all local. The camera tool uses the network only for a search with **Search with** set to **Web** (see [Searching with a photo](#searching-with-a-photo)) and for **Translate** (see [Translating a photo](#translating-a-photo)). [Network policy](https://wmkeyboard.pages.dev/privacy/network/) has the wider picture.
## Granting camera access
tools/camera-permission-gate — The permission gate shown before camera access is granted, with the Allow camera pill.
The first time you open Camera, you'll likely see a short message instead of a live feed:
> The camera tool needs permission to use the camera. The app takes a photo only when you press the shutter.
Tapping **Allow camera** starts a small trampoline activity that exists for one reason. An IME service cannot show Android's runtime permission dialog itself, so this activity comes up instead. It shows a disclosure first, headed "Camera access for the camera tool", spelling out that the camera only runs while the tool is on screen and that no photo is uploaded anywhere. **Continue** hands you Android's own permission prompt. **Not now** leaves without asking for anything. Either way the activity closes the moment you answer, and the keyboard re-checks the permission as soon as it regains focus. You don't need to reopen the panel.
This same trampoline is shared by the [OCR and QR scanner tools](https://wmkeyboard.pages.dev/tools/scanner/): granting camera access from any one of the three covers all three, since they all request the same `android.permission.CAMERA`.
Android stops showing its own prompt once you've denied camera access twice ("Don't ask again"). The disclosure still appears if that has happened, with a line saying so, and its button reads **Open system settings** instead of Continue. That takes you to WM Keyboard's permission page, which is the only place left to grant it.
On a device with no camera at all, the panel shows "Your device has no camera." in place of the viewfinder. WM Keyboard installs and runs fine either way. Nothing about the app requires a camera.
## Options
Tools / Camera
tools/camera-options — Camera's Options screen: selfie camera, mirroring, gallery saving, shutter sound and haptics.
Like every tool on the [full tool list](https://wmkeyboard.pages.dev/reference/settings/tools/), this screen opens with a **Turn on** switch. An **Icon colour** override follows it, but only while [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) are on globally. It's pink by default, and it becomes two rows instead of one if gradient tool colours are on as well.
Under those sits the **Keyword shortcut** group, which every tool that ships a built-in keyword gets:
- **Trigger words**: a text field pre-filled with "camera". Type that word on its own and the suggestion strip offers to open the tool, dropping the word (see [smart suggestion chips](https://wmkeyboard.pages.dev/smart/chips/)). Commas separate more than one word. Clear the field and the tool stops offering itself this way.
- **Match capitals**: off by default, so "camera", "Camera" and "CAMERA" all match. Turn it on and only the exact spelling in the field above counts. That's worth doing for a short trigger word that's also ordinary text.
- **Reset to defaults**: only appears once you've edited the trigger words, and puts them back to "camera". Its subtitle shows what you'd be going back to.
The tool's own settings, in these groups:
**Options**
- **Start with the selfie camera**: off by default. Opens the panel on the front camera instead of the back one.
- **Self-timer**: Off by default. The timer the panel opens on, so a habitual 3s user doesn't re-pick it every time. The in-panel chip still steps Off → 3s → 10s.
- **Photo size**: 1600 px by default, from a choice of 1000, 1600, 2400 and 3200. It's the longest edge of the photos you take, and the camera picks the closest size its sensor offers. Larger photos hold more detail, which matters for a picture of a document somebody has to read, and they take longer to capture and to send.
- **Mirror selfies**: on by default. See [Confirm, retake, or send](#confirm-retake-or-send) above for what this does and why it exists.
- **Send the whole photo**: off by default. It skips the crop described in [What you see is what you get](#what-you-see-is-what-you-get) and sends the full 4:3 frame, including the strips that ran off the top and bottom of the screen. The live viewfinder is unchanged, so this is the one case where the photo holds more than you framed. The confirm step fits the whole picture inside the panel so you can see what you're about to send.
- **Save to the gallery**: off by default. Also copies every capture into `Pictures/WM Keyboard`, on top of sending it.
**Location**
- **Stamp the place on photos**: off by default. The same switch as the pin on the viewfinder. See [Putting the place on a photo](#putting-the-place-on-a-photo).
- **Add the place as text**: off by default. The same switch as the one on the confirm step.
**Search by photo**
- **Search button**: off by default. Adds **Search** after Retake and Send on the confirm step. The camera button in web search offers Search whatever this says.
- **Search with**: **Google Lens** by default, then **Web** and **Share sheet**. See [Searching with a photo](#searching-with-a-photo) for what each does.
- **Search site**: shown only while Search with is **Web**. **Google Lens** by default, then **Bing**, **Yandex**, **TinEye** and **Custom**.
- **Upload address** and **Photo field name**: shown only for **Custom**. Where your server takes the photo, and the form field it reads.
**Translate photos**
- The servers that translate a photo for the **Translate** button. Empty by default, which hides the button. See [Translating a photo](#translating-a-photo).
**Feedback**
- **Shutter sound**: on by default. Plays the camera click when a photo is taken.
- Vibration on the shutter, the controls and the timer countdown follows the keyboard's own haptics setting under [Key press › Haptics and sound](https://wmkeyboard.pages.dev/typing/feedback/). There is no separate switch for the panel.
**Scanner modes**
- **Switch modes in the camera**: on by default. The rail of Photo, QR code, Text and Document over the camera and the scanners. See [Scanner modes](#scanner-modes).
- **Spot QR codes in photo mode**: off by default. A chip over the viewfinder when the camera sees a code; tap it to read the code.
The camera and the gallery are the **Camera and gallery** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools leave the toolbar, the toolbox and the Tools list until the bundle is switched back on.
## Details & edge cases
- **No manual crop, no filters, no multi-shot.** One photo per open of the panel. Retaking replaces rather than appends.
- **Search isn't an in-keyboard result list.** Every search route ends in another app or in your browser, which is where the results are worth reading. Nothing comes back into the field you're typing in.
- **Not pinned or recommended by default.** Camera ships enabled, but it isn't in the toolbar's default pinned set and isn't part of the onboarding wizard's "Recommended" starter selection either. If you went through setup and picked "Recommended" rather than "Everything" on the tools page, you'll need to turn Camera on yourself under Settings → Tools.
- **No direct-boot support.** Camera doesn't work on the lock screen before you've unlocked your device once after a restart, since it depends on the app's private storage, which isn't available that early.
- **Every build, no gating.** Camera has no edition restriction anywhere in its code path. The scanners and handwriting are in the F-Droid build too, with open-source readers where they use Google ML Kit elsewhere.
---
# Clipboard manager
URL: https://wmkeyboard.pages.dev/tools/clipboard/
Section: Tools
Summary: History, pinning, search and rich media, with password hygiene built in.
The clipboard tool keeps a running history of what you copy: text, images, files and more. A paste from ten minutes ago is still one tap away instead of gone the moment you copy something new. On a phone it's one of only three tools pinned to the toolbar out of the box, alongside Emoji and Settings. A tablet starts wider, because three icons across a twelve-inch bar is mostly empty space. A small tablet pins five (Emoji, GIF, Clipboard, Voice, Settings) and a large one pins seven, those five plus Translate and Text edit. Clipboard is in all three sets.
tools/clipboard-panel — The clipboard panel: staggered two-column cards, entity chips for a fresh code or link, and a search pill.
## Opening the panel
Tap **Clipboard** on the toolbar: the clipboard with lines of text on it. The empty clipboard beside it, if you pinned one, is the **Paste** tool, which pastes your latest copy without opening anything. If you unpinned Clipboard, open the toolbox and tap it there. See [the toolbar](https://wmkeyboard.pages.dev/tools/overview/) for how pinning works. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press C.
To open it with the [search](#finding-a-clip-again) already taking your typing, bind the **Clipboard search** action to a flick, a key's long press or a toolbar button's hold. It's an action rather than a tool, so it never shows on the toolbar or in the toolbox: you'll find it under **Other** in those pickers. It turns the search bar on if it was off.
Cards lay out in a staggered grid, two columns by default, rather than a fixed one, so a short text clip doesn't get stretched to match a tall image beside it. Pinned clips sort first by default ([flip that](#options) to put them last instead, or turn on **Keep pinned clips in place** to leave them where they were copied, wearing their pin, the way SwiftKey shows them). Tap any card to insert it. Copying the same content again moves the existing card back to the top rather than adding a duplicate.
### Making the panel taller
The panel opens as tall as the keyboard. A short bar sits across its top edge: drag it up and the panel grows over the app, so more clips fit and there's less to scroll. Drag it down to shrink it again, as far as the keyboard's own height. The app stays still while you drag and moves once, when you let go. Where you let go is kept, and the panel opens that tall from then on. It's the same setting as **Panel height** under [Options](#options), where the reset button takes it back to the keyboard's height. The panel never covers more than most of the screen, so on a short screen or with the phone sideways it stops earlier, and with no room to grow at all the bar isn't drawn. The height changes only the history: a search, or a clip open in the editor, still shrinks the panel so the keys fit under it. A search has its own bar for that, the one every [panel over the keys](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) shares. With TalkBack on, the bar offers **Make the panel taller** and **Make the panel shorter** as actions.
### Clipboard and keys together
The keyboard button at the end of the panel's header stacks the clipboard over the keys. The panel shrinks to its header, the tabs if they're on, and about two rows of clips, and the keys come back underneath it, typing into the app as usual. The strip of copied fragments steps aside to leave the clips that room; tap the button again to see it. Paste a clip, type a sentence, paste the next one, without opening and closing the panel in between. The height bar on top now sets how tall the stacked panel is, not the full one: drag it up for more clips over the keys. That height is the one every [panel over the keys](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) shares. Tap the button again to give the clipboard the whole keyboard back. Stacking lasts for that visit only: close the panel and it opens full again next time. This needs **Full-screen clipboard panel** under [Options](#options), which is on by default; the word suggestions don't show while the panel is stacked.
### Grid or list
The square button beside the search box switches the history between the grid and a list. The list gives each clip a whole row: the text across the full width, up to three lines of it, with the pin and delete circles at the end, and a picture as a small thumbnail at the start of its row. It's the easier one to read when your clips are long text. The button shows the view a tap takes you to, and the choice sticks. It's the same setting as **History layout** under [Options](#options). The button is a cell of the [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) (**Grid or list switch** in the component picker), so you can move it or drop it. With the full-screen panel on, it sits at the right end of the header.
### Numbered clips
Turn on **Number the clips** and every clip gets a small number: at the start of its row in the list, and next to the pin in the grid, so a card never gets taller for it. The number belongs to the clip, not to the spot it's drawn in. Search the history and the results keep the numbers they had, so the third clip is still number 3 when it's the only match. Numbers follow the panel's order from the top, pinned clips included. With [the tabs](#pinned-and-unpinned-tabs) on, each tab counts its own clips: the first pinned clip is 1, and so is the first unpinned one.
### Pinned and unpinned tabs
Turn on **Pinned and unpinned tabs** and two tabs sit over the history: **Unpinned**, where the panel opens, and **Pinned**. Each shows how many clips it holds; tap one, or swipe sideways across the tabs, to change tabs. With swipes on the clips turned off, a swipe across the history changes tabs too; with them on, only the tabs take it, so a finger that slips into the gap between two clips never changes the tab by mistake. Pin a clip and it moves across to the Pinned tab, and unpin it to send it back, so the clips you keep for good are never in the way of a fresh copy. Since the clip leaves the tab you're looking at either way, a bar at the bottom says where it went, "Pinned. It is in the Pinned tab now", with an **Undo** that puts it back, so a swipe that pinned never looks like one that deleted. A search looks through both tabs at once, and the tabs step aside while it runs. With the tabs on, **Pinned entries last** has nothing left to do, so it's greyed out. **Keep pinned clips in place** turns the tabs off, since there is no pinned group left to give a tab.
Or leave the history in one list and turn on **Outline pinned clips**: each pinned clip gets a thin border in the accent color, the same color as its lit pin, so it stands out from across the panel and not only from its pin circle.
### Columns, lines and times
Three settings change how much each clip shows. **Grid columns** sets how many columns the grid has, one to four (two by default): more suits a history of short text, fewer gives long text and pictures room. The list is always one clip per row. **Lines per clip** sets how many lines of text a clip shows, up to 20, in both views. Its default, **Auto**, is six lines on a card and three in a row. Longer text ends in an ellipsis. **Time on each clip** puts a small time next to the number: **Time copied** reads "5 min. ago", and **Time left** counts down to when the [expiry](#expiry-and-the-entry-cap) deletes the clip. A pinned clip, or any clip while expiry is off, has no time left to show. The times update while the panel is open.
**Line spacing** sets how far apart a clip's lines sit, from 100% (as tight as the font allows) to 200%, default 130%, so more short lines fit on a card.
However many lines you ask for, a clip's preview is cut before it's laid out: at the line after the last one shown, and never past 4,000 characters. A clip that holds a whole document costs no more to draw than a short one.
### Editing a clip
Press and hold a text clip and tap **Edit** in its popup. An editor opens where the history was, as tall as the panel, with the keys back underneath it, so the keyboard grows upward while you edit. On a phone held sideways it gets whatever room the keys leave. Your typing goes into the clip rather than into the app you're writing in. Enter starts a new line. Tap the text to move the cursor, or use the spacebar and arrow keys like anywhere else. Press and hold a word, or double-tap it, to select it, with Cut, Copy, Paste and Select all over it, or press and hold anywhere past the end of the text, the empty part of the box included, to put the cursor at the end and get **Paste** there. **Save** keeps the change and **Cancel** (or Back) leaves the clip as it was.
The editor, Combine's boxes and the keyboard's other boxes of their own take the keys the way a text field does. Shift re-cases a selection in the box and otherwise capitalizes the next letter, and never reaches the app's field behind the panel. **Holding delete clears words** clears a word at a time here too. Select all, Copy, Cut, Paste, Select word and Undo act on the box, from their keys, toolbar buttons, long presses and flicks alike. A suggestion picked with the cursor in front of a word you didn't touch puts its word in front of that one rather than over it.
An edited clip keeps its place in the history, its number, its pin and its age, so it doesn't jump to the top and push the others around. It does get a small pencil mark to the right of its pin button (or at the end of the card when the buttons are off). The mark is only a label and does nothing when tapped. **Clip shortcut button** under [Options](#options) can hide it, or put a button in its place on every clip: **Edit** opens the editor in one tap, and **Select** starts [picking clips](#picking-several-clips) with that one. The clip's details show when it was last edited, under when it was copied. A few things do change:
- Rich text becomes plain text once you change it, since the old formatting was for the old words. The editor says so before you save. Saving without a change keeps the formatting.
- A link's fetched preview is dropped, and whether the clip counts as a link at all is worked out again from the new text.
- If the new text matches another clip, the two merge into the one you edited, the same way copying something twice never makes two entries.
- If the clip expired while you had it open, saving adds your text back as a new clip instead of losing it.
Images, files, folders and videos have no text to edit, so their popup has no **Edit**. Neither does a [hidden password or code](#passwords-are-handled-deliberately): the panel never shows its text, and an editor would. When the clip you edited is the one on the system clipboard, your edit goes there too, so the **Paste** tool, a long-pressed V and an app's own paste give the new text. Editing an older clip changes only the history.
Every card carries two small circles along its bottom edge: a pin and a delete. Tap the pin to keep a clip out of reach of the expiry and the entry cap. Tap the bin to drop it. Turn off **Pin and delete buttons** under [Options](#options) and the circles go: the press-and-hold popup gets **Pin** (or **Unpin**) and a red **Delete** instead. A card also loses the row the circles sat on, unless that row still has something to show for that card: its number, its time, or its "rich text" tag. So a card with none of those shrinks to just its text, and more of them fit. Swiping a card left or right until it slides off deletes it too (with **Swipe to pin** set, one of the two directions pins it instead), unless you've turned off **Swipe to delete** under [Options](#options): then a sideways swipe does nothing, and the card's press-and-hold popup gets a red **Delete** button instead. Neither the bin nor the swipe asks first for an unpinned clip. A pinned one is different: the bin, the swipe or **Delete** in the popup puts up a question over the history, "Delete this pinned clip?", and only **Delete** there removes it; **Cancel** leaves it, and a swiped card slides back into place. Turn off **Ask before deleting a pinned clip** under [Options](#options) to have pinned clips go at once too. A deleted clip leaves its place empty for the rest of the visit, so the clips above it stay exactly where they were as you scroll back up; the clips below it close the gap. An empty place takes no height, so deleting clip after clip from the top never pushes the rest down. The places go when you close the panel, or change what it shows with a filter, a tag, a tab, the search or the order. Either way, once a clip is deleted a bar pops up at the bottom of the panel for five seconds saying **Clip deleted**, with an **Undo** button that puts the clip back in the spot it had, pin and all. Delete a few more while the bar is up and it counts them ("3 clips deleted"), and one Undo brings back the lot. If you've asked Android for more time under Accessibility, **Time to take action**, the bar stays up that long instead. Swipe the bar sideways to put it away sooner. Once it's gone, swiped or timed out, the delete is for good. Don't want the bar? Turn off **Undo a deleted clip** under [Options](#options).
Deleting a clip also empties the system clipboard when that still holds the clip, so an app's own Paste can't bring it back. Deleting an older clip leaves the clipboard alone, since what's on it is something you copied later. **Undo** puts a text clip back on the system clipboard too, unless you copied something else while the bar was up. A picture or a file comes back to the history only.
### Clearing the history
Turn on **Clear button** under [Options](#options) and a bin sits at the end of the search box, or on its own in that spot while the search bar is off. It's there while at least one clip is unpinned. Tap it and the panel asks first: "Delete 12 unpinned clips? Pinned clips stay." **Delete** takes every unpinned clip at once and leaves the pinned ones where they are. It empties the system clipboard as well, whatever it holds, unless that's one of the pinned clips. **Cancel**, or a second tap on the bin, takes the question away. With **Undo a deleted clip** on, the Undo bar then offers all of them back in one tap. On a hardware keyboard the bin is the search box's neighbour: Enter on it asks, and Enter again deletes.
To see a clip's info popup instead, press and hold its card. The popup gives the time it was copied, its type and size, and (if [source tracking](#options) is on) the app it came from. Under the details sits a row of icons at the right end, the shapes every phone already uses, with **Paste** last, which does what a tap on the card does. Not sure what one of them is? Press and hold it and its name pops up over it; lifting your finger then doesn't run it. The same goes for the buttons in the search box and the header, like the filter icon and the list or grid switch. Every clip gets **Select** (a checklist) to start [picking clips](#picking-several-clips) and **Reorder** to [put clips in your own order](#putting-clips-in-your-own-order). While you search, every clip also gets **Show in history** (a clock): the search closes, the filters with it, and the history scrolls to that clip on its tab, so you see what you copied around it, the way a chat app's Go to message works. The clip is ringed for a moment so it's easy to spot. With the **Duplicates** [filter](#filtering-and-sorting) on, every clip gets **Keep only this one**, which deletes the clips grouped with it, with one **Undo**. Some clips get more:
- A **text** clip opens straight on its whole text, however long, in a box you can scroll, with every web address in it underlined in your theme's accent colour (not with **Keep formatting** off, which shows everything as plain text). Under the text sits the same row of icons at its right end: ⓘ for the details described above (when it was copied, which app it came from, its type and length), select, reorder, edit, tags, QR code, open link, extract parts, pin and delete as the clip and your settings allow, and **Paste**. If a clip offers more icons than fit, the row scrolls sideways. **Order of a held clip's buttons** under [Options](#options) puts the icons in the order you want, and **Paste button on a held clip** can take **Paste** off the row, since a tap on the clip pastes it anyway. Tap anywhere outside to put it away. From the details, the page icon brings the text back.
- A clip with codes, phone numbers, links or places inside it gets **Extract** while [entity detection](#pulling-codes-and-links-out-of-a-clip) is on. With one part found, it pastes that part. With more, it lists them in sections, **Links**, **Places**, **Phone numbers** and **One-time codes**, and a press on one pastes it. A [hidden password or code](#passwords-are-handled-deliberately) has no such button, since the panel never shows its text.
- A **text** or **link** clip gets **QR code**, which opens the [QR code generator](https://wmkeyboard.pages.dev/tools/utilities/#making-a-qr-code) on the clip's text instead of the field's, to send as a picture or change first. A [hidden password or code](#passwords-are-handled-deliberately) never gets it, since the code would show what the panel hides.
- A **text** or **link** clip gets **Search the web** while the [web search tool](https://wmkeyboard.pages.dev/tools/search/) is on, and **Search Wikipedia** while the Wikipedia tool is on. Either opens that tool on the clip's text (its first 200 characters, on one line) and runs the search straight away. A [hidden password or code](#passwords-are-handled-deliberately) never gets them.
- A **link**, a clip that is one web address and nothing else, gets **Open link**, which opens it in your browser. A hidden clip never does, since the browser would show what the panel hides.
- An **image** gets **View**, which shows the picture full screen: pinch or double-tap to zoom, drag to look around, tap or press Back to return to where you were typing. It also gets **Send as sticker**, which runs the picture through the sticker processor before it goes.
- An **image** gets **Translate** while a server is set up under [Translate photos](https://wmkeyboard.pages.dev/tools/camera/#translating-a-photo). The camera opens on the picture and translates the text in it straight away, ready to send, copy or save. It sits just before **Extract text**.
- An **image** also gets **Extract text** while the [text scanner](https://wmkeyboard.pages.dev/tools/scanner/#scanning-text-ocr) is on in your tools. The scanner opens on the picture instead of the camera and reads it straight away, in the language you type in. The words come back as chips: keep the ones you want, then **Copy** puts them on the clipboard, where they become a clip of their own, or **Insert** types them. **Scan again** shows the picture with its language chip, so you can read it again in another language. The back button returns to the clipboard.
### Tagging clips
Press and hold a clip and tap **Tags** (the label icon) to give it labels of your own, such as `work`, `links` or `to read`. The editor opens on the clip's tags as a list with a comma between them, and the tags other clips already carry sit under it as chips, a tap away, so one group keeps one spelling. Enter or **Save** keeps the list; save it empty to take every tag off. Any clip can carry tags, pictures and files included, and they never change the clip's text or its place.
A tagged clip shows its tags under its text, each with a `#`, in your accent colour. The search finds a clip by its tags, with or without the `#`, and every tag in the history gets a chip of its own in the [filter row](#filtering-and-sorting).
### Pinned folders
Turn on **Pinned folders** under [Options](#options) and tags turn into folders for your pinned clips. A pinned clip with a tag leaves the pinned clips and goes into a folder named after the tag, so a set of clips you only paste together, like the questions for one survey, stays out of the way until you need it. The folders sit in a row over the pinned clips, on the Pinned tab when the [tabs](#pinned-and-unpinned-tabs) are on, each with a folder icon and how many clips it holds, in alphabetical order. Tap one to open it: its name sits over its clips, and the back arrow beside the name returns to the rest. Tap a clip in a folder to paste it, as anywhere else.
To file a clip, pin it and [give it a tag](#tagging-clips); to take it out, remove the tag or unpin it. A clip with two tags is in both folders. Pinned clips without a tag stay where they always were, and unpinned clips are never filed, though with the tabs off an open folder shows the unpinned clips carrying its tag too. A search looks through every clip, filed or not. With **Keep pinned clips in place** on, and the tabs off, the folder row sits over the whole history, and a filed clip leaves its place in it for the folder.
### Picking several clips
Press and hold a clip and tap **Select** (the checklist icon). The clip is picked, a tick appears in its corner, and every other clip shows an empty ring: tap a clip to pick it or drop it, instead of pasting it. A bar over the history counts the picked clips and offers what you can do with them. The select-all button beside the count picks every clip on show, and pressing it again clears them. **Delete** removes them all at once, with one **Undo** for the lot. **Combine** joins them into one new clip, below. **Tidy**, there while you have [copy rules](#tidying-copies-with-rules) or **Plain letters** on, runs them over the picked clips, so clips you copied before you wrote a rule get the same treatment; each changed clip keeps its place, as an edit does. **Zawgyi ⇄ Unicode**, there while a picked clip holds Burmese, converts each such clip between Zawgyi and Unicode, whichever way it needs (see [Burmese](https://wmkeyboard.pages.dev/languages/burmese/#converting-text)). The ✕ at the start of the bar stops picking. While you pick, the pin and delete circles and the swipes stand down, so nothing happens to a clip by accident.
### Combining clips
Pick two or more text clips and tap **Combine**. A box opens where the history was, with the keys back underneath. The clips stay in view at the top, a line or two each, with the part each one gives marked in your accent colour and a clip that gives nothing dimmed, so you can write a pattern against them. Under them you set how the clips are joined:
- **Between the clips**: what goes between one clip and the next. A space by default. The chips under it put in a **Space**, a **New line**, a **Comma**, a **Semicolon** or a **Bar** ( | ) in one tap, or type your own. In the box a space shows as ␣ and a new line as ⏎, so a single space is not an empty box.
- **Prefix**: optional text that starts the new clip. It can name a group of the pattern as found in the first clip, like `$1`.
- **Pattern**: optional. A [regular expression](#regular-expressions); each clip then gives only the first part of it that matches, and a clip with no match is left out.
- **Combination**: optional, and only used with a pattern. What each clip's match becomes, with `$1`, `$2` and so on for the pattern's groups and `\$` for a dollar sign. Empty keeps the whole match.
A long pattern wraps onto more lines in its box instead of scrolling sideways. The clips join in the order you picked them, and the **Result** under the boxes shows what you'll get as you type, scrolling when it's long. Press it to edit the result by hand before you save; changing a box after that goes back to what the rule makes. Turn on **Delete these clips after combining** to have the joined clips go once the new one is added, with one **Undo**; otherwise they stay as they were. **Combine** adds the result to the top of the history. The boxes, and the switch, are remembered for the next time.
For example, to join two YouTube links into one line of video ids, pick `https://m.youtube.com/watch?v=Z7pkvfRl5S8` and `https://m.youtube.com/watch?v=A1bcdeFg2H3`, then set the joiner to `; `, the prefix to `youtube.com/watch?v` and the pattern to `=[a-zA-Z0-9_]{11}`. The new clip reads `youtube.com/watch?v=Z7pkvfRl5S8; =A1bcdeFg2H3`. With groups, two links to one owner's GitHub repositories, `https://github.com/wasi-master/wmkeyboard` and `https://github.com/wasi-master/wmkeyboard-addon-repository`, joined with `; `, the prefix `$1: github.com`, the pattern `github\.com/([^/]+)(/[^/]+)` and the combination `/$1$2` read `wasi-master: github.com/wasi-master/wmkeyboard; /wasi-master/wmkeyboard-addon-repository`.
Tap a box to type into it. Enter in the first box and in the result adds a new line, in the prefix moves to the pattern, in the pattern moves to the combination, and in the combination combines.
### Putting clips in your own order
Press and hold a clip and tap **Reorder**. The history turns into a list of the clips it was showing, in the same order: the open tab, the filter you set, newest or oldest first. Each row is two lines tall, with a thumbnail for a picture, and a handle at its start. Drag a clip by its handle, or press and hold it anywhere and then drag, to move it up or down, or tap the arrow at the end of its row to send it to the top. Pinned clips move among the pinned ones and unpinned among the unpinned ones, unless **Keep pinned clips in place** is on, where any clip goes anywhere. A clip you drop in a filtered list lands between the two clips it was dropped between. **Done** goes back to the history.
A clip you moved stays where you put it. One you dragged to the top stays there even as you copy new things, which land just under it, so the clips you always want first stay first. Copying the same text again doesn't move a clip you placed.
tools/clipboard-info-popup — Long-pressing a clip: the info popup shows when it was copied, its type and its size.
## What gets captured
Turning [clipboard history](#options) off stops capture entirely. Even with it on, incognito being active and [set to pause the clipboard](https://wmkeyboard.pages.dev/privacy/incognito/) skips a copy, and that always wins. A copy made while a password field has focus goes into history exactly as you copied it, so you can paste it back into the box, but never as a chip on the strip. What happens to it there follows [Sensitive clips](#passwords-are-handled-deliberately): **Keep it like any other clip**, the default, leaves it readable in the panel, **Hide it and delete it quickly** masks it as dots and deletes it on the short timer, and **Never save it** leaves it out. Both go by the field that has focus when you copy. Once you leave a private-browsing field or a password box, a copy made somewhere else, such as a link copied off a browser's address bar or a new screenshot, is captured as usual.
Clips are stored under seven kinds. The panel groups them like this:
tools/clipboard-rich-media — A file, video, and folder clip side by side. Each keeps only a reference to the source, not the bytes.
- **Text and rich text**: the plain content, shown as-is. A clip that is just one colour code, like `#1e88e5`, `#fff8`, `0xFF1E88E5`, `rgb(30, 136, 229)` or `hsl(210, 79%, 51%)`, gets a small square of that colour in front of it. A copied **link** with no preview is shown as the address itself, as many lines of it as **Lines per clip** allows, with no separate host line; with a preview, the title and description take its place and the address and host sit underneath.
- **Links**: shown as the raw URL, or as a fetched title and description if [link previews](#options) are on. An address counts without `https://` or `www.` in front too, like `x.com`, `youtu.be/…` or `wmkeyboard.pages.dev`, when it ends in a common web ending. File names like `notes.txt` and words like `Node.js` stay text.
- **Images**: copied into the app's own storage, so the source app can discard its copy without losing yours. Also captures device screenshots directly, if you turn that on (not in the Play Store build).
- **Files and videos**: not copied. Only the file's URI, name, MIME type and size are kept, so copying a multi-gigabyte video costs nothing. A video card even shows a frame grabbed from the very start of the clip instead of a blank icon. Copying several files or a whole folder at once keeps up to 20 items from that one copy.
- **Folders**: a reference, same as files. A folder can't be attached to anything, so tapping its card types the folder's name into the field and hands the folder's URI back to the system clipboard for a file manager to use.
### Tidying copies with rules
Clipboard / Copy rules holds rules that change a copy before the history keeps it. Each rule is a pattern, a [regular expression](#regular-expressions) where case doesn't matter, and what to put in place of every match. Leave the replacement empty and the match is removed, which is what most rules are for: `https?://(www\.)?` takes the front off every address, so `https://www.github.com/wasi-master/wmkeyboard/` is kept as `github.com/wasi-master/wmkeyboard/`, and it works inside a longer copy too. A replacement can name a group of the pattern, like `$1`, to add to a match rather than only take from it.
Rules run in the order listed, each on what the one before left. Tap a rule to change it, use its switch to turn it off without losing it, or the bin to delete it. **Change the order**, above the list once there are two rules, lets you drag them into another order. The **Try the rules** box at the bottom shows what a copy you paste into it would be kept as. A clip its app marked as a password is never changed. The rules apply to new copies; to run them over clips already in the history, [select those clips](#picking-several-clips) and press **Tidy**.
**Plain letters**, on the same screen, keeps a copy written in decorated letters as the plain letters they stand for, before any rule runs: the bold, italic, script and double-struck alphabets chat apps and bio generators type with, full-width letters, circled and squared letters, small capitals, and the lines some text generators draw through or under each letter. Symbols like ™ and ℃ stay. It's off by default.
By default the rules change only what the history keeps, and what the keyboard pastes from it. Turn on **Change the system clipboard too** on that screen and a copy a rule changed is put back on the system clipboard as changed, so the paste menu of every app gets the tidy text.
**Remove trackers from copied links**, on the same screen and off by default, takes the tracking parameters off every link in a copy: `utm_source`, `fbclid`, `gclid`, `si` and the rest of the list the selection bar's [Remove trackers](https://wmkeyboard.pages.dev/smart/selection-actions/) uses. A link inside a sentence is cleaned where it stands, and the rest of the text is left alone. Since version 0.6.2 it also knows what the big sites add on their own: Amazon's `/ref=…` and `pd_rd_*`, YouTube's `feature`, X's `s` and `t`, TikTok's share ids, Google search's `ved` and `ei`, and more. Those names come off only on that site, so a `t=42` elsewhere stays. A redirect link is unwrapped to the page it leads to, as with Google's `/url?q=`, Facebook's `l.php?u=`, Outlook safe links and Steam's link filter. If a cleaned link stops working, press **Undo** straight after pasting it: the clean link is swapped for the one you copied, which also goes back on the clipboard. The clean copy always goes back on the system clipboard, whatever the setting above says, because a clean paste is the point. **Your own tracking parameters** adds names to that list, one on each line, for both this and the selection bar. End a name with `*` to take a whole family, so `abc_*` removes `abc_id` and `abc_src`.
### Pasting rich media
Tapping an image, file, or video card negotiates a MIME type with the focused field. The keyboard offers the clip's own type, and sends it via `commitContent` when the field advertises that it takes one. If the field wants PNG but the clip is a static WebP, it converts on the fly. Animated WebP is left alone, so an animation is never silently flattened into a still. If nothing matches, a picture goes where the gallery's [When the app does not take pictures](https://wmkeyboard.pages.dev/tools/gallery/#when-the-app-does-not-take-pictures) sends it: by default Android's share screen opens for the app you were typing in, **Copy them to the clipboard** puts it back on the system clipboard to paste, and **Ask each time** opens Android's share sheet, which offers both. A file or video goes back onto the system clipboard and a toast tells you to paste it there. That happens when the field takes no rich content at all, or when the permission to read the file has lapsed.
### Expiry and the entry cap
Unpinned clips fall off two ways. Clipboard sets an expiry in hours, where 0 turns expiry off. It also caps how many unpinned entries history keeps at once, and once that cap is hit the oldest unpinned clip drops with each new copy. Set the cap to **Unlimited** and only the expiry removes clips. Pinning a clip exempts it from both. A pinned clip never expires and never counts against the cap.
A third limit is on the length of each clip, and it starts off. **Longest clip kept** keeps all of what you copy by default. Set it (1,000 to 100,000 characters; Gboard cuts at 20,000) and a longer copy is stored cut to that length, so a copied book doesn't fill the history and every backup of it. Rich text that gets cut is stored as plain text, since its formatting no longer matches. Only the history is cut: the system clipboard still holds the whole copy, so an app's own paste is unchanged.
## Finding a clip again
Turn on the search bar and a search pill appears at the top of the panel, once you have at least one clip. Tap it and your keystrokes go to the search field instead of into your document, the same trick the emoji panel uses. The panel shrinks to just the field and its results while you type, and the bar on top drags it taller if a couple of rows of results isn't enough. The back button stays at the start of the row, where the header had it, and closes the search; the number at the end counts the clips the search and the filters leave. Results are listed newest first, pinned or not, so the first one is your latest matching copy.
To see more results than fit over the keys, put the keys down: press Back, or tap the keyboard button at the right end of the search's suggestion strip. The query stays and the results fill the panel; tap the search field to type again. The clip editor and Combine's boxes work the same way. See [Putting the keys down](https://wmkeyboard.pages.dev/tools/overview/#putting-the-keys-down).
Matching is a case-insensitive substring search, and it reaches past a clip's own text, to its [tags](#tagging-clips) among other things. A clip with no text of its own is still found by what it is: "image", "picture" or "screenshot" for a copied picture, "video", "movie" or "recording" for a clip, "folder" for a folder. The file format works too, because all of that is indexed alongside the file name, the source app and any link preview's title and description.
With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, the clips whose text means what you typed come after these matches, best first, and with pictures on, so do the screenshots and images that show it. "plane ticket" finds "Flight AA102 departs from gate 23", which shares no word with it. Clips marked as passwords are never read for it.
### Filtering and sorting
The filter icon at the end of the search pill opens a row of chips under it, without starting a search. The first chip flips the history between **Newest first** and **Oldest first**. The rest show only some clips: **All**, **Pinned**, **Unpinned**, **Text**, **Links**, **Images**, **Files** or **Duplicates**. **Duplicates** shows clips whose text is the same as another's but for capital letters and spacing, `GitHub` and `github` say, each group side by side; press and hold the one to keep and tap **Keep only this one**. Your [tags](#tagging-clips) have a row of their own under that one, one chip each, most used first: tap one to see only the clips that carry it, tap it again to see only the clips that don't (the chip then reads **Not #tag**), and a third time to let go. With a kind chip such as **Unpinned** set as well, that is every unpinned clip without the tag. A filter works with or without a search, and on top of the [tabs](#pinned-and-unpinned-tabs). While anything is set the row stays up, so a short history always shows why. The panel remembers the order and the kind for as long as the keyboard runs, so the next search opens filtered the way you left it. A tag lets go when you close the clipboard.
### Regular expressions
The `.*` button beside the search pill, or **Regular expressions** under [Options](#options), turns on pattern search, and the button is lit while it's on. The search then reads what you type as a pattern instead: `^https` finds clips that start with a web address, `\d{6}` six digits in a row, `invoice|receipt` either word. Case still doesn't matter, and `^` and `$` hold on to the clip's own text, not the words it's indexed under. A pattern you haven't finished yet, such as an open bracket, searches as plain text until you have, so the results don't vanish halfway through typing it.
### Searching your notes too
With **Search notes too** on, which it is by default, the search also looks inside your [notes](https://wmkeyboard.pages.dev/tools/notes/). Once you've typed two letters or more, the paragraphs that hold the match come first, under **From notes**, and the clips follow under **Clips**. Tap a passage to type it into the field as the note has it; press and hold it to open the note in the Notes panel. A passage is the paragraph around the match, cut down to the line or the sentences when the paragraph is long, three to a note and twelve in all. The number at the end of the search row counts them with the clips.
The search's tools work on notes the same way. With the `.*` button lit, a pattern finds passages too, and `^` and `$` hold on to each line of the note. The tag chips include your notes' tags, so a note tagged **Quotes** shows up under the **#Quotes** chip next to any clip with that tag, and **Not #Quotes** leaves it out. **Text** keeps the passages, while **Pinned**, **Unpinned**, **Links**, **Images**, **Files** and **Duplicates** hide them, since none of those apply to a note. Locked notes and the trash are never searched. That lets you keep groups of related text together in one note, a set of replies or a list of addresses, and find any of it from the clipboard without opening Notes.
## Pulling codes and links out of a clip
Turn on entity detection and the panel scans your recent clips for one-time codes, phone numbers, links and places. A link is found with or without `https://` in front, so `x.com` or `m.youtube.com/watch?v=…` in a message gets a chip too. A place is a pair of coordinates, a Plus code or a `geo:` link; a map link stays a link. The [Location tool](https://wmkeyboard.pages.dev/tools/location/#places-somebody-sent-you) reads both kinds and can turn them into an address, a different map's link, a saved place or a compass bearing. Each one becomes a dashed-outline chip above the history grid, so the six digits inside a verification text are one tap away rather than a copy-select-paste. A chip is one line: the kind's icon and tag, then the part itself. Tapping a chip pastes just that fragment and leaves the full clip in place. Press and hold a chip to see it highlighted inside the message it came from.
The ✕ at the end of the chips folds the strip away, and it stays away. A scissors button beside the search, with a count of the parts found, brings it back.
With **Show the found parts as icons on each clip** on, the chips give way to icons on the clips themselves: a link, a pin, a phone or a code icon at the end of each card's bottom row, for each kind found in it, with a small number when there's more than one. Press an icon to paste its part, or, when it counts more than one, to pick from the list. Every clip shows its own parts this way, not only the newest few the chip strip has room for.
With **Copy picked parts to the clipboard** on, a part you pick, from a chip, an icon or **Extract**, is copied as a clip of its own instead of typed, ready to paste wherever you need it.
With **Show the found parts on the suggestion strip** on, you don't have to open the panel at all. Copy something with a code, a number or a link in it, and the suggestion strip shows the whole copy as a chip followed by a dashed chip for each part, the way FlorisBoard and FUTO Keyboard do. Tap a part to paste just it. The chips hold the strip until you type something or press the ✕ at its end; after that the words come back, with the usual chip for the copy beside them. It's off by default.
tools/clipboard-entity-chips — Entity chips above the history grid: an OTP and a phone number pulled out of a longer message.
Detection is conservative on purpose. A 4-digit year is never mistaken for a code, and a run of digits after a currency symbol or a `#` is never mistaken for a phone number. Google's `G-123456` verification prefix is stripped, so only the code itself pastes. Unless **Show the found parts on the suggestion strip** is on, pulling a fragment out of a longer message is the panel's job. The suggestion strip only ever offers a copied code when the clip *is* the bare code, which is the **Suggest copied codes** rule under [Options](#options).
Phone numbers are the one fragment with no shape of their own, so they get an extra setting. Without it, anything with 7 to 15 digits counts, which is how an invoice total or a tracking number ends up wearing a phone chip. The list starts with one format for your own country, worked out from your phone's region. Open **Phone number formats** from the clipboard settings to change it, or to add the second country you copy numbers from. Give it a number you copy often. The screen turns that into a format like `+880 1XXX-XXXXXX`: it keeps the dial code and makes the rest wildcards, and you can type digits back over a wildcard to pin it. After that, only numbers matching one of your formats become chips.
The matching is forgiving about the things people vary and strict about the things they don't. Spaces, hyphens, brackets and dots are ignored on both sides. The same format accepts the number with its country code, without it, or with a leading 0 in its place. What it won't accept is the wrong length, the wrong leading digit, or an explicit `+` naming a different country. There's a test field on the screen, so you can paste a number in and see the answer before you trust it. Add as many formats as you like, one per country you deal with, and delete them all to go back to catching everything. [Phone number formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/) is the field reference for that screen: the mask characters, the length bounds, and every bit of leeway spelled out.
This entity strip is a different mechanism from [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), which reacts to what you're currently typing rather than to what you've copied. The two look alike, both dashed outlines, but they share no code.
## Passwords are handled deliberately
The clipboard tool can treat a copied password or code as sensitive in two independent ways: at copy time, and at paste time. Both are off out of the box, so a password you copy is kept, shown and pasted like any other clip. They're here for when you'd rather it wasn't.
**At copy time**, Clipboard / Passwords and codes chooses what happens to a clip Android itself marks sensitive. That's the flag a password manager sets when you copy a login. You can keep it like any other clip (the default), hide it and forget it quickly, or never save it at all. Hiding and forgetting is the one to pick if you want this at all. Never saving would trade away the everyday "I pasted a password into the wrong field" recovery, and it closes a risk a short timer already closes. Turn on recognizing sensitive clips yourself and the same handling covers anything that merely *looks* like a password or a bare code. That's for managers older than the flag, or a code you copied by hand, and it's narrow enough that a sentence, a link or an email address never qualifies.
A hidden clip shows as a row of dots and a character count in the panel, and it's never revealable there. It's also left out of the strip's paste chip (except in a password field with [Offer copied passwords in password fields](#paste-chip-and-codes) on, and then shown as dots), the entity strip and the settings backup. It expires on its own timer, independent of the ordinary history expiry above and not capped by it. Pinning a sensitive clip overrides all of that, the same as pinning anything else. Switch back to keeping them like any other clip and the clips that were hidden turn back into ordinary ones.
**At paste time**, turn on forgetting after a password paste. The clip is then deleted from history the moment you paste into a password field, and from the system clipboard too if that still holds exactly what you pasted. Every paste route fires it: a tap on a card, the strip's paste chip, a pasted entity chip, a long-pressed V, the text-editing panel's Paste, and a hardware Ctrl+V. So the rest of an SMS can't sit on the clipboard after you've pasted only its code.
**When the app wipes it**, the history lets it go as well, whatever you chose above. A password manager that marks its copies sensitive usually empties the clipboard a little later; [WM Pass](https://wmkeyboard.pages.dev/tools/wm-pass/) does it after the time set in its own settings. If the clipboard is emptied while it still holds that copy, the clip leaves the history and the strip's paste chip goes with it. A clip the keyboard hid as sensitive itself goes the same way. A pinned clip stays. So does one you'd copied something else over before the wipe, because the manager had nothing of its own left to wipe; it then keeps to the rules above.
The clipboard is already out of reach while the lock screen is showing, and no setting controls that. History reads as empty, the paste chip is cleared, and the panel refuses to open. Hiding the whole top strip there, suggestions and toolbar together, is a separate [Appearance](https://wmkeyboard.pages.dev/reference/settings/appearance/) setting and not part of this group.
## Options
Clipboard
Everything below lives on the Clipboard screen and its three pages, under Features on the settings home screen, rather than on the tool page. Your copies land in the history whether or not the panel's button is on the toolbar, so these aren't really the tool's settings.
tools/clipboard-settings — The Clipboard screen before it became a hub: the History group and, below it, Passwords and codes.
The Clipboard screen is a hub. Three rows open its pages, and the Panel group sits under them as a fold, closed until you open it.
- **Clipboard history**: the master capture switch, on by default, on the same row as the door to the [Clipboard history](#clipboard-history) page below. The switch flips here; the rest of the row opens the page.
- **Paste chip and codes**: opens the [page](#paste-chip-and-codes) of what the history puts on the suggestion strip.
- **Rules and privacy**: opens the [page](#rules-and-privacy) with the copy rules, the phone-number masks and how passwords and codes are handled.
**Panel**, in the order the rows appear:
- **Panel layout**: opens the panel's [layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) in the layout editor. The search box, the grid or list switch, the strip of copied fragments and the history are cells you can move and resize, and any key can join them: an abc, space and backspace row along the bottom, say, which is what the old "Bottom control row" switch used to add. A layout made with that switch on carries over.
- **Full-screen clipboard panel**: on by default, so this is what the panel does unless you say otherwise. The history takes the whole keyboard, and a slim back header stands in for the toolbar, the emoji row and the symbol row. Picking the right clip gets easier the more of them are on screen, which is why that trade is the default. Start a search and the panel drops back to its compact size so the keys fit underneath. Turn it off to keep the toolbar visible while the panel is open.
- **Panel height**: how much taller than the keyboard the panel opens, up to 600 dp more, default **Same as keyboard**. The same setting as the [bar on top of the panel](#making-the-panel-taller).
- **Height over the keys**: how much taller the panel opens while the keys are under it, stacked or searching, up to 400 dp more, default **Compact**. Every [panel over the keys](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) shares it, and dragging the bar on any of them sets it.
- **ABC, space and enter keys**: off by default. Adds a row along the bottom of the panel: ABC back to the letters, a space bar, and enter. Paste a link and press enter to open it, or paste several clips with a space or a new line between them, without going back to the keys each time. Enter shows the field's own action, Go or Send, the way it does on the keyboard. The row steps aside while the clipboard is [stacked over the keys](#clipboard-and-keys-together), since the real keys are right there. It only changes the panel as it ships: if you've edited the **Panel layout**, add the keys there instead.
- **History layout**: **Grid** (default) or **List**, the same choice as the [switch in the panel](#grid-or-list).
- **Grid columns**: 1–4, default 2. Shown only while the history is a grid.
- **Lines per clip**: **Auto** (default: six on a card, three in a row) or 1–20.
- **Line spacing**: 100% to 200%, default 130%. How far apart a clip's lines sit.
- **Time on each clip**: **None** (default), **Time copied** or **Time left**. See [Columns, lines and times](#columns-lines-and-times).
- **Number the clips**: puts a [number](#numbered-clips) beside every clip; off by default.
- **Pinned and unpinned tabs**: splits the history into [two tabs](#pinned-and-unpinned-tabs); off by default.
- **Outline pinned clips**: a thin accent border around each pinned clip; off by default.
- **Pin and delete buttons**: the pin and bin circles on every clip; on by default. Off, **Pin** and **Delete** move into the clip's press-and-hold popup, and a card with no number, time or rich text tag loses its bottom row.
- **Clip shortcut button**: the small button at the end of a clip's bottom row. **Edited mark** (default) is the pencil on an [edited clip](#editing-a-clip), which does nothing when pressed; **Nothing** hides it; **Edit** puts an edit button on every clip you can edit; **Select** starts picking clips with that one; **QR code** puts a QR button on every text clip, which opens the [QR code generator](https://wmkeyboard.pages.dev/tools/utilities/#making-a-qr-code) on the clip's text, ready to send as a picture.
- **Order of a held clip's buttons** (advanced): drag the icons under a held text clip into the order you want, first at the left. An icon the clip doesn't offer is skipped wherever it stands.
- **Paste button on a held clip** (advanced, on by default): the **Paste** button at the end of that row. Turn it off for more room; a tap on the clip still pastes it.
- **Type out pasted clips**: pastes a clip one character at a time, as if typed, the way a one-time code goes into a row of code boxes; off by default. A clip longer than 200 characters is pasted all at once. Bare codes are typed out either way, under the control of the One-time codes setting.
- **Move a pasted clip to the top**: a clip you paste from the panel or the strip goes first in the history, as if you had copied it again, and back on the system clipboard so an app's own Paste gives it too; off by default. A pinned clip goes first among the pinned ones. Nothing is moved by a paste into a password field, and a hidden password or code, or anything pasted while the keyboard's clipboard is [kept private](https://wmkeyboard.pages.dev/privacy/incognito/#keeping-copies-off-the-system-clipboard), stays off the system clipboard.
- **Rich text tag**: the small "Rich text" label under a clip that was copied with formatting; on by default. Off, the label goes, and a card with nothing else on that row loses the row.
- **Keep formatting**: on by default, so a copy that came with bold, links or other formatting pastes it back into apps that take it. Off, every copy from then on is stored as plain text, pastes as plain text everywhere, and never wears the tag. The panel then draws every clip as plain text too: a link loses its colour, its underline and the icon in front of it. Clips already in the history keep what they have.
- **Swipe to delete**: a sideways swipe across a clip deletes it; on by default. Off, the swipe does nothing and a **Delete** button joins the clip's press-and-hold popup. The bin works either way, while **Pin and delete buttons** is on.
- **Swipe to pin**: shown while swipes are on. **Both ways delete** by default. **Right pins, left deletes** or **Left pins, right deletes** part the two directions: one pins or unpins the clip and lets it slide back into place, the other deletes.
- **Undo a deleted clip**: shows the Undo bar for a few seconds after you delete a clip with the bin or a swipe; on by default. Off, a delete is immediate and final.
- **Ask before deleting a pinned clip**: deleting a pinned clip, by the bin, a swipe or the press-and-hold popup, asks first; unpinned clips still go at once. On by default.
- **Search bar**: enables the [search pill](#finding-a-clip-again) described above; off by default.
- **Regular expressions**: shown while the search bar is on, off by default. On, the search reads what you type as a [regular expression](#regular-expressions). The same switch as the `.*` button beside the search.
- **Search notes too**: shown while the search bar is on, on by default. The search also [lists passages from your notes](#searching-your-notes-too) above the clips.
- **Clear button**: the [bin](#clearing-the-history) that deletes every unpinned clip after asking, and empties the system clipboard unless it holds a pinned clip; off by default.
- **Link previews**: fetches a copied link's page title and description directly from the linked site; off by default, and the toggle's own description says so plainly.
- **Pop-up when you copy**: off by default. It lives in the Panel fold but it isn't a panel setting, or a history one, at all, see [below](#details--edge-cases).
### Clipboard history
Clipboard / Clipboard history
Three groups: Limits, Pinned clips, and Screenshots and the source app. The last only shows while the history is on, since nothing is stored to carry a screenshot or a source without it.
- **Maximum entries**: how many unpinned clips history keeps, from 5 to 5,000 in steps, default 100, or **Unlimited** at the end of the slider. Unlimited leaves the expiry as the only limit; with the expiry set to never as well, nothing is deleted, and a very long history makes the panel slower to open.
- **Clipboard expiry**: hours before an unpinned clip is removed, 0–168, default 24 hours (0 means never).
- **Longest clip kept**: **No limit** (default), or 1,000 to 100,000 characters in steps; a [longer copy is stored cut](#expiry-and-the-entry-cap).
- **Pinned entries last**: moves pinned clips to the end of the panel instead of the top; off by default. Hidden while the tabs are on or pinned clips are kept in place.
- **Keep pinned clips in place**: pinned clips stay where they were copied, among the others, wearing their pin; off by default. With it on, the tabs and **Pinned entries last** have no effect.
- **Pinned folders**: files each tagged pinned clip in a [folder per tag](#pinned-folders); off by default. With pinned clips kept in place, the folders sit over the whole history.
- **Your screenshots**: shows device screenshots in the panel alongside copied text and images; off by default. While it's on without the photos permission granted, a row appears underneath to open system settings. Screenshots start arriving the next time the keyboard opens after you grant it, without turning the row off and on. The Play Store build has no such row: Google Play only allows the photos permission to apps that can't use the photo picker for their main job, so that build doesn't declare it, and screenshots reach the clipboard only the usual way, by copying one. GitHub and F-Droid builds keep it.
- **Show the source app**: records which app a clip was copied from, shown as that app's small icon at the start of the clip's bottom row and by name in the long-press info popup; off by default, and needs the Usage Access permission. It's a best-effort guess of the foreground app at copy time, not a precise log. It gets the same "grant this" row underneath until you do.
### Paste chip and codes
Clipboard / Paste chip and codes
Two groups, Paste chip and Codes, numbers and links. While the history is off the page shows a notice and a **Turn on** button in place of its rows, since nothing is kept to suggest from.
- **Suggest the last copy**: offers your latest copy as a paste chip on the suggestion strip. On by default. The chip is as wide as what it holds, so a short copy is a short pill with its ✕ right beside it; a long one stops at about half the strip and ends in an ellipsis. While you type it keeps its narrow width for a moment after the words leave the strip, so it doesn't flicker between the two sizes as suggestions come and go. Press and hold the chip to delete that copy from the history, for something copied by mistake or needed only once.
- **How long the paste chip stays**: 30 s to 30 min, default 5 minutes, or **Until dismissed** at the bottom of the slider. Shown only while "Suggest the last copy" is on. Typing never dismisses the chip, since it's meant to survive writing the message you'll paste into. Shorten it if you'd rather a copied password or address stopped being on screen.
- **Recent copies on the strip**: 1 to 5, default 1, shown only while "Suggest the last copy" is on. At 1 the strip offers the single paste chip above. Set more and the chip gets a clock button beside its ✕. Tap it and the strip shows that many of your latest text clips, newest first whatever is pinned, as a row of chips you can scroll sideways, each a tap from pasted. The row never opens by itself, and it stays up until you type, tap its ✕, or move to another field, so you can paste several in a row. Press and hold a chip in it to delete that copy. Hidden (sensitive) clips never appear in it. The **Recent copies** action opens the same row from a gesture: bind it to the Copy tool's hold in that tool's settings, or to a flick or a key's long press, and the last few copies are one press away. It's under **Other** in those pickers, not on the toolbar. With this set to 1, the action shows five.
- **Compact paste chip**: off by default, shown only while "Suggest the last copy" is on. With nothing else on the strip, a long copy stretches the chip to about half the row, which leaves no room for the tools beside it. On, the chip always keeps the narrow form it takes next to suggestions, so the rest of the row stays in reach while you write.
- **Suggest copied codes**: **Any field** by default, and only shown while "Suggest the last copy" is on. A code on its own is otherwise treated as a secret and kept off the strip, since the chip sits in view while you type something else. A code you copied by hand is the case that rule was never aimed at: you copied it to paste it, and it sits one tap away in the clipboard panel either way. **Code boxes only** narrows it to fields that ask for a code (a number keypad, or a name like "OTP" or "verification code") if you'd rather a code never showed up over the keys of a message, and **Never** keeps the strip clear of codes entirely. Only a bare code qualifies at any setting. A copied password is never offered, unless you turn on the next option. Since 0.6.6 a copied code also shows in a password field, where the password manager chip used to cover it.
- **Offer copied passwords in password fields**: off by default, shown only while "Suggest the last copy" is on. It changes a privacy rule, which is why you have to ask for it. With it on, a password field, and no other field, offers your newest copy that looks like a password or a code as a paste chip for five minutes after you copied it. The chip shows dots instead of the password, since the field hides what you type and the chip shouldn't undo that, and it disappears the moment you move to any other field. While it's up it takes the place of the password manager chip. It can only offer a copy the clipboard kept, so with **Passwords and codes** set to never save, there's nothing to offer.
- **Detect codes, numbers and links**: enables the [entity chips](#pulling-codes-and-links-out-of-a-clip); on by default. Detection runs entirely on the device.
- **Show the found parts as icons on each clip**: off by default, and only while detection is on. Icons with counts on each clip in place of the chip strip.
- **Copy picked parts to the clipboard**: off by default, and only while detection is on. A picked code, number or link is copied rather than typed.
### Rules and privacy
Clipboard / Rules and privacy
**Rules**:
- **Copy rules**: a nav row to the [rules that tidy every copy](#tidying-copies-with-rules). Its subtitle counts your rules.
- **Phone number formats**: a nav row, shown only while detection is on, opening the mask editor covered in [Phone number formats](https://wmkeyboard.pages.dev/reference/settings/phone-formats/). Its subtitle counts your saved formats, and says every number becomes a chip once you have deleted them all.
**Passwords and codes**:
- **Keep the keyboard's copies private**: off by default. On, text you copy or cut with the keyboard's own keys, tools and panels never goes to the system clipboard, where any app can read it. It goes into the history instead, and the keyboard's paste keys and the panel paste it, until you copy something anywhere else or delete that clip from the history; then the paste keys paste from the system clipboard again. The copy and paste menu of other apps doesn't see it. Since Android's "Copied" bubble can't appear for a copy it never saw, the keyboard shows its own sign instead: the selection is let go with the cursor left at its end, and the copied text comes up as the paste chip on the suggestion strip, as a system copy does. That chip holds the whole strip until you type a key or move to another field, over any words and any smart chip, so it is always there to see after the copy. It's the always-on form of [Keep copies in the keyboard](https://wmkeyboard.pages.dev/privacy/incognito/#keeping-copies-off-the-system-clipboard), which holds only while incognito is on and records nothing in the history.
- **Forget after you paste a password**: the paste-time purge described [above](#passwords-are-handled-deliberately); off by default.
- **Sensitive clips**: keep (default), hide-and-forget, or never save. See [above](#passwords-are-handled-deliberately).
- **Let the keyboard find them**: extends sensitive handling to clips that merely look like a password or code; on by default, shown only when sensitive handling isn't set to "keep."
- **Forget sensitive clips after**: the hidden-clip timer, 1–120 minutes, default 5; shown only under the hide-and-forget option.
Tools / Clipboard
The tool page carries only what belongs to the button:
- **All clipboard settings**: opens the screen above.
- **Enabled**: whether Clipboard appears on the toolbar and in the toolbox.
- **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) is turned on globally. Overrides just this tool's icon color.
- **Keyword shortcut**: the words that make the [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) suggestion offer to open this panel.
## Backups and exports
The whole-app [config backup](https://wmkeyboard.pages.dev/privacy/backup/) has its own Clipboard section, off by default when you export. It only ever includes pinned and recent *text* clips. Images and files are deliberately left out, since they live on the device they were copied on and wouldn't open anywhere else. Sensitive clips are excluded from an export regardless of that toggle.
## Details & edge cases
- Every toggle and slider on this screen carries the usual [restore button](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back) once you've moved it off its shipped value, so the rows that grew one are the ones you've changed.
- The panel stays open when the app changes the text of the field you're in by itself, such as a browser address bar's clear button, or the pencil that copies the page address into the bar for editing. Moving to a different field still closes it.
- The clipboard tool needs the device to have been unlocked at least once since restart. Its history lives in credential-encrypted storage, so it sits out any lock-screen or pre-unlock typing until then.
- Ships the same way in every build. On the F-Droid build **Extract text** reads with PaddleOCR, so it reads Chinese, Japanese and Latin-script text there, and other scripts only with the scanner's Online model reader.
- Link previews make a direct, unauthenticated request to whichever site you copied a link from, not through any of the app's own network services. Turn that setting on and the site sees a request from your device the moment you open the panel with a fresh link in history.
- A file or folder clip is only as good as the URI permission grant behind it. If that's lapsed by the time you tap it (the source app cleared its cache, for instance), pasting puts the URI back on the system clipboard with an explanatory toast rather than failing silently.
- "Pop-up when you copy" appears in this same screen's History group but is actually a [feedback](https://wmkeyboard.pages.dev/typing/feedback/) setting. It's about the keyboard's own copy shortcuts (A/C/V/X and the text-editing panel's copy button), not clipboard history. It only fires in fields that give no copy feedback of their own, and it's off by default.
- Pausing capture while [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) is on is a separate Incognito setting, not part of this screen. So is [keeping the keyboard's copies off the system clipboard](https://wmkeyboard.pages.dev/privacy/incognito/#keeping-copies-off-the-system-clipboard) while incognito is on; **Keep the keyboard's copies private** on this screen does the same all the time.
---
# Clock
URL: https://wmkeyboard.pages.dev/tools/clock/
Section: Tools
Summary: World clocks, alarms, timers, a stopwatch, a meeting planner, date maths and a history, in one keyboard tool that also types any time in the form you need.
The **Clock** tool is a full clock app that lives in the keyboard. It keeps the time in the cities you care about, rings alarms and timers, runs a stopwatch, finds a meeting time that suits people in several time zones, counts days between dates, and keeps a history of all of it. And because it sits on a keyboard, every time it shows can go into the text you are writing, in the form you pick.
Open **Clock** from the toolbox, or type `clock`, `alarm`, `timer`, `stopwatch`, `countdown`, `planner` or `date` on its own and tap the chip that appears. The panel has seven tabs along the top: **Clocks**, **Alarms**, **Timers**, **Stopwatch**, **Planner**, **Dates** and **History**. A dot on a tab means something is running there, and a number counts the alarms that are on or the timers that are running. The panel opens on the tab you used last. Swipe left or right across the panel to move to the next or previous tab; the highlight slides from one tab to the next, and the row scrolls so the tab you landed on stays in view. The gear at the end of the row opens the tool's settings.
Every tab is laid out the same way: what matters most is large at the top, the rest is cards and tiles, and the one thing to do is a bar along the bottom with the main action as a wide button and the other ways beside it as round ones. Hold a round button to see its name. Cards and tiles pop in one after another when a tab opens; with **Reduce motion** on they appear at once.
The settings app has a **Clock** screen too, under Tools / Clock / Open the clock. It works on the same alarms, timers, stopwatch, cities, countdowns and history, with a full-size time picker and a sound picker. A change made in one place shows in the other at once. **Add to home screen**, under **Open the clock**, puts an icon for that screen on your home screen, so it opens like a clock app.
## World clocks
The **Clocks** tab starts with your own time, large, with the date, the city your phone's time zone is named after, and a small clock face. The face is light by day and dark by night, and under the time are today's sunrise and sunset.
Under it, the cities you add ride a carousel of tiles that scrolls sideways. A tile is tinted by the daylight there: warm, in the keyboard's accent, where it is day, and dusky where it is night, with a sun or a moon in its corner and a stripe down its side in the same mood, so you can see at a glance who is awake. Each tile shows the city's name, its time, whether it is today, tomorrow or yesterday there, how many hours it is ahead of or behind you (`+5:30`, `−3`), and its sunrise and sunset. The last tile is **Add a city**.
**Add a city**, the wide button at the bottom or the last tile, opens a search, and the keyboard types into it. Type a city (`tokyo`, `sao pa`), a country, a zone abbreviation (`ist`, `pst`) or an offset (`+5:30`, `utc-3`). Accents don't matter. The list comes from the time zone data already on your phone, about 420 places, in your language where Android has a name for them. That data names each zone after one city, so about 60 big cities that share a zone with another are added by name: Beijing, Mumbai, New Delhi, Bengaluru, Osaka, San Francisco, Washington, Mecca and the like, in English, with older spellings such as `bangalore` or `bombay` finding them too. Each one keeps its own name on its card, and its own sunrise and sunset. Nothing is looked up online. Enter adds the first match.
Hold a city tile for its own row of chips under the carousel: type its time in the default format, move it left or right (unless **Sort cities by time** is on, which keeps the carousel in order of the time there, earliest first), rename it (to `Mum` or `Office`, say), or remove it. A removed city has an **Undo** for a few seconds.
### Typing a time
Tap your own clock or any city tile, and a row of chips opens under it (under the carousel, for a city). Each chip shows exactly what it will type, so you pick by looking:
| Format | Example |
|---|---|
| Time | `14:05` or `2:05 PM` |
| Date and time | `Wed 14 Oct, 14:05` |
| Full date | `Wednesday, 14 October 2026` |
| Time and place (the default) | `14:05 Tokyo (UTC+9)` |
| ISO 8601 | `2026-10-14T14:05:00+09:00` |
| Relative | `in 3 hours`, `2 days ago` |
| Unix time | `1791000000` |
Under those is a row for Discord timestamps. Discord turns `` into a time in each reader's own time zone and language, so one message works for everyone in the server. Every style Discord has gets a chip, showing roughly what readers will see:
| Discord style | Typed | Readers see |
|---|---|---|
| Short time | `` | `10:23 PM` |
| Long time | `` | `10:23:18 PM` |
| Short date | `` | `10/18/2026` |
| Long date | `` | `October 18, 2026` |
| Date and time | `` | `October 18, 2026 at 10:23 PM` |
| Full date and time | `` | `Sunday, October 18, 2026 at 10:23 PM` |
| Relative | `` | `in 2 hours`, kept current as the message ages |
| Numeric | `` | `10/18/2026, 10:23 PM` |
| Numeric with seconds | `` | `10/18/2026, 10:23:18 PM` |
Every chip types the time now unless you pick another. **Another time** above the chips opens a time wheel set to the next whole hour, with arrows to step the day: tomorrow, next week, or a date you have to remember. The time is on the clock you tapped, so tapping the Kolkata card and picking 12:00 tomorrow gives 12:00 tomorrow in Kolkata, and a Discord chip then shows it to a reader in Karachi as 11:30. **Now** goes back to the current time.
Hold a chip to make it the default. Holding a Discord chip makes Discord the default in that style. After that, holding your own clock types the default straight away, without opening the chips, and the default is the first chip when you hold a city. **Insert all clocks**, the round button beside **Add a city**, types one line with every clock: `14:05 London = 9:05 New York = 18:35 Kolkata`. A city whose date is different from yours gets its weekday, so nobody turns up a day early.
## Alarms
The **Alarms** tab is a card per alarm: its time large, its label, the days it repeats as seven round pips (filled on the days it rings) and a switch. The alarm that rings next carries a line saying how long until it does ("Rings in 7 hr, 12 min"). An alarm with no days rings once, then turns itself off, like on any phone's clock. Tap a card to change it. Hold it for **Skip next**, which skips only the next ring of a repeating alarm, **Move to phone's clock**, and **Delete**, which has an **Undo**.
**Move to phone's clock** hands the alarm to your phone's own clock app, with its time, days, label and vibration, and takes it off the keyboard's list (with an **Undo**, which only brings it back here). From then on it rings from the phone's clock app like any alarm you set there. **Phone's alarms**, beside **New alarm**, opens that app's list of alarms. The keyboard can't read the alarms in the phone's clock app, so they never show on this tab. Handing an alarm over uses an install-time permission Android grants without asking, and nothing reads or changes the phone's alarms otherwise.
Along the bottom, **New alarm** is the wide button, and above it a row of quick alarms for 10, 20 and 30 minutes and for one and one and a half hours from now. A quick alarm is set with one tap.
The editor puts the time on drums across the top that you spin with a finger, with a small tick at each step. Under them, a line says when the alarm will ring as you change it, then come the seven days, and one row with shortcuts for **Weekdays**, **Weekends** and **Every day**, a label, a vibration switch and the snooze length for this alarm. **Save** is the wide button at the bottom, with **Cancel** and **Delete** as round ones beside it.
### When an alarm rings
The phone lights up with the ring screen, also over the lock screen. It shows the time, the alarm's label and a pulsing bell. **Snooze** is the big button; smaller chips under it snooze for 5, 10, 15 or 30 minutes instead. To dismiss, slide the thumb across the bar at the bottom. A slide rather than a tap means a sleepy hand can't turn an alarm off by accident. The volume keys snooze, unless you turn that off.
The sound starts quietly and gets louder over 20 seconds, then plays until you answer it. If nobody answers, it stops by itself after 10 minutes, shows as missed in the history and leaves a notification saying so. With **Snooze when nobody answers** set, it snoozes instead, a few times, before it counts as missed, or every time until you answer it when the slider is at **Until answered**, so an important alarm is still ringing when you come back to the phone. The shade carries the same **Snooze** and **Dismiss** buttons. A snoozed alarm stays in the shade with the time it rings again, and its **Dismiss** ends the snooze: a one-time alarm turns off, a repeating one waits for its next day. With **Notice before an alarm** on, the next alarm appears there ahead of time too, and **Skip** skips that one ring, or turns off a one-time alarm. Several alarms or timers ringing at once share one ring screen: the slide dismisses them all, and each one after the first also has its own **Dismiss**.
### What a reliable alarm needs
Android asks for three permissions before an app can ring an alarm the way a clock app does. The Alarms tab shows a line for any of them that is missing, and the tool's settings page lists them under **For alarms you can rely on**:
- **Alarms and reminders** (Android 12 and later). With it, alarms ring at the exact minute. Without it they still ring, but Android may hold one back by some minutes while the phone sleeps.
- **Full-screen notifications** (Android 14 and later, not in the Play Store build). With it, the ring screen takes over a locked phone. Without it, a ringing alarm shows as a notification instead.
- **Notifications** (Android 13 and later). For the Snooze and Dismiss buttons in the shade, and for running timers.
Each one opens with the keyboard's own explanation first, then Android's settings page for it. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/).
While something rings, the keyboard runs a short foreground service that plays the sound and keeps the phone awake. It is the only part of the app that runs that way, and it stops the moment you answer the ring or it gives up by itself.
### In the Play Store build
Google Play allows that kind of service, and the ring screen's full-screen permission, only for apps whose main job is an alarm clock, so the Play Store build has neither. There, an alarm or a timer rings through Android's own alarm notification instead. Snooze, Dismiss and the history work the same, and the ring still stops by itself after **Stop ringing after**. What changes:
- The ring shows as a notification at the top of the screen, not as the ring screen. Tap it to open the ring screen.
- The sound and the vibration are the ones set for the **Alarms ringing (backup)** notification in Android's settings. **Sound and vibration** in the tool's settings opens that page. There is no sound or vibration per alarm.
- There is no fade-in: the alarm starts at the volume Android gives alarm notifications.
- Timers ring with the same sound as alarms.
The GitHub and F-Droid builds have the service and every option above.
After you restart the phone, Android forgets every alarm an app has set. The keyboard sets them again when it next starts, which happens straight after a restart when it is your default keyboard. The app has no permission to start itself at boot.
## Timers
With no timer running, the **Timers** tab is a keypad. Digits fill from the right, the way a microwave's do: `1`, `3`, `0` is one minute thirty seconds. Under the reading are your presets; tap one to load it. Tap **Start**, the wide button at the bottom, and give the timer a label first with the round button beside it if you want to. The pill above the keypad switches between **Timer** and **Pomodoro**.
You can run as many timers as you like. Each one is a tile with a large ring that empties as the time runs out and the time left in the middle; under the ring are its name, when it ends (or **Paused**), and round buttons for **Pause**, **+1 min** and **Stop**. One timer takes the whole width; two or more sit two to a row. A paused timer can be reset to its full length. Your presets are a row of chips above the tiles, each starting a timer with one tap, and **New timer** at the bottom goes back to the keypad.
Presets start as 1, 3, 5, 10 and 25 minutes. With a time on the keypad, **Save as preset** adds it; hold a preset to delete it.
Running timers also show in the shade, counting down by themselves, with **Pause**, **+1 min** and **Stop**. When a timer runs out it rings like an alarm, at full volume, and **Snooze** on its ring screen becomes **Add 1 minute**.
### Pomodoro
Switch the keypad side to **Pomodoro** for focus sessions: 25 minutes of focus, a 5 minute break, again, and a 15 minute long break after every fourth focus session. **Start focus** begins the run. Each phase starts the next one by itself when it ends, so you only answer the ring. Dots under the timer show how far you are into the set of four. All four numbers are in the settings.
## Stopwatch
The **Stopwatch** tab shows the time to the hundredth of a second in large digits across the top, with a thin line under them that fills once a minute. Along the bottom, **Start** (or **Stop** while it runs) is the wide button, and beside it **Lap**, **Reset** and a button that types the time with all the laps into the field.
Laps are compact cards between the two, newest first, each with its own length, how much faster or slower it was than the lap before (`−0.31`, `+0.42`) and the total at that point. The fastest lap is green and the slowest is red.
The stopwatch keeps running with the keyboard hidden, and the shade has its **Lap** and **Pause** buttons. **Reset** puts the run in the history with its laps.
## Meeting planner
The **Planner** tab lays the same day out for your own city and every city on your list: one strip each, 24 hours across, in half hours. Working hours are the strong colour, waking hours the faint one, and night is nearly blank. The half hours where everyone is at work are brightest, and a soft band in the accent runs down every strip through them, so the good hours read as one column. Three tiles above the strips say how long that overlap lasts, the moment picked and how many of the places are at work then.
Drag along any strip, or tap it, and a cursor follows your finger with a tick at each half hour. Every city's time at that moment shows beside its name, with `+1` or `−1` where it is already tomorrow or still yesterday. Along the bottom, **Insert** is the wide button and types the moment in every city at once; **Discord** types it as one timestamp that each reader sees in their own time zone, in your default Discord style; and **Best time** jumps to the first half hour that suits the most people with nobody asleep. The arrows at the top move to other days.
Working hours are 9:00 to 17:00 unless you change them in the settings. They apply to every city.
## Dates
The **Dates** tab does date maths with steppers, and shows each answer as tiles. **Between**, **Add** and **Countdowns** sit on one pill at the top. Each date has buttons for a month back, a day back, a day on and a month on; tap the date itself to go back to today. For a date far away, such as a birthday years back, press and hold the date and type it in the box that opens, such as `17/5/2004` or `2004-05-17`. The day comes first unless your phone is set to a country that writes the month first, like the US; a date that only makes sense the other way round is read that way, and a two-digit year up to 50 is this century.
- **Between** shows three tiles: the days between the two dates (with the same span as years, months and days under it), the weeks, and the working days (Monday to Friday). The arrow between the steppers swaps the two dates.
- **Add** moves a date by days, weeks, months, years or working days, and shows the result as a card with the date, beside tiles with its week number and its day of the year. A month added to 31 January gives the last day of February.
- **Countdowns** is a grid of cards for the days you are counting towards (a trip, an exam), each with how many days are left, or how many have passed. Tap one to type it, such as `23 days until Exam`. Hold one for **Delete**. **New countdown**, the wide button at the bottom, asks for the date and a name; **Save** and **Cancel** take its place while you set it.
**Insert**, the wide button at the bottom, types the result of the Between or Add view.
## History
The **History** tab is a timeline of what rang and what was timed, newest first: a rail down the left names each day (**Today**, **Yesterday**, then dates) and the day's entries are compact cards beside it, each with its kind, its name, its length, its outcome and its time. It covers alarms (dismissed, snoozed or missed), finished and stopped timers, Pomodoro sessions, and stopwatch runs. A stopwatch run opens to show its laps. Above the timeline, two tiles hold today's focus time from Pomodoro and the total time you timed. Filter by kind with the chips; the bin clears what the filter shows, with **Undo**. The button on each card types it.
A timer stopped within its first five seconds, or a stopwatch reset before five seconds, is not kept. History is kept for 90 days by default, and never more than 500 entries.
## Options
Tools / Clock
| Setting | What it does | Default |
|---|---|---|
| **Open the clock** | The Clock screen in the settings app | |
| **Add to home screen** | A home screen icon that opens the Clock screen | |
| **Hour format** | **Like the phone**, **12-hour** or **24-hour**, for the panel, the ring screen and what the tool types | Like the phone |
| **Show seconds** | Seconds on the big clock and its face | On |
| **Show local time first**, **Show UTC first** | Hold your phone's own time, UTC or both ahead of the cities | Off |
| **Sort cities by time** | Lists the world clocks earliest time first, and the move buttons go | Off |
| **Cities in a grid** | The city tiles two to a row under your own clock instead of on a sideways carousel. A city's chips and the ways to type its time open under its row | Off |
| **Default format** | What a hold on your own clock types, and the first button when you hold a city | Time and place |
| **Sound and vibration** | Play Store build only: opens Android's settings for the alarm notification, which plays the ring there | |
| **Alarm sound** | Any sound from Android's picker, or silent (vibrate only). Not in the Play Store build | Default alarm sound |
| **Timer sound** | The same, for timers. Not in the Play Store build | Same as alarms |
| **Vibrate** | While an alarm or a timer rings. An alarm can turn it off for itself. Not in the Play Store build | On |
| **Snooze length** | 1 to 30 minutes. An alarm can keep its own | 10 minutes |
| **Fade in** | How long an alarm takes to reach full volume, up to 60 seconds. Timers always ring at full volume. Not in the Play Store build | 20 seconds |
| **Stop ringing after** | When an alarm nobody answers stops, 1 to 30 minutes | 10 minutes |
| **Snooze when nobody answers** | Each alarm can override this with a number of its own, or **Until answered**, in its dialog in the app. Off, 1 to 5 times, or **Until answered**: an alarm that stops by itself snoozes instead, until it has been snoozed that many times, and only then counts as missed. At **Until answered** it keeps snoozing and ringing again until someone answers it | Off |
| **Notice before an alarm** | Off, or 15, 30, 60 or 120 minutes: the next alarm shows in the notifications that long before it rings, with **Skip** | Off |
| **Notice while snoozed** | A snoozed alarm shows in the notifications with the time it rings again and **Dismiss**, which ends the snooze | On |
| **Notice for a missed alarm** | An alarm that rang with nobody to answer it leaves a notification | On |
| **Volume keys snooze** | On the ring screen. When off, the volume keys do nothing there | On |
| **Focus**, **Short break**, **Long break** | The Pomodoro lengths | 25, 5 and 15 minutes |
| **Long break after** | How many focus sessions come before a long break | 4 |
| **Work starts**, **Work ends** | The planner's working hours, in half hours | 9:00 and 17:00 |
| **Keep history for** | 7, 30, 90 or 365 days, or always | 90 days |
The tool's settings live in the phone's device storage rather than with the keyboard's other settings, because the alarm has to read them before you unlock the phone after a restart. A [backup](https://wmkeyboard.pages.dev/reference/settings/backup/) carries them in its **Clock** section, together with the alarms, the presets and the countdowns.
The clock and the calendar are the **Calendar and clock** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools and this screen leave the keyboard and the app until the bundle is switched back on; your clocks and countdowns stay.
## Details & edge cases
- **Alarms ring before the first unlock.** Alarms, timers and the clock's settings are kept in the part of the phone's storage that is readable before you unlock it after a restart, so a 6:30 alarm rings even if the phone restarted overnight. The keyboard panel itself waits for the unlock, like the other tools that keep files.
- **Daylight saving time.** An alarm set for a time that does not exist on the night the clocks go forward rings at the first minute that does, usually an hour later. On the night they go back, it rings the first time that hour comes round.
- **Changing the clock or the time zone** moves every alarm with it: they ring at their time on the clock that is now showing.
- **Timers follow the phone's clock.** A timer keeps running when the keyboard is closed and survives the keyboard being stopped, because its end time is stored. If you change the phone's clock by hand while one runs, it ends early or late by that much.
- **A timer that ran out while the keyboard was not running** rings as soon as the keyboard starts again, rather than not at all.
- **Two phones don't ring for one alarm.** [Sync](https://wmkeyboard.pages.dev/reference/settings/backup/) leaves the Clock section out unless you add it. The world clocks are part of the Clock section, not of the keyboard's settings.
- **Nothing goes online.** The city search, the sunrise and sunset times and every calculation run on the phone. The sunrise and sunset for a city come from the city's coordinates in the time zone data, so they are for the city the zone is named after, except for the added cities like Beijing, which carry their own.
- **The planner's working hours are the same for every city.** A city with a different working week or hours still uses yours.
---
# File manager
URL: https://wmkeyboard.pages.dev/tools/files/
Section: Tools
Summary: Pick a file for a tool without leaving the app you are typing in, and rename, copy, move or delete files while you are there.
When a tool wants a file, the keyboard can show its own file manager in a sheet over the keys. Android's picker takes over the screen and sends the keyboard away; this one leaves the app you are typing in where it is, and the tool is back as soon as you have picked.
It isn't in the Google Play build, which cannot hold the permission it needs. There, every tool below opens Android's picker as before.
## Where it opens
- **Files** and **Browse all** in the [Gallery](https://wmkeyboard.pages.dev/tools/gallery/).
- **Files** in [Send files](https://wmkeyboard.pages.dev/tools/send/).
- **An image** and **A file** in the **+** menu of the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) on the keyboard. The camera option still opens the camera.
- **Files…** in [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/).
- The picture button in the [QR scanner and the text scanner](https://wmkeyboard.pages.dev/tools/scanner/), and in the [camera](https://wmkeyboard.pages.dev/tools/camera/).
Settings screens keep Android's picker, since you have already left the app you were in by then. So do backup folders, which need a grant only Android's picker can give.
## The first time
The first pick asks for **All files access** (on Android 7 to 10, the storage permission). The keyboard explains what that covers before it sends you to Android's switch. The grant reaches every file in shared storage, because Android has no narrower one for browsing. The keyboard reads only the folders you open and the names you search for. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/#the-ones-that-arent-permissions).
You don't have to allow it. **Android's picker** on the same screen does this pick the old way, and **Always use Android's picker** turns the file manager off.
## Picking
The sheet opens at the keyboard's height. Its expand button makes it nearly as tall as the screen.
The list starts at **Places**: Recent, each storage volume, and shortcuts such as Downloads and Pictures. The row above the list shows where you are, and any step in it is a tap away. Back goes up a folder, and out of the top back to the tool.
Only files the tool can take are listed. The QR scanner shows pictures, for example. A tool that takes one file gets it at the first tap. For a tool that takes several, each tap adds a file, you can move between folders while you pick, and **Add** hands them over.
The search button looks through file names in the open folder, or everywhere from **Places**. The ⋮ menu sorts by name, date or size (choose the same one again to reverse it) and shows hidden files.
For anything not on the phone, such as Google Drive, the header's file button opens Android's picker for this pick.
## Changing files
Press and hold a file or a folder to select it. Tap others to add them. The bar along the bottom then offers:
- **Rename**, with one thing selected.
- **Copy** and **Move**, which carry the selection until you open a folder and press **Copy here** or **Move here**. A name already taken there gets a number after it, so nothing is overwritten.
- **Delete**, which asks first. It is permanent: Android has no recycle bin for files. A folder goes with everything in it.
**New folder** is in the ⋮ menu. Copies, moves and deletes run in the background with a **Stop** button. A copy you stop takes back the part it had made.
Android keeps `Android/data` and `Android/obb` closed even with this permission, so those folders look empty.
## Turning it off
Tools / Built-in file manager switches it off, and every tool goes back to Android's picker. Taking away All files access in Android's settings brings back the first-time screen.
Nothing opens over the lock screen, whatever the setting says. A pick made there goes to Android's picker.
---
# Find & replace
URL: https://wmkeyboard.pages.dev/tools/find-replace/
Section: Tools
Summary: Search the text you are writing and replace what you find, with match case, whole words, regular expressions and your recent searches.
Plenty of apps have no search of their own, or one that only finds. **Find & replace** works in any text field: it searches what you have written, steps through the matches, and replaces one or all of them.
## Opening it
Tap **Find & replace** on the toolbar, or in the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). Tap it again to close it. If you have a few words selected on one line, they become the search, the way Ctrl+F works on a computer. Otherwise it opens empty.
The same panel opens from the [selection actions bar](https://wmkeyboard.pages.dev/smart/selection-actions/#find-and-replace)'s **Replace** chip, already holding what you selected.
Password fields can't be searched; the tool says so and opens nothing.
## Searching
The panel has two fields, **Find** and **Replace with**. Tap one and type into it; the keys go to the panel, not to the app. While one of them is focused, its word suggestions sit in the row above the keys. With the toolbar on a row of its own, the suggestion strip above the panel gives up its row while the panel is open, so there is only one strip, and the toolbar row stays open. The count beside the arrows says which match you are on and how many there are (**3 of 12**). **‹** and **›** step through them and wrap round at the ends. Enter in the Find field goes to the next match. The **×** at the end of a field empties it in one tap.
The current match is selected in the field itself, and also shown in its line at the bottom of the panel with the match marked, so you can see it even when the keyboard covers that part of the text.
The field is read again on every step. Some editors, code editors and notes apps in a web page among them, only draw the part of a long text that is on screen, so the text changes under the panel as it jumps from match to match. After selecting a match the keyboard also reads back what ended up selected. If it isn't the match, which happens in editors that count their text differently from the way they hand it over, the keyboard looks for the match close by and selects it there.
Four toggles change how the search reads and what a replacement writes:
- **Aa** matches case.
- **[ab]** matches whole words only, in any script.
- **.\*** reads the Find field as a regular expression. `$1` in the replacement stands for the first group. A pattern that isn't valid says so, and one that takes too long on the text is stopped rather than left to hang. In the replacement, `\n` is a line break and `\t` a tab.
- **AB** preserves case: each replacement takes the case of the text it replaces, so replacing `cat` with `dog` turns `Cat` into `Dog` and `CAT` into `DOG`. Mixed case such as `iPhone` is replaced as you typed it.
## Replacing
**Replace** swaps the current match and moves on to the next one. Enter in the Replace with field does the same. **All** replaces every match at once, and **Undo** takes it back in one go too. A field that doesn't report where its text is (a few web views) still lets you find, but not replace in place.
## Recent searches
While the Find field is empty, the bottom of the panel offers your last ten searches. Tap one to search for it again, or **Clear** to forget them. A search is remembered once you use it, by stepping to a match or replacing, not while you type it.
Nothing is remembered in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), in a field that asks not to be learned from, or in a password field. To stop remembering searches altogether, or to clear them from Settings:
Tools / Find & replace / Remember searches
Find and replace is the **Find and replace** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on.
---
# Gallery
URL: https://wmkeyboard.pages.dev/tools/gallery/
Section: Tools
Summary: Your photos and videos inside the keyboard: albums, multi-select, a full-screen viewer, a photo editor, and a send that hands the app only a copy.
The Gallery tool puts your photo library in a sheet over the keys, the way a chat app's attach button does. Pick a few photos or videos, caption them, edit one if you like, and send. The app you are typing in never sees your library: it gets a copy of what you picked and nothing else, with the location taken out by default. all.
## Opening it
Open **Gallery** from the toolbar or the [toolbox](https://wmkeyboard.pages.dev/tools/overview/). Typing *gallery*, *photos* or *pics* also offers it as a [suggestion chip](https://wmkeyboard.pages.dev/smart/chips/).
The first time, the panel asks for access to your photos and videos. The keyboard shows its own explanation before Android's dialog, and you can refuse. Without access, **Pick instead** opens Android's photo picker, which needs no permission. On Android 14 and later you can also allow only some photos; the panel then says so, and **Choose more** asks again.
Photos and videos are two permissions from Android 13 on. If photos were already allowed for something else, such as screenshots in the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/), the grid shows photos only and a banner says videos are not allowed yet. **Allow videos** asks for them. Turning off **Show videos** hides the banner too.
The sheet opens at the keyboard's own height. Drag the bar on top of it, or press the expand button in its header, to make it nearly as tall as the screen. **Open at full height** under Tools / Gallery makes that the starting size. The viewer and the editor always use the full height.
### In the Google Play build
The Play Store build cannot hold the photo library permission, so there the tool has no grid of its own. A tap on **Gallery** opens Android's photo picker straight away. Whatever you pick is prepared with the same size and privacy settings and sent the moment the keyboard is back in the text box. The rest of this page describes the grid in the GitHub and F-Droid builds.
## Albums and filters
The chip at the start of the header names the album you are in. Tap it for the list of albums, each with its newest photo and a count:
- **Recents**: everything, newest first: by the date a photo was taken, or, for a picture with no camera date (one saved from a browser, a sticker, an edited copy), the date it was saved.
- **Sent from the keyboard**: what you sent through this tool before, the most recent send first.
- **Favorites**: what your gallery app has starred, on Android 11 and later.
- **Camera**, **Screenshots**, **Videos** and **GIFs**, when they have anything in them.
- Then every folder on the phone, the biggest first.
Next to it, **All**, **Photos**, **Videos** and **GIFs** narrow whatever album is open. **Open the last album** brings you back to the album you left; with it off, the panel always opens on Recents.
## Searching your photos
With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on **Text and pictures** and its model downloaded, the header has a search button. Tap it and type what a photo shows, in any language: "beach at sunset", "receipt", "my dog in the snow". The keys come up under a short strip of the grid, and the photos that fit best fill it, best match first, a moment after you stop typing. Enter or **Done** gives the keys back and keeps the results. The query then stays in the header in place of the album chip; tap it to change the words, or its cross to go back to the album.
The results are photos only, from every album. Picking, the viewer and sending work on them as on any album. Choosing an album or a filter ends the search.
The search finds a photo once the keyboard has read it. That happens on the phone, in the background, while the gallery panel is open: a few dozen photos at a time, newest first, up to your 3,000 newest. Nothing is uploaded. The first time, a large library takes many openings to work through, and a photo not read yet is simply not found. Without the model, or with search by meaning on Text only, there is no search button and the panel is the grid it always was. The Play Store build has no grid and so no search.
## The grid
The first square is a camera tile, which opens the [Camera tool](https://wmkeyboard.pages.dev/tools/camera/). Every other square is a photo or a video. Videos show their length, GIFs say GIF, starred photos carry a star, and a photo you have sent before carries a small send mark.
- **Pinch** the grid to change the number of columns, from two to six. The setting remembers it.
- **Drag the handle** that appears on the right edge while the grid scrolls to move through years of photos fast. A bubble shows the month under your finger. When the handle is hidden, the right edge is free, so taps on the last column pick photos as usual.
- New photos appear by themselves: a screenshot taken while the panel is open joins the top of the grid.
## Picking
- **Tap the round check** in a photo's corner to pick it. Picked photos shrink a little, get an accent ring, and show their place in the send order.
- **Tap the photo itself** to open it full size. Set **Tap on a photo** to **Pick it** if you would rather a tap picked, as Signal does; the corner check and the viewer still work.
- **Hold a photo** to peek at it big. Lift, and it goes away.
- **Hold, then slide** across the grid to pick every photo you pass, the way Google Photos does. Slide back and they drop out again. Near the top or bottom edge the grid scrolls under your finger. A slide that starts on a picked photo unpicks instead.
You can pick up to 20 at once; **Pick up to** changes that, from 1 to 100.
## The send bar
As soon as something is picked, a bar rises from the bottom:
- **The picks in order.** Hold one and drag it sideways to move it. Tap a photo to edit it, a video to watch it. The cross takes one out.
- **The caption.** Tap it and type. What you type goes into the message box after the pictures, or before them, as **Caption** says. Turn the box off there if you never use it.
- **The size chip.** **Original**, **HD** (2560 px on the long side), **SD** (1280 px) or **Small** (800 px), with an estimate of the total under it. Tap to step through them. The starting size is **Picture size** in settings. Videos always go at their own size.
- **Send.** Hold it for the other ways out: **Send at full size**, **Send without the caption**, **Share to this app**, **Share to another app**, **Copy to the clipboard**, **Save a copy to the gallery** and **Clear the selection**.
Each item goes into the text box in turn, a moment apart, so the chat app has time to take each one. Some apps, WhatsApp and Signal among them, answer the first picture by opening a send screen of their own, which takes the text box away. The keyboard waits a moment after the first picture to see whether that happens, and if it does, all of them go to the same app together through its share screen, where you pick the chat again.
### When the app does not take pictures
Some text boxes accept pictures from a keyboard and some do not, and fewer accept videos. What happens to the ones a box refuses is up to **When the app does not take pictures**:
- **Share them to that app** (the default): Android's share screen opens for the app you were typing in, with the pictures and the caption. Most chat apps then ask which chat they go to.
- **Copy them to the clipboard**, to paste yourself.
- **Ask each time**, with Share and Copy on a card.
A picture tapped in the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/) that the box refuses follows the same setting.
## The viewer
The viewer pages through the album you opened it from, or through your picks when you open it from the send bar.
- **Swipe** sideways for the next or the previous one, **pinch** or **double-tap** to zoom, and drag a zoomed photo to look around it.
- **Drag down** to put the viewer away.
- **Tap** the picture to hide the bars for a clear look, and again to bring them back.
- Videos play while they are on screen and loop. Tap pause, or drag the line under them to seek. This is a preview: what is sent is the video file itself.
- The **check** at the top right picks or unpicks what you are looking at. **Send** with nothing picked sends just this one.
- **Details** shows when it was taken, its size in pixels and in bytes, its length and its folder.
## The editor
Tap a picked photo in the send bar, or **Edit** in the viewer, to edit it. Editing leaves the photo in your library alone unless you save over it (see below). Every step can be undone and redone, **Remove every edit** starts over, and holding the compare button, or holding the picture in Filters and Adjust, shows the original. Every photo in the bar carries a pencil mark to show it opens the editor; on an edited photo the mark turns the accent colour, and the photo goes out with its edits at the chosen size.
- **Crop.** Drag the corners or the sides, or drag inside the frame to move it. Shapes: **Free**, **Original**, **Square**, **4:3**, **3:4**, **16:9**, **9:16**, **3:2** and **2:3**. **Turn a quarter** rotates it, **Mirror** flips it, and the **Straighten** slider tilts it up to 45 degrees either way while it zooms just enough to keep the corners filled.
- **Draw.** **Pen**, **Marker**, **Neon** and **Arrow** draw in any of twelve colours at the size you set. While you move the size slider, a dot of that size and colour sits in the middle of the photo, so you see how thick the line will be before you draw. **Eraser** takes back drawing, never the photo. **Blur** and **Pixelate** paint over a face, a number plate or an address so it cannot be read.
- **Text.** Tap the photo where the text should go, or **Add text**, and type with the keyboard. **Plain**, **Outline**, **Box** and **Glass** styles, three alignments, and the colour row. Drag a text to move it, pinch to resize it and turn two fingers to rotate it. Tap it again to change the words. Drag it onto the bin that appears at the bottom to delete it.
- **Stickers.** Thirty-two popular emoji, then your own [sticker packs](https://wmkeyboard.pages.dev/emoji/stickers/). They move, resize, rotate and delete the way text does.
- **Filters.** Fourteen looks, each previewed on your photo: Original, Vivid, Warm, Cool, Mono, Noir, Fade, Sepia, Chrome, Dramatic, Matte, Retro, Lush and Dusk. Tap one, then set how strongly it applies.
- **Adjust.** Brightness, contrast, saturation, warmth, tint, exposure, highlights, shadows, fade, vignette, grain and sharpen, each on its own slider. A dot marks the ones you changed. Double-tap a slider to put it back.
- **Upscale.** Redraws a small photo two or four times larger, on the phone, sharper than stretching it would be. Pick **Photo**, or **Drawing or text** for a screenshot, a drawing or a meme (a PNG starts on that one), then **×2** or **×4**, each shown with the size it makes. The result goes up to 4096 pixels on its long side, so ×2 is offered up to 2048 pixels and ×4 up to 1024; a larger photo has nothing to offer. A bar shows the progress and **Cancel** stops it. Once it's done the upscale is a step like any other: Undo takes it back, and **Original size** removes it. Your crop, drawings and text stay where they were. The first time, the upscaler downloads: a 2.2 MB network from the project's data repository, and on the GitHub and Play builds the engine the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/) runs on, if that isn't on the phone yet. The photo still goes out at the size the send bar's chip allows, so choose **Original** to send all of an upscale; a save to the gallery keeps all of it.
**Pinch** with two fingers in any tool to zoom in on the photo, and drag with two fingers to move around it, for drawing or blurring something small. Zooming only changes the view; the photo keeps its edits as they are. A chip at the top shows how far in you are, and a tap on it fits the whole photo again. A pinch that starts on a text or a sticker resizes that instead.
### Saving to the gallery
The save button in the editor's top bar, next to Done, keeps the edited photo on the phone. It works once the photo has an edit.
- **Save as a copy** adds a new photo next to the original, in the same folder, named after it with *_edited* on the end. A photo from a folder Android does not let apps add pictures to goes under Pictures/WM Keyboard instead. The original stays as it was.
- **Replace the original** writes the edited photo over the one in your library. Android asks first whether the keyboard may change a photo another app made. This cannot be undone, and the editor closes afterwards, since the edits are now part of the photo.
Either way the photo is saved at up to 4096 pixels on its long side, in its own format (JPEG, PNG or WebP; other formats are saved as JPEG), and keeps its date and camera details. It does not keep its location: Android hides that from the keyboard. **Replace the original** is only offered for JPEG, PNG and WebP photos from the library, not for HEIC photos or ones picked through Android's picker. The quality chip in the send bar does not apply to these saves.
**Keep edited photos** in settings also saves each edited photo you send, under Pictures/WM Keyboard.
## Privacy
What the app you type in receives is a copy in the keyboard's own storage, offered through Android's content system. It cannot see your library, other photos in the album, or the file the copy came from.
- **Remove the location** (on) takes the GPS position out of every photo and video sent. On Android 10 and later the keyboard never reads the location in the first place, because Android hides it from apps without a separate permission that this one does not ask for.
- **Remove the camera details** (on) also takes out the camera's make and model, the serial numbers, the owner's name and the time the photo was taken. A photo sent at a smaller size is re-encoded, which leaves all metadata behind anyway. A video is copied into a new container, which keeps its picture, sound and rotation and nothing else.
- **Random file names** (on) sends copies under a random name such as IMG_3f9a01c2d4.jpg, not the photo's own.
- **Remember what you sent** (on) keeps a list of the library ids you sent, for the Sent album and the send mark. It holds no pictures and no names, it stays on the phone, an incognito text box adds nothing to it, and **Clear the sent list** empties it.
- The copies are deleted from the keyboard's storage a day after they were made.
The library is read only while the panel is open. Nothing is uploaded: the tool works the same with no internet at all. With [photo search](#searching-your-photos) on, the numbers the keyboard makes from your photos stay on the phone, out of backups, and are deleted when you turn search by meaning off or take the photos permission away.
## Settings
Everything above is under Tools / Gallery: access, **Tap on a photo**, **Columns**, **Camera tile**, **Show videos**, **Fast scroll by month**, **Open at full height**, **Open the last album**, **Vibrate on each pick**, **Folders**, **Picture size**, **Pick up to**, **Caption**, **When the app does not take pictures**, **Close after sending**, **Keep edited photos**, and the privacy switches. The Play Store build shows only the ones that apply to the picker.
**Folders** lists the folders on the phone that hold photos or videos as a tree under DCIM, Pictures and the rest, with a search and a tick for each, so a whole branch can go at once. Untick one and its photos leave the gallery altogether: its own album goes from the list, and nothing from it shows in **Recents**, **Favorites** or any other collection, or in a search. A folder of saved receipts or a messaging app's downloads can stay out of the way. Tick it again to bring it back.
The gallery and the camera are the **Camera and gallery** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, both tools leave the toolbar, the toolbox and the Tools list until the bundle is switched back on.
---
# Handwriting
URL: https://wmkeyboard.pages.dev/tools/handwriting/
Section: Tools
Summary: Write with your finger or stylus, and on-device recognition turns the ink into text.
Handwriting turns a full-width canvas into a keyboard. Write a letter or a word with a finger or a stylus, and the keyboard recognizes it on-device and drops it into your text. Three recognisers sit behind it. Google ML Kit's digital ink models read 126 languages google. [zinnia](#zinnia-for-chinese-and-japanese), an open-source recogniser, reads Chinese and Japanese in every build. [PaddleOCR's text recognisers](#paddleocr-for-other-scripts) read Latin, Cyrillic, Arabic, Devanagari, Tamil, Telugu, Thai, Greek and Korean writing wherever ML Kit has no model for the language, which in F-Droid's build is every language.
## Opening the panel
Open **Handwriting** from the toolbar (or the toolbox if you haven't pinned it) to swap the keyboard for a full-width writing canvas. Under it is a row of four keys: abc, which closes the panel and returns you to normal typing, Space, Delete and Enter. They are ordinary keys, so the space bar's swipes and the delete key's repeat work as they do on the letters. You can move them, or add others, in the [panel's layout](#the-panel-is-a-layout).
Write anywhere on the canvas. Strokes render live as you draw, and dim to partial opacity while recognition is running. A single tap still counts as a stroke (enough to register a period or the dot on an "i"), so short marks aren't lost.
If two or more of your enabled languages have a handwriting model, a small chip in the top-left corner shows the active one. Tap it to cycle through the rest. With only one language available, no chip appears.
### Fixing a stroke
Two different controls undo ink, and they don't do the same thing:
- **Undo last stroke**, an icon that appears in the bottom-left corner once you've drawn something, removes only the most recent stroke and re-runs recognition on whatever ink remains.
- The panel's Delete key is different: while ink is still waiting to be recognized, pressing it throws away all pending strokes at once rather than deleting already-committed text. It's the natural "no, not that" reaction mid-word. Once the strokes have already turned into text, Delete goes back to deleting committed characters normally, and it auto-repeats on a hold like it does on the main keyboard.
## The panel is a layout
Tools / Handwriting / Panel layout
The handwriting panel is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) like the emoji, clipboard, text editing and trackpad panels. Out of the box it is the writing area over an abc, Space, Delete and Enter row. Open the layout editor from **Panel layout** on the tool's page (or from **Panel layouts** in the layouts gallery) to rearrange that row, add keys beside or above the canvas, or give the canvas more or less height. The writing area is a component, and the one the layout cannot lose: a layout without it gets one added back. The language chip, the undo button and the full-screen button stay on the writing area wherever you put it. Each key layout also has a Handwriting tab of its own, so one language can carry a different handwriting panel from the rest.
Before this panel became a layout, its four keys sat in a column down the right side of the canvas. The shipped layout puts them in a bottom row instead, so the canvas gets the full width.
## Writing over the whole screen
Tools / Handwriting / Full-screen writing
Tap the expand button in the top-right corner of the panel and the canvas leaves the keyboard: it covers the whole screen, see-through, so you write straight over the app and watch each word land in the text field. The keyboard shrinks to a small bar at the bottom with the recogniser's other readings of the last word (tap one to swap it in), and keys for undo stroke, Delete, Space, Enter, a shrink button back to the panel, and a keyboard button that closes handwriting. It works the same with the keyboard floating. While the canvas is up, the app keeps its full height and never jumps around under your ink.
A see-through canvas has to decide which touches are writing and which are meant for the app. **Full-screen writing** picks how:
- **Pen button** (the default): a pen button on the bar switches the canvas between writing and using the app. While it shows the pen, every touch on the screen is ink. Tap it and it turns into a hand: now every touch goes to the app underneath (scroll, tap a link, move the cursor) while the canvas stays out of the way. Works with a finger or any stylus.
- **Stylus** (Android 14 and later): a stylus writes and a finger keeps using the app, at the same time, with no button. Start writing with the pen over a text field and Android hands the stroke to the keyboard, which then takes pen ink anywhere on the screen until you stop for ten seconds. This rides on Android's own stylus handwriting, so Android's **Write in text fields** setting for styluses has to be on. It sits with the phone's stylus settings, and phones that ship with a pen usually have it on already. It works in any app's text field even with the keyboard closed, since Android starts it from the pen. The row is not shown on Android 13 and older.
The stylus mode only works with a stylus the screen reports as one (S Pen, a Wacom-type EMR pen, a USI pen, most Bluetooth styluses); a finger or a passive rubber-tip pen looks like a finger and goes to the app.
## Handwriting as a layout
Chinese, Japanese and Korean have a **Handwriting** layout of their own, the way Gboard offers one, and so do 296 more languages, Burmese among them: every language a recogniser here reads in the script the keyboard writes it in. A language with a model only from ML Kit, Burmese again, says no recogniser is available on the F-Droid build. Turn it on with the language's other layouts and pick it with the globe key: the writing area takes the place of the letter keys, and a normal bottom row (?123, globe, punctuation, Space, Delete, Enter) stays under it. The [number row](https://wmkeyboard.pages.dev/typing/basics/#number-row-and-numerals) stays off on this layout even when it is on for the others, because there are no letter keys for it to sit above; the ?123 layer still has it. Ink is read in that layout's language, with everything below about models, delete and undo working the same. There's no language chip on it; the globe key changes language. On the F-Droid build these layouts write Chinese and Japanese through zinnia, and Korean through [PaddleOCR](#paddleocr-for-other-scripts).
## Handwriting without opening the panel
Typing / Gestures / What a swipe does
There's a second way to write by hand that doesn't need the panel at all. Turn on **Glide typing** and a **What a swipe does** choice appears underneath it. Set that choice to "Handwrite". A swipe across the letter keys then draws a stroke on the keyboard itself instead of gliding a word. Recognition and insertion work exactly like the panel, and a plain tap still types its key. The default is "Type words", so this is off until you change it.
Picking "Handwrite" swaps one set of options for another:
- **Extra time for a dot or a cross** appears: default **700 ms**, adjustable from 0–1500 ms. Letters like i, j, and t need a second mark after the main stroke. For this long after you finish a stroke, a tap over the letters is folded into that same character, as the dot or the cross, instead of typing the key underneath it. Set it to 0 to turn the leeway off.
- The glide-only rows go away: **Glide across the spacebar**, **Ask when a glide is unclear** and the four rows under it, **Space after a glided word**, **Backspace removes a glided word** and **Delay after typing**. None of them means anything once a swipe draws ink rather than a word.
If the language you're typing in has no downloaded model yet, a one-time notice points you at the Handwriting settings screen instead of silently failing.
## Downloading language models
Tools / Handwriting / Recognition models
tools/handwriting-model-manager — Recognition models: one row per enabled language, each with its own download status.
The **Recognition models** section lists one row per enabled language that a recogniser can read, with its own status: not downloaded (with a download button, and the space that model would take), downloading (counting megabytes toward the total), downloaded and working offline (with a delete/trash icon), or download failed. Recognition runs entirely on-device once a model is on your phone, so after that one download the language works with no network connection at all. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what else does and doesn't touch the network.
Model sizes vary more than you might expect, so each row shows its own rather than one rounded figure for everything. Bangla installs 24.6 MB, English (US) 25.8 MB, French 26.1 MB, and Chinese only 7.9 MB. Roughly half of that crosses the network, because the files travel compressed and get unpacked on the phone. The numbers come out of ML Kit's own manifest rather than an estimate, so the progress readout counts real megabytes toward a real total.
You can start a download from the panel too. Open Handwriting in a language whose model is missing and it offers a **Download** button instead of a canvas. The panel shows the same running total, and that button turns into **Cancel** while the download is going, so you are never stuck watching it.
The recogniser itself is a separate file on most builds: ML Kit's handwriting engine, about 6.9 MB on the GitHub build (one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/offline/#feature-libraries)) and about 3 MB from Google Play on the Play Store build. Until it is on the phone, the offer to download says the language needs two downloads, the handwriting engine first and then the model, with both sizes where the build knows them. Play publishes no size before its download starts, so on Play only the model's size is shown. The same **Download** button covers both. While they come in, the panel says **Step 1 of 2** for the engine and **Step 2 of 2** for the model, the settings row and the notification say the same, and each step counts its own file's bytes. Once the engine is here, every other language's row drops it, since one engine reads them all. On the Play build, until the engine is here the rows cannot say how much a model takes, and the languages list still shows every language the recogniser knows, because that list is built into the app.
The first time handwriting is used after the keyboard starts, the model check and any download wait about ten seconds before anything happens. ML Kit fetches the models through Google's Mobile Data Download service, and that service sets up its full list of models before it answers anything. On a fresh start WM Keyboard clears the service's list first, because re-listing models it already knew about could leave every check and every download waiting forever. A model you already downloaded stays downloaded through this, and nothing is fetched again.
Downloads can also take longer than their size suggests, because the service can go quiet for a while before any bytes move. That is the real reason the readout counts megabytes: it tells you whether a download is slow or actually stuck. If nothing arrives for twenty seconds, both the panel and the settings row say so. After ninety seconds of silence the download gives up and clears out what it had already written, so trying again starts fresh instead of rejoining a transfer that already died.
The list of ML Kit's models isn't hardcoded in WM Keyboard. It's read straight out of ML Kit's own digital ink library, then filtered down to the languages you have enabled. So enabling a new keyboard language surfaces its handwriting model on its own, with nothing WM-Keyboard-specific to configure. The flip side: the catalog of *which languages have a model at all* only grows when ML Kit itself grows.
A phone that cannot download, or a build with no internet permission, can import a model instead. The **Import from a file** row under the models lists each language's files under **Get the files**: two or three `.zip` files from `dl.google.com`, the same ones ML Kit would fetch. Download them on any device, move them to the phone and pick them under **Import**. On the offline build the recogniser's own library has to be imported too; the import report says so, and the model finishes installing once it is in. See [Importing instead of downloading](https://wmkeyboard.pages.dev/privacy/offline/#importing-instead-of-downloading).
If an enabled language has no ink model, it's called out separately underneath the list: writing in that language falls back to the nearest language that does have one, rather than failing outright. And if none of your enabled languages has a model, the list itself is replaced by a note asking you to add a language on the Languages screen first. A **Languages and layouts** row at the bottom of the section takes you straight there either way.
## zinnia for Chinese and Japanese
Tools / Handwriting / Offline recogniser for Chinese and Japanese
[zinnia](https://github.com/taku910/zinnia) is a small open-source recogniser (BSD licence) that runs on the phone with models from the [Tegaki project](https://github.com/tegaki/tegaki). It reads Chinese and Japanese, and it takes over handwriting for them in three cases:
- In the F-Droid build, which has no ML Kit. zinnia is the only recogniser there, so handwriting works in Chinese and Japanese and in no other language, with no switch to set.
- For Cantonese, in every build. ML Kit has no Cantonese model, so Cantonese writes with zinnia's Traditional Chinese one.
- When you turn on Offline recogniser for Chinese and Japanese, a switch under Input that appears where ML Kit is in the build. It is off by default, so ML Kit stays the recogniser for both languages until then.
Chinese has two zinnia models, one for Simplified and one for Traditional characters, and the Recognition models list shows both. Which one the panel writes with follows **Traditional characters** in the Chinese settings: off writes Simplified, on writes Traditional.
| Model | Characters | Download |
|---|---|---|
| Japanese | 6,420 (kana and the common kanji) | 26.1 MB |
| Simplified Chinese | 6,763 (all of GB 2312) | 26.8 MB |
| Traditional Chinese | 11,853 (Big5) | 52.2 MB |
The download is the model and, the first time, zinnia's own library (about 450 KB). One press fetches both; the offer says so first, and the panel and the settings row name the step that is running (**Step 1 of 2** for the library, **Step 2 of 2** for the model). On the F-Droid build the library is one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/network/), so it needs **Download feature libraries** turned on under Privacy first, and the panel says so if it is off. The models come from the project's data repository and are checked against the checksum the app ships with. A phone that cannot download can import a model file: the model's row under **Import from a file** links to it.
zinnia reads one character at a time. Write a line of characters with a gap between each, and the keyboard cuts the ink at the gaps and reads every way of joining the pieces back up, so a character whose halves stand apart (石 and 艮 in 硍) is not read as two. The reading zinnia is surest of commits, and the next ones wait in the suggestion strip. It is less accurate than ML Kit, especially for hurried or cursive writing, and it does not use the text before the cursor.
## PaddleOCR for other scripts
Where ML Kit has no model for a language, handwriting reads with [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)'s PP-OCRv5 text recognisers (Apache licence), one for each script. In F-Droid's build, which has no ML Kit, that means every language below. No open recogniser exists that reads pen strokes in these languages, so the keyboard draws your ink as a line of printed text and reads that line the way the [text scanner](https://wmkeyboard.pages.dev/tools/scanner/) reads a photo.
| Model | Languages | Download |
|---|---|---|
| Latin script | English and about 45 more, among them French, German, Spanish, Portuguese, Italian, Dutch, Polish, Turkish, Indonesian, Swahili and Vietnamese | 8.0 MB |
| East Slavic | Russian, Ukrainian, Belarusian | 7.9 MB |
| Cyrillic script | Bulgarian, Serbian, Macedonian, Kazakh, Kyrgyz, Mongolian, Tajik, Tatar, Bashkir and 19 more | 8.0 MB |
| Arabic script | Arabic, Persian, Urdu, Pashto, Sindhi, Uyghur, Sorani and Balochi | 8.0 MB |
| Devanagari | Hindi, Marathi, Nepali, Sanskrit, Konkani, Maithili, Bhojpuri and more | 7.9 MB |
| Tamil, Telugu, Thai, Greek | one language each | 7.8 to 7.9 MB each |
| Korean | Korean | 13.4 MB |
Every language of a script shares its model, so the Recognition models list shows one row per script for the languages you have enabled, and one download covers them all. The first download also fetches MNN, the open-source library that runs the model, which the sticker generator and the F-Droid text scanner use too. The offer to download says so before you press it, and the readout names the step that is running. On the F-Droid build MNN is one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/network/), so **Download feature libraries** has to be on under Privacy first, and the panel says so if it is off. The models come from the project's data repository and are checked against the checksums the app ships with. A phone that cannot download imports the model's two files instead: its row under **Import from a file** links to both.
A text recogniser reading handwriting gets a lot of letters wrong, so each word it reads is weighed against the words the keyboard would suggest if you had typed what it read. The recogniser gives every candidate a score for how well that exact spelling fits your ink, and the best fit wins. What the recogniser read on its own still competes, so a name or a word missing from the dictionary can come through. On 192 English words of real handwriting traced into strokes, it read 43% right on its own and 52% with the dictionary's help. ML Kit does much better, which is why PaddleOCR never takes over a language ML Kit has a model for. Thai is written without spaces, so it skips the dictionary step.
Write a word or a few words on one line. Several words written in one go are read separately, each with the word before it as context, and the strip offers other readings of the last one.
## Options
Tools / Handwriting / Input
The panel is also the **Handwriting** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list, and the models you downloaded stay on the phone.
- **Stylus only**: off by default. With it on, only a stylus (S Pen or similar) draws on the canvas, and finger touches are ignored completely. Even with it off, a finger touch is briefly ignored right after a stylus stroke, so a palm resting on the screen while you write doesn't add stray ink.
- **Automatic space**: on by default. Inserts a space between words you write one after another, but never before punctuation, and never next to Chinese, Japanese, Thai or another script written without spaces.
- **Offline recogniser for Chinese and Japanese**: off by default, and shown only where ML Kit is in the build. With it on, Chinese and Japanese read through zinnia; see [zinnia for Chinese and Japanese](#zinnia-for-chinese-and-japanese).
- **Panel layout**: opens the layout editor described in [The panel is a layout](#the-panel-is-a-layout).
- **Pen thickness**, **Pen color** and **When ink turns into text**: the pen's width from 1 to 12 dp (default 4), its color (the theme's accent until you pick one), and how strokes leave the canvas: Dissolve (default), Fade, Shrink, Rise or Disappear at once.
- **Panel height**: opens the canvas taller than the keyboard, without changing the keys. The emoji panel has its own, under Emoji.
- **Recognition pause**: default **700 ms**, adjustable from 300–2000 ms. How long the canvas waits after your last stroke before it sends the word off for recognition. Shorter feels snappier. It can also cut a multi-stroke letter or a Bengali conjunct in half if you pause mid-letter, where a longer wait gives you room to breathe between strokes.
Whatever the Recognition pause slider is set to, handwriting in these languages never commits sooner than 1200 ms after the last stroke. Bengali glyphs are frequently built from several separate strokes (conjuncts, the matra, vowel signs), and a kanji, hanzi or Hangul block can take ten strokes or more. A natural pause between them would otherwise trigger an early, wrong commit.
## Details & edge cases
- **Palm rejection** happens automatically and isn't a setting: once the canvas sees a stylus point, it ignores finger touches for 800 ms afterward.
- There's no hover preview, no air-gesture support, and no S Pen side-button handling. The stylus-specific behaviors are palm rejection and the **Stylus** full-screen mode.
- Switching between the panel and full screen throws away ink that hasn't been recognised yet: it was drawn on the other canvas, at another size.
- Auto-capitalization at the start of a sentence applies when you're writing in American English through ML Kit, or in a Latin, Cyrillic or Greek script through PaddleOCR, with Auto-capitalize on, and never in a password or other secure field. Bangla and other languages without letter case are skipped entirely.
- ML Kit's recognizer only sees the last 20 characters of text before your cursor as context (a limit set by ML Kit itself, not WM Keyboard). PaddleOCR's reader sees the word just before the cursor, for its dictionary step. zinnia sees none.
- Handwriting has no direct-boot support: neither the panel nor handwriting-by-swipe works before you've unlocked your device once after a restart, unlike some other tools. If [Power Saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) is set to drop on-device models to save battery, handwriting-by-swipe is switched back to plain gesture typing until you turn it back on.
## On F-Droid
ML Kit's digital ink recognition depends on `com.google.mlkit:digital-ink-recognition`. That library is Google's and not open source, so the F-Droid build leaves it out entirely. The Handwriting tool, the handwriting layouts and the "Handwrite" option of **What a swipe does** are all still there. Chinese and Japanese read through zinnia, and the nine scripts in the [PaddleOCR table](#paddleocr-for-other-scripts) through PaddleOCR on MNN; F-Droid compiles both libraries from source. Languages in other scripts, Bangla among them, get a note in the panel instead of a canvas. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're deciding between builds.
---
# Smart home
URL: https://wmkeyboard.pages.dev/tools/home-assistant/
Section: Tools
Summary: Your home from the keyboard: Home Assistant, Google Home, SmartThings, a Philips Hue bridge, openHAB, Smart Life (Tuya) or SwitchBot as rooms of live tiles for lights, blinds, thermostats, media, locks and sensors, with a typed command bar for Home Assistant's Assist.
The **Smart home** tool puts your house inside the keyboard. It connects to the homes you list, shows every room as a tab of tiles, and keeps them live: when somebody flips a wall switch, the tile rings and changes while you watch. A tap switches a light, runs a scene or types a sensor's reading into your message. A hold opens the thing's own card, with a brightness slider, a thermostat dial, blinds buttons, the media transport, or a lock that takes a hold to open. With a Home Assistant on the list, a bar under the tiles takes a sentence for Assist, so `turn off the kitchen lights` typed with the keyboard does exactly that.
Seven kinds of home can be on the list, and more than one at once: [Home Assistant](#home-assistant), [Google Home](#google-home), [SmartThings](#smartthings), a [Philips Hue bridge](#philips-hue-bridge), [openHAB](#openhab), [Smart Life and Tuya](#smart-life-and-tuya) and [SwitchBot](#switchbot). Each is a row of the tool's [provider list](https://wmkeyboard.pages.dev/tools/providers/): add it once, sign in or pair, and its rooms join the panel. The panel talks only to the homes you listed; nothing about your home passes through any other service.
Open **Smart home** from the toolbox, or type `hass`, `smarthome`, `googlehome`, `smartthings`, `hue`, `openhab`, `tuya`, `smartlife` or `switchbot` on its own and tap the chip.
## Adding a home
The tool's page is at Tools / Smart home. **Homes** is the list: **Add a provider** opens the kinds this build can use, and each kind's setup sheet asks for what it needs. A home that is set up but not yet signed in, paired or granted says so in red in the list; **Edit** opens the sheet again. Drag rows to change the order the rooms appear in, switch a home off to keep its setup without connecting, or remove it, which forgets its key with it.
### Home Assistant
Needs the server's address and a sign-in.
**The address.** Type it the way you reach it at home: `homeassistant.local:8123`, `192.168.1.5`, or whatever your browser shows. The sheet also lists any Home Assistant that announces itself on your Wi-Fi; tap one to fill the address in. A remote address is optional: a Nabu Casa or reverse-proxy `https://` address that works from anywhere. With both set, the keyboard tries the home address first and falls back to the remote one when the home one does not answer.
**Signing in**, two ways:
- **Sign in** opens your browser on your own server's login page. You sign in there, with your password and whatever second factor you use, and Home Assistant hands the keyboard a key. The keyboard never sees your password, and the key renews itself. Home Assistant checks `wmkeyboard.pages.dev/ha/` once during this, because that page is the keyboard's name as a sign-in client; your server, account and key never go there.
- **Use token.** In Home Assistant, open your profile, the **Security** tab, and create a **long-lived access token**. Paste it into the sheet. This is the way in when the browser cannot reach your server, or when you prefer a key you can revoke from Home Assistant yourself.
**Test connection** asks the server its name and version with the key you have, so a wrong address or a pasted token with a character missing shows up here rather than in the panel. **Sign out** revokes the browser key on the server and keeps only the addresses.
Home Assistant is the one kind with an assistant, so the [Assist bar](#assist) and the **Ask home** chip appear only while a Home Assistant is on the list. Every kind of entity is shown: lights, switches, fans, covers, valves, climate, humidifiers, water heaters, media players, locks, alarm panels, vacuums, sirens, numbers, selects, text, buttons, scenes, scripts, automations, cameras, sensors, weather and people.
### Google Home
The devices of one Google Home, through Google's own Home APIs on the phone. Needs Google Play services and the Google Home app, so it is offered only in the edition from Google Play, and only on a phone that has them.
The Home APIs are a separate part of the keyboard, which Play downloads the first time you tap **Choose a home**. When the download finishes, the screen reopens, and you tap **Choose a home** again.
**Choose a home** opens Google's own screen: you pick the account and the home, and which kinds of device the keyboard may see. Locks, cameras and doorbells are granted one by one. One home at a time; **Change home** runs the screen again. To take the access back on Google's side, remove WM Keyboard under your Google account's third-party apps, or in the Google Home app's linked apps.
What arrives are Matter and Google device types: lights (on/off, brightness, colour temperature, colour), plugs and switches, thermostats, air conditioners, heat pumps and air purifiers, locks, blinds and shutters, fans (by the steps a fan has, so one with five speeds moves in 20 % steps, and its named speeds as presets), speakers and players, and contact, occupancy, temperature, humidity and light sensors. The keyboard reads and controls them through Google's service, which keeps its own connection; away from home, Google needs a hub in the house to reach the devices.
While Google's developer console stays closed to registration, Google caps an app's Home APIs access at a hundred test users and shows an unverified-app warning during consent. That is Google's limit on every app at this stage, not a setting.
### SmartThings
Your SmartThings devices, rooms and scenes, through Samsung's cloud. Add one row per account, so a second account (a parent's house, an office) sits beside the first with a nickname of its own. Two ways in:
- **Sign in** opens Samsung's login in your browser, and SmartThings hands the keyboard a key that renews itself. The browser comes back through `wmkeyboard.pages.dev/st/`, which only forwards the sign-in result to the app. Offered when the build carries a SmartThings sign-in client.
- **Use token.** A personal access token from `account.smartthings.com/tokens`, with the devices, locations and scenes scopes. SmartThings expires these after a day, so this is for trying things out.
SmartThings has no event stream for an app like this one, so the panel asks for the device list every ten seconds while it is open; the dot by the home's name stays amber for that. A tap still acts at once. What arrives: lights with level and colour, switches and plugs, fans, shades and doors, locks, valves, thermostats (heating and cooling setpoints, modes, fan modes), media players, and contact, motion, water, presence, smoke, temperature, humidity, battery, light, power and energy readings. Scenes run from their tiles.
### Philips Hue bridge
A Hue bridge on your Wi-Fi, directly, with nothing leaving the house. The sheet finds the bridge on the network (or you type its address), then: press the round button on the bridge and tap **Pair** within thirty seconds. The bridge mints a key of its own for the keyboard, and the bridge's certificate is pinned with it: a different device answering at that address later is refused.
What arrives, live over the bridge's event stream: every light with brightness, colour temperature, colour and effects; one group light per room; scenes, which run from their tiles; and the motion, contact, temperature, light level and battery readings of Hue sensors. Rooms come from the bridge.
### openHAB
Your openHAB server, at its address, with an API token from your profile (the user menu, **Create new API token**). Leave the token blank when your server lets devices on your network in without one.
Items map by type and semantic tag: Switch, Dimmer and Color items are lights or switches (a `Lock` tag makes a lock), Rollershutter is a cover, Contact a contact sensor, Number a sensor or, with a `Setpoint` tag, a slider, String a selection or a text, Player a media player. Rooms and floors come from the semantic model: groups tagged as locations. States arrive live over the server's event stream.
### Smart Life and Tuya
Devices in the Smart Life or Tuya Smart app, through Tuya's cloud. Tuya lets another app in only through a developer project of your own, so this takes a one-time setup on Tuya's site, free of charge:
1. Sign up at `platform.tuya.com` and create a cloud project. Pick **Smart Home** as the industry and development method, and the data centre that serves the country your app account was registered in; Tuya's own data centre table lists them.
2. In the project, open **Devices**, then **Link App Account**, and add your account: the page shows a QR code that you scan from the Smart Life or Tuya Smart app (**Me** tab, the scan icon). Your devices appear in the project.
3. Make sure the project's **Service API** list has **IoT Core** and **Smart Home Scene Linkage**; the first is added for you, the second gives the scenes.
4. Copy the **Access ID** and **Access Secret** from the project's **Overview** into the keyboard's setup sheet, and pick the same data centre there.
The Access Secret stays on the phone: it signs each request and is never sent. Tuya's free trial of the cloud services has to be extended on its site from time to time; when it lapses, the home shows as refused until you extend it. One row per project, so two app accounts can sit side by side.
Tuya has no push channel a phone can hold, so the panel asks for the device list every 30 seconds while it is open, and the dot stays amber. What arrives: lights with brightness, colour temperature and colour; plugs, power strips (one switch per socket) and wall switches; fans; curtains and garage door openers; thermostats, heaters and air conditioners; and temperature, humidity, battery, power, voltage, energy, CO2, PM2.5, door, motion, presence, leak, smoke and gas readings. Rooms and tap-to-run scenes come from the app's homes. Locks and cameras are left out: Tuya's cloud opens a lock only through a separate password flow.
### SwitchBot
Your SwitchBot devices through SwitchBot's cloud, with the token and secret key from the SwitchBot app: **Profile**, **Preferences**, **About**, then tap **App Version** ten times; **Developer Options** appears, and **Get Token** shows both. Paste them into the setup sheet. The secret key stays on the phone and only signs requests. Bluetooth devices answer through a hub, and only once **Cloud Services** is on in each device's settings.
SwitchBot allows 10,000 requests a day per account, and a device's state is one request, so the panel asks each device that reports anything once a minute while it is open; a tap acts at once and checks the device two seconds later. What arrives: bulbs, strips and ceiling lights; plugs, relays (two switches for a 2PM) and Bots; Curtain, Roller Shade and Blind Tilt, with position; the garage door opener; locks, with the door sensor where there is one; circulator fans and air purifiers; the radiator thermostat; and meter, CO2, motion, contact, presence and leak readings. The infrared remotes a hub has learnt are switches that remember what the keyboard last sent them, since a remote cannot report. Manual scenes run from their tiles. SwitchBot has no rooms, so its things sit under **Elsewhere**.
## The panel
Along the top: the tabs, then search and settings. Under the tiles, in small type, the home's name with a dot beside it. The dot breathes while the keyboard connects, turns green when the live stream is up, amber when only a polled or plain web API answers, and red when a home cannot be reached or its key was refused. With several homes, the names join and the dot shows the best of them.
**Tabs.** **Favourites** first, then one tab per room of each home in the list's order, then **Elsewhere** for things with no room, then **All**. With more than one home, a room tab carries the home's name first. Swipe across the tiles to change tab. The panel opens on Favourites, or on the tab you used last if you prefer.
**Tiles.** Each tile is the thing's icon in a soft circle, its name, its state, and the one control its kind has. The circle fills with the theme's accent as the thing comes on, and for a light, as bright as the light is. Tiles arrive with a short stagger when a tab opens.
| Kind | On the tile | A tap |
|---|---|---|
| Light, switch, fan, input boolean, automation | A switch | Toggles |
| Scene, script, button | A play arrow | Runs or presses it; the tile flashes |
| Cover, valve, climate, media player, lock, alarm, vacuum, humidifier, water heater, number, select, text, camera | A chevron | Opens the card |
| Sensor, binary sensor, weather, person | The reading, large | Types it into your text |
A tap acts straight away: the tile shows the new state and the home's next event confirms it. If the home disagrees, or refuses, the tile shakes and shows the truth. Hold any tile to open its card.
**The card** grows out of the middle of the panel over the dimmed tiles. At the top are the name, the room and the live state; at the bottom, **Insert**, **Hide** and **Open in its app**, plus a star to make it a favourite. In between, the controls for its kind:
- **Light**: the switch, a brightness slider, a warmth slider from candle to daylight, a hue strip and a row of colours, and the light's effects. Dragging a slider ticks every five per cent and sends as you go.
- **Fan**: speed, oscillation, direction, presets.
- **Cover, valve**: open, stop and close, a position slider, a tilt slider. A garage door, a gate or a door takes a hold instead of a tap.
- **Climate**: a dial that fills to the target in orange while heating and blue while cooling, the current temperature as a dot on it, minus and plus by the step your thermostat uses, then the modes, presets and fan modes.
- **Media player**: what is playing, previous, play or pause, next, the volume with mute, and the sources.
- **Lock**: Unlock and Lock, each a hold; Open where the lock has a latch.
- **Alarm panel**: Disarm, and the arm modes your panel offers, each a hold. A panel that wants a code asks for one; the keys type it.
- **Vacuum**: start or pause, stop, dock, find, and suction levels, with the battery in the state line.
- **Humidifier, water heater**: the target slider and the modes.
- **Number, select, text**: a slider, a row of options, or the text with an Edit that hands the keys over.
- **Camera**: a still from the camera, **Refresh**, and **Send picture**, which puts the picture into the chat the way the Gallery does. Home Assistant only.
- **Sensors**: the reading large with its unit and when it last changed, and three chips for how to type it: the value, the value with its unit, or the name and the value. Hold a chip to make it the default for every sensor tile's tap.
A kind a home does not have never shows; a control a home cannot do (a colour on a Hue scene, say) is left off the card.
**Search** filters every tab by name, entity id or room as you type; Enter opens the first match. **Hide** takes a tile out of every tab; the Favourites and rooms screen brings it back.
### Risky things
Locks, alarm panels, garage doors, gates, sirens and valves are never one tap. Their buttons fill a ring while your finger stays down and act when it is full, about a second; lifting early does nothing. **Hold to confirm risky actions** on the tool's page turns this off.
### Assist
With a Home Assistant on the list, the bar at the bottom reads **Ask your home…**. Tap it and the keys type into it, glide and voice included. Enter sends the sentence to Home Assistant's Assist, the same one that answers in its own app: `turn on the hall light`, `set the bedroom to 20 degrees`, `is the garage closed`. The reply slides up over the bar. Things it changed ring their tiles; things it could not find shake. **Insert reply** types what it said into your message. A follow-up question stays in the same conversation until you close the panel.
Assist speaks the language you are typing in, when **Send the keyboard's language** is on, and you can pick which of your conversation agents answers. The first Home Assistant on the list is the one asked.
### Favourites and rooms
Tools / Smart home / Favourites and rooms lists everything every home has, by home and room. Star what the Favourites tab should show, drag the favourites into order, hide single things, and switch off whole kinds (every `automation`, say) you never want to see from the keyboard. Entities a home marks as diagnostic or configuration, and ones it hides itself, never show.
## Elsewhere in the keyboard
- **A chip while you type.** With a Home Assistant connected, a sentence that reads like a command for the house (`turn off the lights`, `dim the bedroom to 30%`, `lock the front door`, `open the blinds`) gets a strip chip, **Ask home**. Tapping it opens the panel and sends the sentence to Assist instead of leaving it in your message. **Suggest commands as you type** on the tool's page switches it off; it is part of the [smart chips](https://wmkeyboard.pages.dev/smart/chips/).
- **The AI chat's tools.** The [AI chat](https://wmkeyboard.pages.dev/tools/ai/) gets two tools: **Control your home**, which works a device on any home on the list (Google Home, SmartThings and the rest included) or sends one plain sentence to Home Assistant's Assist, and **Read your home**, which lists what is on across every home on the list, or whatever matches a name. Both ask before they run, like every tool that touches your things. A fan with a few fixed speeds reads as `speed 2 of 5`, so asking for speed 1 sets the first speed rather than one percent. They are listed only while a home is set up.
- **Snippet variables.** `{ha:sensor.outside_temperature}` expands to the entity's state with its unit, and `{ha:light.kitchen:brightness}` to one attribute; see [snippets](https://wmkeyboard.pages.dev/tools/snippets/). The entity is looked for in every home; `{ha:home_key|entity_id}` names one. They read what the keyboard last saw and ask the homes for a fresh state when they are connected.
## Options
Tools / Smart home
Smart home is the **Smart home** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool, its panel and the devices screen leave the keyboard and the app; your connection and devices stay until you switch it back on.
| Setting | What it does | Default |
|---|---|---|
| **Smart home tool** | The switch; adding a home turns it on | Off |
| **Homes** | The provider list: each home's kind, address and sign-in, in the order their rooms appear | Empty |
| **Favourites and rooms** | The Favourites tab's tiles, hidden things, kinds to leave out | |
| **Open on** | **Favourites** or **Last tab** | Favourites |
| **A tab per room** | Each home's rooms as tabs; off, just Favourites and All | On |
| **Tile size** | **Comfortable** (two across) or **Compact** (four) | Comfortable |
| **Hold to confirm risky actions** | Locks, alarms, garage doors, gates, sirens and valves take a hold | On |
| **What a sensor tile types** | **Value**, **Value and unit** or **Name and value** | Value and unit |
| **Command bar** | The Assist bar under the tiles (with a Home Assistant listed) | On |
| **Conversation agent** | Which of your Home Assistant's assistants answers | Home Assistant's default |
| **Send the keyboard's language** | Assist answers in the language you type in | On |
| **Stay connected** | **While the panel is open**, or **While the keyboard is shown** so the panel is live the instant it opens; either way the connections drop a minute after | While the keyboard is shown |
| **Keep the last states on the phone** | Draw the tiles from the last session at once, dimmed until each home confirms them | On |
| **Suggest commands as you type** | The **Ask home** chip (with a Home Assistant listed) | On |
## Details & edge cases
- **What is sent, and where.** Only to the homes you listed: each home's key in a header, the commands you give, the states it sends back, and a camera still when you ask for one. Home Assistant is a WebSocket to `/api/websocket`; a Hue bridge and openHAB hold an event stream open; SmartThings is asked every ten seconds, Smart Life every 30 and SwitchBot once a minute; Google Home goes through Play services. All of it only while the panel is open or the keyboard is shown, and a minute after. Nothing goes out when the tool is off or no home is signed in. The [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) shows every request under the home's platform.
- **Plain HTTP to your own server.** A Home Assistant or openHAB address without `https://` is plain HTTP on your own network, which is how most installs answer. A remote address should be `https://`; a bare public domain is taken as such. A Hue bridge is always HTTPS, with its own certificate pinned at pairing.
- **When a stream cannot connect**, Home Assistant falls back to its plain web API: taps still work, states refresh when you act, and the dot turns amber. When nothing answers, that home's tiles stay as last seen, dimmed, under a banner with **Retry**; the other homes carry on.
- **A key that stops working** (you revoked it, the server was reset, a SmartThings token expired, a Tuya trial lapsed) shows as a red dot and a banner on the tool's page. Edit the home and sign in again; nothing else is lost.
- **Several homes.** Rooms with the same name in two homes are two tabs, each with its home's name first. Favourites and hidden things remember which home they belong to.
- **Before the first unlock** after a restart, the tool is not available: the keys live in the part of the phone that opens with your unlock.
- **Backups** leave every home's key out unless you turn on **Include API keys**; the addresses and the list go with them.
- **Camera stills** are fetched over Home Assistant's web API with the key, never through any image service, and are not kept on the phone.
---
# Instruments
URL: https://wmkeyboard.pages.dev/tools/instruments/
Section: Tools
Summary: Flashlight, compass, bubble level, moon phase, and weather: glanceable tools between sentences.
"Instruments" is this site's name for five small utility tools that Settings groups together as "Utilities," alongside [Calendar](https://wmkeyboard.pages.dev/tools/calendar/) and [Plugins](https://wmkeyboard.pages.dev/plugins/overview/): **Flashlight**, **Compass**, **Bubble level**, **Moon phase** and **Weather**. None of them makes you leave the keyboard to check something. Four open as a panel that replaces the key rows. Flashlight is the odd one out, a single tap with no panel at all. The same group also holds [Screenshot](#screenshot), another single tap.
tools/instruments-settings-utilities-group — The Utilities group under Settings → Tools: Flashlight, Compass, Bubble level, Calendar, Weather and Moon phase.
Open any of the five from the toolbar if you've pinned it, or from the toolbox. None ships pinned by default, and none is in the onboarding wizard's Recommended set, so the first time you want one you'll probably find it in the toolbox. Weather is the one the "show me everything" answer does turn on, next to Calendar. The toolbox doesn't label a "Utilities" section the way Settings does, and it doesn't keep the five together either. Flashlight, Compass, Bubble level and Moon phase sit as a block near the end of the default ranking, after the scanners and the camera and just before the one-tap cursor moves. Weather is much earlier, right after Calendar in the everyday run.
None of the five needs an Android runtime permission. The torch doesn't need the `CAMERA` permission (that's only for the separate [camera tool](https://wmkeyboard.pages.dev/tools/camera/)). Weather's location is typed or searched, or read once from the phone when you tap **Use my location**, which asks for location access the first time; see [Location](https://wmkeyboard.pages.dev/tools/location/#weather-and-compass). No build is short-changed either: every build behaves identically across all five.
## Flashlight: a one-tap torch
Tapping **Flashlight** turns the device's torch on or off immediately. There's no panel to open or close. If the device has no flash, a toast says "Your device has no flashlight" and nothing else happens.
The toolbox tile mirrors the torch's real state. Flip the torch on from the system quick-settings tile instead of the keyboard and the Flashlight tile lights up to match. Turning it off from either place turns it off in both.
tools/flashlight-toggled-on — Flashlight is a direct toggle: no panel opens, and the toolbox tile lights up while the torch is on.
### Options
Tools / Flashlight
- **Turn off with the keyboard**: on by default. On, closing the keyboard or switching apps switches off a torch you turned on from the keyboard, so it's never left burning in your pocket. A torch you turned on from the quick-settings tile or another app is left alone. Off, the torch stays on until you toggle it again from the tool or the system quick-settings tile.
## Compass, with an optional qibla bearing
**Compass** draws a live rose from the device's rotation-vector sensor, with a fixed needle at the top marking the way you're actually facing. The heading glides smoothly rather than jumping with every sensor sample. On a device with no orientation sensor at all, the panel says so instead of drawing an empty rose.
Turn on **Show the qibla** and a 🕋 marker rides the rose, pointing toward the Kaaba. The compass has no location field of its own. It reuses whatever location you've already saved in the [Weather](#weather) tool's settings, and without one saved the panel prints "Qibla needs a saved location. Save one in the settings of the weather tool." instead of a bearing, with a **Use my location** button under it that reads your position once and saves it as that location. Everything is calculated on-device from that saved latitude and longitude. The compass itself never touches the network.
The [Location tool](https://wmkeyboard.pages.dev/tools/location/#places-somebody-sent-you) can also point the compass at any place, a friend's pin, say. A 📍 marker rides the rose next to the 🕋 one, and a line under the rose gives its distance and direction, with **Stop pointing** beside it.
Once a location is saved, the rose turns to true north rather than magnetic north. The phone's sensor finds magnetic north, which is a few degrees off true north in most places and up to twenty in some; Android's own model of the Earth's field gives the difference for the saved place, so the heading and the qibla marker are both measured from true north.
tools/compass-panel-qibla — The compass panel: rotating rose, degree readout, and the qibla marker pointing toward Mecca.
### Options
Tools / Compass
- **Degree readout**: on by default. It shows the numeric heading and a 16-point cardinal (N, NNE, NE, … NNW) under the rose. Before the sensor has reported it reads "Calibrating…" instead.
- **Show the qibla**: off by default. Turn it on before saving a location and the settings screen shows a caution of its own: "No location is saved yet. Set one under Tools → Weather."
## Bubble level
**Bubble level** turns the accelerometer, or the gravity sensor on a device that has one, into three gauges at once. A circular bullseye bubble combines pitch and roll, and a vertical and a horizontal tube vial each track one axis alone. The bullseye turns accent-colored once both pitch and roll are within about a degree of flat. Each tube vial turns accent-colored independently on its own single-axis threshold, so a vial can read level a moment before or after the bullseye agrees.
tools/level-panel — Bubble level: a bullseye bubble plus a vertical and horizontal tube vial, with an angle readout below.
### Options
Tools / Bubble level
- **Angle readout**: on by default. It shows pitch and roll in degrees below the gauges, colored to match the bullseye's flat/not-flat state.
## Moon phase
**Moon phase** is arithmetic rather than a lookup. The panel computes the current phase from a fixed synodic-month constant and a reference new moon, with nothing fetched over the network. It's accurate to within a few hours, which is plenty for "is it worth looking up tonight" and not built for precise astronomical prediction.
The panel shows a painted lit/dark disc, one of eight phase names (New moon, Waxing crescent, First quarter, Waxing gibbous, Full moon, Waning gibbous, Last quarter, Waning crescent), the percentage illuminated, and how many days into the roughly 30-day cycle you are. Under that is a short timetable of the four main phases (new moon, first quarter, full moon and last quarter), each with its day and time: the one that has just passed, in a dimmer color, then the next three.
tools/moon-phase-panel — Moon phase: the current lit fraction, phase name, and next full/new moon dates, computed on-device.
### Options
Tools / Moon phase
- **Arabic names too** (off): shows each phase's Arabic name as well, under the name in your language and beside each phase in the timetable: محاق, هلال, تربيع أول, أحدب متزايد, بدر, أحدب متناقص, تربيع ثانٍ and هلال متناقص.
- **Southern hemisphere**: read off your phone's region, so it starts on in Australia, Brazil or South Africa and off in Bangladesh or the UK. It mirrors the lit disc the way the moon actually looks south of the equator. Only the drawing changes, never the phase name, dates or illumination percentage.
## Weather
**Weather** shows current conditions and a same-day high/low for one saved location, sourced from [Open-Meteo](https://open-meteo.com). No account and no API key are needed. You set the location yourself: search a city or place name (which hits Open-Meteo's geocoding endpoint), type latitude and longitude directly, or tap **Use my location** to read the phone's position once. Only that last one asks for location access, and only the first time you use it. The saved place doesn't follow you afterwards; it stays put until you change it.
A fetched result is reused for as long as **How often to check** says, 15 minutes out of the box, so reopening the panel is usually instant. A refresh button in the panel forces a new request. Until you save a location the panel shows a prompt with an **Open settings** shortcut and a **Use my location** button instead of any weather data. If a fetch fails it shows a retry button rather than stale numbers.
tools/weather-panel-ready — Weather: current conditions plus a stat grid: feels-like, humidity, wind, and whatever else Open-Meteo returns for that location.
Once a location is set, the panel shows an emoji and short description for the current conditions, the place name with today's high and low, and a wrapping grid of stats. Feels-like temperature, humidity, and wind speed with an 8-point cardinal direction always appear. Rain chance, cloud cover, pressure, UV index, sunrise and sunset each appear only when Open-Meteo's response actually includes them.
### Options
Tools / Weather
tools/weather-options-location — Weather's settings: a location search or coordinate entry, and the Fahrenheit toggle.
- **Location**: unset by default. It opens a dialog with **Use my location** at the top, a search field backed by Open-Meteo's geocoding API, and manual latitude and longitude fields if you'd rather skip both. The same dialog adds places in the [Location](https://wmkeyboard.pages.dev/tools/location/#saved-places) tool.
- **Fahrenheit**: off by default, so temperatures show in Celsius until you turn this on. It moves the current, feels-like, and day's high and low temperatures. **Wind speed follows this switch too**: km/h with Celsius, mph with Fahrenheit. Pressure stays in hPa, which has no second unit here.
- **Fetch the forecast as you type**: on by default, off in the F-Droid edition. On, a weather chip fetches the forecast it needs as soon as you type a weather question. Off, the chip reads **Get the forecast** and fetches nothing until you tap it. The panel fetches when you open it either way.
- **How often to check**: how long a reading is reused before the tool fetches again. 1 to 180 minutes, default 15. The panel's refresh button still forces a fetch whatever this says.
Those four rows are the whole screen. The settings screen carries its own privacy note underneath: "The weather and the place search both use Open-Meteo, only when you use them. The keyboard makes no other network requests." Weather is the only one of the five instruments that reaches the network at all. The one exception to that note is **Use my location** with the Location tool's address lookup on, which asks the address server for the city's name once. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what every network-touching feature sends and when.
The flashlight, the compass, the bubble level and the moon phase are the **Instruments** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/), and weather is part of **Weather and location**. A bundle that is off takes its tools off the toolbar, the toolbox and the Tools list until it is switched back on.
## Screenshot
**Screenshot** takes a screenshot of the app you're in, the same one the power and volume keys take. The keyboard goes down first, so it isn't in the picture, and stays down afterwards. Android saves the screenshot where every screenshot goes and shows its usual preview, with its own editing and sharing. Bind it to the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#gestures) and the button steps out of the picture too, coming back a moment later.
Android lets another app take a screenshot only through an accessibility service, so the tool needs one of its own, **WM Keyboard system buttons**, turned on in Android's accessibility settings. It presses Android's own buttons when you ask: the screenshot for this tool, and Back, recent apps and the like for the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#system-buttons). It reads nothing on screen. The first press while it's off explains this and then opens Android's accessibility settings; the tool's page under Tools / Screenshot has a **System buttons service** row that says whether it's on and takes you there too. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/).
It needs Android 9 or later, and it isn't in the Play Store build, which ships without accessibility services that aren't its main purpose. It doesn't work before your first unlock after a restart.
## Details & edge cases
- **None have a default hardware-keyboard letter.** Tools such as web search (W) and dictionary (D) ship with a leader-key letter, and none of these five do. Reach them through the toolbox shortcut (double-tap Ctrl, then T) or [rebind a letter to one yourself](https://wmkeyboard.pages.dev/reference/shortcuts/).
- **Four keep working before your first unlock. Weather doesn't.** Flashlight, Compass, Bubble level and Moon phase are all direct-boot safe. They're pure sensor readings or arithmetic, so they work right after a restart, before you've entered your PIN or pattern for the first time. Weather is the exception: it needs network access and reads a location saved in credential-encrypted storage, and neither is available until you unlock.
- **Every tool here has more than a bare switch.** Each of the five gets a "Tune" icon in Tools, meaning it has real options beyond the shared Enabled toggle. None of them is one of the toggle-only tools on that screen.
- **Colored tool icons** under Tools is on out of the box, so each of these five already wears its own accent color in the toolbox and in the Tools list itself. The pinned toolbar row keeps the theme's plain icon color regardless, same as every other tool.
For the toolbar and toolbox mechanics these five share with every other tool (pinning, the toolbox drag, and what "direct-boot safe" means more broadly), see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/).
---
# KDE Connect
URL: https://wmkeyboard.pages.dev/tools/kde-connect/
Section: Tools
Summary: Pair the keyboard with a computer: use its mouse and keys from your phone, control its music, share the clipboard and files, and let the computer type into your phone.
[KDE Connect](https://kdeconnect.kde.org/) links a phone to a computer over your home network. The **KDE Connect** tool speaks that protocol from inside the keyboard, so you can reach your computer from whatever app you are typing in, and the computer can type back. It works with KDE Connect on Linux, Windows and macOS, with GSConnect on GNOME, and with Valent.
You do not need the KDE Connect app on the phone. The keyboard shows up on the computer as a device of its own, named after the phone's model followed by **WM Keyboard**, for example *Pixel 8 - WM Keyboard*. If you do have the app, both keep working side by side, as two devices, and the name tells you which is which.
To move a file to another phone, or to anyone as a link, see the [Send files](https://wmkeyboard.pages.dev/tools/send/) tool: its Nearby mode runs on this same engine, and its direct link joins two phones with no Wi-Fi between them.
## Turning it on
**Use KDE Connect** is on from the start, but being on sends nothing by itself. The keyboard only goes on your network once a computer is paired, or while a device list is open in the panel or in settings. Turn the switch off under Tools / KDE Connect and it does nothing at all on your network: it sends nothing and listens for nothing.
After that the panel lists the computers it can see. On the computer, KDE Connect (or GSConnect, or Valent) has to be running.
## Pairing
1. Press **Pair** next to the computer's name.
2. An eight character code appears on the phone. The same code appears on the computer.
3. If the two match, accept on the computer. You have 30 seconds.
You can also start from the computer: pick the phone there, press pair, and accept on the phone. That only works while the keyboard's device list is open, because that is the only time the keyboard talks to computers it has not paired with.
The code is what makes pairing safe. It is calculated from both devices' keys, so a match means the computer on your screen is the one in front of you and not something else on the Wi-Fi. After pairing, the computer's certificate is remembered and nothing else is accepted under its name.
### Pairing with WM Keyboard on a computer
WMK Desktop, WM Keyboard for the Mac, shows a pairing code under **Backup & restore › Sync devices › Pair a phone**, and on its setup page. Point the phone's camera, or any QR scanner, at the code and open the link it shows, and WM Keyboard opens on the devices screen. Or press **Scan a code** in this panel, next to **Add by address**, which opens the keyboard's own [QR scanner](https://wmkeyboard.pages.dev/tools/scanner/) (not in the F-Droid build). A link opened from the camera or another app first asks whether to pair, naming the computer and its address, because any web page or app can open such a link; press **Pair** only for a code you scanned from your own computer. The keyboard's scanner pairs at once. Either way there is no code to compare. The link holds the computer's address and its certificate fingerprint, so the phone pairs only with the computer that showed it, and its one-time token works once.
Once paired, the two sync through each other: see [Sync between devices](https://wmkeyboard.pages.dev/privacy/backup/#sync-between-devices).
### If the computer does not show up
- Both devices have to be on the same network. Mobile data does not work, and many guest networks keep devices apart.
- The computer's firewall has to allow ports **1714 to 1764**, for both TCP and UDP.
- Press **Add by address** and type the computer's IP address. The phone then announces itself to that address directly, which gets through on networks that block discovery.
- If another KDE Connect app on the phone is already using the discovery port, the keyboard can still be found. Press **Refresh** on the phone rather than waiting for the computer.
## The tabs
Tap the device name in the panel's header at any time to switch computers, pair another one, or unpair.
Swipe sideways across the panel to move between tabs. On the touchpads a swipe moves the pointer instead, so start it off the pad.
Not every computer can do everything. A tab or button for something the computer does not support is left out, or says so when you open it.
### Input
The big area is a **touchpad** for the computer.
| Gesture | What it does |
|---|---|
| One finger drag | Move the pointer |
| Tap | Left click. Tap twice for a double click |
| Two finger tap | Right click |
| Three finger tap | Middle click |
| Two finger drag | Scroll |
| Hold still, then drag | Drag with the button held. Lift to drop |
| Tap, then drag straight away | The same drag, without the wait |
Slow, careful movement moves the pointer by single pixels rather than not at all, and a steady drag keeps a steady pointer speed, so the pointer glides rather than stepping.
The L M R buttons beside it are the mouse buttons, for when a tap is not precise enough.
Press **Type on computer** and the keys come back under a smaller panel. Now everything you type goes to the computer instead of the app: letters, glide typing, suggestions you pick, autocorrections, emoji, and any language the keyboard has. A strip of keys a phone layout does not have sits on top: Esc, Tab, the arrows, Del, Home, End, page keys and F1 to F12. Ctrl, Alt, Super and Shift stay lit until the next key, so Ctrl then c copies on the computer.
**Compose** changes how typing is sent. Normally each keystroke goes out as you make it. With Compose on you write a whole line on the phone, fix it, and send it with Enter. The **Send** button sends the line without pressing Enter on the computer.
### Media
Whatever is playing on the computer: artwork, title, a seek bar, previous, play or pause, next. Shuffle and repeat appear for players that have them. If several players are open you can switch between them. The slider at the bottom is the computer's own volume, with a mute button.
KDE Connect on macOS does not let a phone control its players, so on a Mac this tab has only the volume. The official KDE Connect app has the same limit there. A [Run](#run) command such as `osascript -e 'tell application "Music" to playpause'` is a way around it.
### Send
Until something has been sent or received, the three buttons fill the tab as large tiles. Once there are transfers they shrink to a row above the list.
- **Clipboard** sends what is on the phone's clipboard right now.
- **Text in the field** sends your selection, or the whole field if nothing is selected.
- **Files…** opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) (Android's picker in the Play Store build) and sends what you choose.
- A place goes from the [Location tool](https://wmkeyboard.pages.dev/tools/location/#sending-a-place-to-your-computer) instead: its **Send to** button sends it as a map link, which the computer opens in its browser.
Transfers are listed with their progress and can be cancelled. Files the computer sends to the phone are listed here too, with an **Open** button.
### Run
The commands you have set up on the computer, as buttons. Output shows up live while a command runs, and **Stop** ends it. Commands are created on the computer, in its KDE Connect settings. **Set up on computer** opens that screen there.
### Slides
Big **previous** and **next** buttons for a presentation, **Start** (F5) and **End** (Esc). Hold and drag on the pad between them to show the computer's pointer dot and move it around. Let go and it disappears.
### Device
**Ping** the computer, **Ring it** to find a laptop in a bag, and **Lock** or **Unlock** its session where the desktop supports it. The device's address, battery and certificate fingerprint are here, along with **Unpair**, and **Pause clipboard**: see [Pausing clipboard sharing](#pausing-clipboard-sharing).
## What the computer can do
- **Type into the phone.** On the computer, open the phone in KDE Connect and use its remote keyboard box. What you type lands in the field on the phone. Arrows, Home, End, selection with Shift, word moves with Ctrl, Ctrl+A C X V Z, and Enter all do what you expect. This works only while the keyboard is on screen and the phone is unlocked.
- **Share its clipboard.** Text copied on the computer is ready to paste on the phone, and shows up as the paste suggestion.
- **Send text, links and files.** Text and links arrive on the clipboard. Files are saved to **Downloads/WM Keyboard** and added to the [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/), so you can paste a picture from the computer straight into a chat.
- **See the phone's battery**, and **control the phone's media** if you allow it.
## Pausing clipboard sharing
Sometimes you want the clipboard to stay on one side for a while, say while you copy passwords on the computer or show your screen to someone, without unpairing and without flipping the clipboard switches one by one.
In the panel, open the **Device** tab and press **Pause clipboard**. Pick how long: **15 min**, **1 hour**, or **Until I resume**. Tick **All computers** first to pause every paired computer at once instead of only the one you are on. The same choice is on the **Devices** screen under Tools / KDE Connect / Devices: a **Pause clipboard** button under each computer, and an **All computers** row at the top when more than one is paired.
While a computer is paused, nothing you copy on the phone goes to it, and nothing it copies lands on the phone. Everything else keeps working: typing, the touchpad, media, files. A timed pause ends by itself, and a pause outlasts a restart of the phone or the keyboard. To end it sooner, press **Resume clipboard** in the panel, **Resume** on the Devices screen, or **Resume** on the notification that shows while a pause is on. A computer held by a pause on all of them is resumed together with the rest.
**Send › Clipboard** still sends while paused, because pressing it is asking for that one clip to go. Pauses are kept on this phone only and never sync to another device.
## Options
Tools / KDE Connect
KDE Connect is the **KDE Connect** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool, the devices screen and the pairing leave the keyboard and the app; the pairing itself is kept until you switch it back on.
**Use KDE Connect**: on by default. The master switch. Nothing goes on the network until a computer is paired or a device list is open.
**Devices**: paired computers and the ones nearby, with a switch per computer for clipboard, typing, files, phone media, battery and sync, and a [pause for the clipboard](#pausing-clipboard-sharing). Also the list of addresses added by hand, and this phone's certificate fingerprint.
**Name shown on the computer**: blank uses the phone's model followed by *WM Keyboard*, shortened to *WMK* when the model name is long. Up to 32 characters, and the protocol does not allow punctuation.
**Stay connected**: default **Keyboard showing**.
- *Panel open*: only while the KDE Connect panel is open.
- *Keyboard showing*: while the keyboard is on screen and for a minute after, so switching apps does not reconnect every time.
- *In the background too* (called *Always* before 0.6.4): the link also stays up after the keyboard hides, so the clipboard can keep syncing while you are not typing. There is no background service behind it. The link lives inside the keyboard, and once the keyboard is off screen Android is free to pause or close it, which many phones do within minutes and some at once; the link drops then, and comes back the next time the keyboard shows. Setting WM Keyboard's battery use to *Unrestricted* in Android's app settings keeps it up longer on most phones, but nothing guarantees it. Power saving, with its *Background network* switch on, puts it back to *Keyboard showing*.
**Connect automatically**: default on. Reconnect to paired computers when they are found.
**Receive the computer's clipboard** and **Send this phone's clipboard**: both default on.
**Clear received passwords**: default **1 minute**, up to 30, or *Never*. Text from the computer that looks like a password or a one-time code is taken off the phone's clipboard after this long, along with its unpinned copy in clipboard history. It is only cleared if it is still the latest clip, so something you copied since stays. Ordinary text from the computer is never cleared.
**Let the computer type here**: default on.
**Use the keyboard language**: default off. Off, what the computer sends is typed exactly. On, its keys go through the keyboard's layout the way a [hardware keyboard's](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) do, so a Latin keyboard on your desk can write Bengali through Avro, with suggestions and autocorrect.
**Pointer speed**, **Acceleration**, **Scroll speed**, **Natural scrolling**, **Tap to click**, **Vibrate on clicks**: how the touchpad feels. Speeds go from 0.3× to 3×.
**Receive files**: default on.
**Show in the share menu**: default on. Adds **Send to computer** to other apps' share sheets. The entry only exists once a computer is paired, so it never appears for someone who does not use the tool.
With more than one computer paired, **Send to computer** asks which one. Computers that are still connecting are listed greyed out and become selectable as they connect.
Only files another app shares from its own storage go through. A share that names a file inside WM Keyboard's own storage is ignored, so an app cannot use **Send to computer** to send out the keyboard's settings or clipboard history.
**Report battery level** and **Offer this phone's media**: both default on. Media needs the same notification access as the [media controls](https://wmkeyboard.pages.dev/tools/media/) tool, and does nothing without it.
**Send one-time codes**: default off. A verification code that arrives in a notification goes to the paired computer, the code alone and never the message around it. WM Keyboard for the desktop offers it above the cursor; a KDE Connect desktop shows it as a notification. It needs **Codes from notifications** on under Typing, which is what reads them.
Notifications for a pair request, a file or a ping that arrives while the keyboard is hidden, and the one shown while clipboard sharing is paused, have their own switch under Notifications.
## Details and edge cases
- Everything stays on your local network. There is no account and no server. Both ends encrypt the connection with TLS, and each side pins the other's certificate at pairing.
- Passwords and one-time codes are never sent on their own, even with clipboard sending on. Nothing is sent from a password field, or while [incognito](https://wmkeyboard.pages.dev/privacy/incognito/) pauses the clipboard. **Send › Clipboard** still sends whatever is there, because you asked.
- A password or code from the computer is marked sensitive on the phone, the way a password manager marks one, so Android 13 and later hide it in the copy preview and clipboard history treats it as a secret.
- Clipboard sync is separate from clipboard history. Turning history off does not stop the sync, and the other way round.
- Computers you have not paired with are only contacted while a device list is open, in the panel or in settings. The rest of the time the keyboard only talks to computers it already knows.
- The tool is hidden on the lock screen and before the first unlock after a restart. A computer cannot type into a locked phone.
- The phone's key and the paired computers' certificates are left out of backups on purpose. Restored onto another phone they would put two devices on your network under one identity. Pair again on a new phone.
- The scroll direction and speed can differ between desktops, because the protocol does not define a scale. Both are settings for that reason.
- Text, links and clipboard only. Images on the clipboard are not synced. Send them as files.
- Not supported: SMS and calls, notification mirroring, browsing the phone's files from the computer, and ringing the phone. Those need permissions a keyboard should not hold. The KDE Connect app does them well, and the two can run together.
---
# Learn from text
URL: https://wmkeyboard.pages.dev/tools/learn-from-text/
Section: Tools
Summary: Teach the keyboard your words from something you already wrote: it lists the words it doesn't know, and you add them in one go.
A new keyboard doesn't know your names, your slang or the jargon of your job, and normally it only picks them up one sighting at a time as you type. **Learn from text** skips the wait. Open it in a note, an email draft or anything else you wrote, and it reads the text, lists every word the keyboard doesn't know yet, and lets you add the ones you want to your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) at once.
tools/learn-from-text — Learn from text listing the new words in a note, all checked, with Add 5 in the header.
## What it reads
It starts switched off, whatever you picked during setup, so turn it on first in Tools. Then open **Learn from text** from the toolbar, or from the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). It reads the text you have selected. With nothing selected, it reads the whole field. It never moves your cursor and never changes the text.
A few things are left out on purpose:
- **Password fields.** The panel says so and reads nothing.
- **Words the keyboard already knows.** That means words in the word lists for your current language and its [secondary languages](https://wmkeyboard.pages.dev/smart/mixed-language/), your imported lists, your personal dictionary, Android's personal dictionary, your contacts and your app names.
- **Words on your [blacklist](https://wmkeyboard.pages.dev/smart/blacklist/).**
- **Things that aren't words:** numbers, single letters, links, email addresses and @handles.
- **Chinese, Japanese and the other scripts written without spaces**, where the run of letters between two spaces is a phrase rather than a word. Thai, Lao, Khmer and Burmese are the exception: the keyboard finds the words inside their runs and lists those. Burmese written in Zawgyi is still left out, since the words can only be found in Unicode Burmese.
Some apps only hand a keyboard part of a long field. When that happens a line at the top says only part of it could be read. Select the part you care about and open the tool again. The list stops at 2,000 words, and a note says so when a text has more.
A word only in a language that isn't in your current mix shows up as new. Switch to that language first, or add it as a secondary language, if you're reading text in it.
## The list
If the text holds nothing the keyboard doesn't already know, the panel says so, and under that it points the other way: the words already learned are not listed here but in the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) in Settings, which **Open the dictionary** takes you to. This tool is for adding words, not for reviewing them.
Every word gets a row, and every row starts checked. Each row shows the word, how many times it appears in the text (**×4**), and **seen 2** when the keyboard has already seen it while you typed but hasn't learned it yet (see [Words wait their turn](https://wmkeyboard.pages.dev/smart/personal-dictionary/#words-wait-their-turn)).
Tap a row to check or uncheck it. The header holds the rest:
- **Select all** / **Select none** flips every row at once.
- The sort chip cycles through **Most used**, **Text order** and **A to Z**. The panel remembers your choice for next time.
- **Word pairs** decides whether adding words also teaches the word pairs in the text (below).
- **Add N** adds the checked words.
The word's spelling comes from how the text writes it away from the start of a sentence. "Zelda" written mid-sentence is added with its capital, and the capital is kept for good. A word you only ever wrote at the start of a sentence is added in lowercase, because that capital says nothing about the word.
## Row actions
The ⋮ on a row opens four actions under it:
- **Edit** fixes the spelling before you add it: `teh` becomes `the`. The keys come up while you type. Press Enter or **Done** to keep the new spelling, or **Cancel** (or Back) to leave the word as it was. The row then reads **teh → the**. Only what gets added changes, never your text.
- **Show in text** selects the word's first appearance in the field, so you can see how it was used. Some apps don't tell the keyboard where text sits, and the action is greyed out there.
- **Never suggest** puts the word on the [blacklist](https://wmkeyboard.pages.dev/smart/blacklist/) and takes the row away.
- **Ignore** hides the row for this list only. Open the tool again and the word is back, and typing it still counts toward learning it the usual way.
## Adding
**Add N** (or **Learn text** when no new unknown words remain) puts every checked word in your personal dictionary and compiles the document's word and n-gram usage statistics. Each new word is marked as added by hand, so it's safe from autocorrect straight away and reads "You added this word" on the dictionary screen. It leaves the waiting room, comes off the blacklist if it was on it, and is copied to Android's dictionary too when **Add words to the system dictionary** is on. Known words and n-grams in the text also have their usage counts learned into your typing history, building your personal language model. The added rows disappear, and a line at the top says what happened: "Added 5 words · 12 word pairs".
With **Word pairs** on (the default), the same tap teaches the pairs the text holds, the way typing the text would have: two-word pairs, three-word contexts and the word two back. So "see you soon" helps predict "soon" after "see you". The same rules as typing apply:
- Both ends of a pair have to be words the keyboard knows once your words are in. A word you left unchecked never becomes a next-word suggestion.
- A pair never crosses a full stop, a question mark, an exclamation mark, a danda or a line break.
- Each pair counts as many times as the text repeats it, the same as typing the text would have, so a phrase you use often in your writing is predicted sooner.
- Pairs follow **Learn from typing**. With learning off, or paused in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), words are still added (you asked for them) but no pairs are learned.
The panel closes when you move to another field, since what it read belonged to the old one.
## Options
Tools / Learn from text
The tool has no settings page of its own beyond its **Enabled** switch and the toolbar hold action every tool has. Its two choices, the sort order and **Word pairs**, live on the panel and are remembered from one use to the next.
| Choice | Default | Where |
| --- | --- | --- |
| Sort order | Most used | Panel header |
| Word pairs | On | Panel header |
To move words between your personal dictionary and Android's own in bulk, use **Import from Android's dictionary** and **Export to Android's dictionary** on the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#androids-dictionary) screen.
Learn from text is the **Learn from text** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on; what it taught the dictionary stays.
---
# Location
URL: https://wmkeyboard.pages.dev/tools/location/
Section: Tools
Summary: Share where you are, or a place you saved, as a map link, an address, coordinates or a Plus code, without leaving the chat.
Most chat apps can send a location, each in its own way, and some can't at all. The **Location** tool does it from the keyboard, so it works the same in every app: a tap finds you, you pick how the place should read, and the text goes into the box you're typing in. It also keeps a list of places you name yourself, like Home or Work, which go in with one tap and never need location access at all.
Open **Location** from the toolbox, or type `location` or `gps` and tap the chip that appears. Typing `I'm at` or `my location` offers it too, and keeps your words, so the place goes in right after them. The panel has two tabs, **Here** and **Places**, a precision chip, and a shortcut to the tool's settings. Swipe sideways across the panel to change tabs.
## Sharing where you are
The first time, **Here** shows a **Locate me** button and nothing else. Tap it and the keyboard explains what it is about to ask for, then Android shows its own location dialog.
- **Only this time** is a good answer. The keyboard reads your position once and is done with it, so the grant has nothing else to do. Next time you tap Locate me, Android asks again.
- **Approximate** works too. Android then hands over a point snapped to a grid of a few kilometres, and the panel says **Approximate** with an **Allow precise** link beside it for the day you want the street.
- **Don't allow** leaves the tool exactly as it was. Saved places still work.
Once Android allows it, the panel finds you the moment it opens, unless you turn **Locate when the tool opens** off. A position Android already holds from the last minute shows up at once, then the keyboard asks every location source the phone has switched on (the satellite receiver, the network one, and Android's own combined source on Android 12 and later) and keeps the best answer. It stops as soon as one is within about 25 metres, or after 20 seconds with whatever it has. Nothing keeps listening after that, and nothing runs in the background.
If location is switched off for the whole phone, the panel says so and offers **Turn it on**, which opens Android's location settings.
## Reading the panel
Instead of a map, **Here** shows a dial. A map would need pictures of the streets from a map server, and that server would learn roughly where you are every time the panel opened. The dial shows the two things you would check a map for, drawn to scale with north at the top:
- **The ring** is how sure the reading is. A tight ring is a good fix; a wide one means the phone is guessing.
- **The square** appears when you share at a coarser precision. It is the area that goes out, with a hollow dot at the point that is actually shared and the filled dot where you really are.
If you'd like streets behind the dial anyway, name a tile server under **Map behind the dial** in settings. The keyboard then fetches the few tiles around you, and only from that server. None is built in, because the public OpenStreetMap servers don't allow apps to use them this way, and because every tile request says roughly where you are.
Next to the dial: the address (when **Look up the address** is on), the coordinates, how accurate the reading is, how old it is, and its Plus code. **Open in maps** hands the point to your own maps app, which is the way to see it on a real map.
Under that is a row of formats. Tap one and the box below shows exactly what will be typed, then **Insert** types it. Hold a format to make it the default; the default carries a small dot.
| Format | What it types |
|---|---|
| **Message** (the default) | 📍 and the address on one line, the map link on the next. Without an address, the coordinates take its place. |
| **Link** | Just the map link. |
| **Coordinates** | `23.780600, 90.419300` |
| **Degrees** | `23°46'50.2"N 90°25'09.5"E`, the way Google Maps writes them. |
| **Address** | The short address, or the place's name. |
| **Plus code** | `7MMGQCJ9+6P`, a grid reference any maps app can search for. |
| **Custom** | Your own template, see [Options](#options). |
Numbers always use a dot and Latin digits, whatever language the keyboard is in, because a map app reads `23,78` as two numbers.
The buttons beside **Insert** save the place, open it in your maps app, make it the location the [weather and compass](https://wmkeyboard.pages.dev/tools/instruments/) use, and find you again.
## Precision
The precision chip in the panel's header cycles through four settings, and the setting is remembered:
| Precision | What a share gives away |
|---|---|
| **Exact** | The reading as it is. |
| **About 100 m** | A point in a 100 metre square: the street, not the house. |
| **About 1 km** | A point in a 1 kilometre square: the neighbourhood. |
| **City** | A point in a 10 kilometre square, and only the city's name. |
A coarser setting doesn't blur the point or add noise. It moves the point to the centre of a fixed square, and the squares are the same every time. That matters: ten noisy shares from one sofa can be averaged back to the sofa, but ten shares that all name the same square centre can't. Everything in a share uses the moved point, including the link, the Plus code and the address, and the address is looked up at a matching level, so a city share is answered with a city.
## Saved places
**Places** lists the places you saved. Tap one to insert it in the format that's selected; it needs no location access and no network. The distance beside each one appears once the panel has a reading. Its menu opens it in your maps app, makes it the weather location, or deletes it.
Save a place from **Here** with the bookmark button. It takes the first part of its address as its name, which you can change in settings. In Tools / Location you can also add a place by searching for it, by using where you are, or by typing its coordinates.
Saved places are ordinary settings: a backup carries them, and [sync](https://wmkeyboard.pages.dev/reference/settings/backup/) brings them to your other phone.
## Places somebody sent you
The **Found** tab reads the places in the text box and in your ten most recent clips: map links from Google Maps, Apple Maps, OpenStreetMap, OsmAnd, Waze and Bing, `geo:` links, coordinates in decimals or in degrees, minutes and seconds, and full Plus codes. Copy the link a friend sent and it's there when you open the panel. Each place shows where it was found, and its distance and direction from you once the panel has a reading.
Tap a place for what you can do with it:
- **Insert** types it in the format that's selected, so a long Google link becomes an address, a Plus code, or an OpenStreetMap link for someone who doesn't use Google.
- **Open in maps**, **Save this place**, and **Use for weather and compass**, as on the other tabs.
- **Point the compass** opens the [compass](https://wmkeyboard.pages.dev/tools/instruments/#compass-with-an-optional-qibla-bearing) with a 📍 marker on the rose pointing at the place, and the distance and direction under it. It measures from your current reading, or from the weather tool's saved place when there's none. **Stop pointing** under the rose takes the marker away.
Reading is done on the phone, and nothing is looked up. A shortened link such as `maps.app.goo.gl/…` names no place until a server is asked, so it isn't listed. The text box is never read while it is a password field, and clips marked sensitive are skipped. Numbers that only look like coordinates are left alone: a pair needs at least three decimals on each side, and coordinates inside a link that isn't a map link don't count.
## A link anyone can open
**Map link** in settings also offers **A WM Keyboard page**. The link goes to a page on this website with the place after the `#`, which a browser never sends to the site, so nothing about the place reaches it. The page shows the name, the coordinates and the Plus code, with a button for each map: Google Maps, OpenStreetMap, Apple Maps, OsmAnd and Waze, plus the phone's own maps app on Android. A map view is one more press away, and it loads from OpenStreetMap only then. Use it when you don't know which maps app the reader has. Another WM Keyboard reads the link back, name and all, in its Found tab.
## Sending a place to your computer
With a computer paired through [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/) and in reach, the **Here** tab has a computer button, and every place on **Places** and **Found** has **Send to** with the computer's name. The place goes as a map link, in the map you picked, which the computer opens in its browser. A `geo:` link has nothing to open it on most desktops, so with that choice the computer gets an OpenStreetMap link instead.
## In snippets
[Snippets](https://wmkeyboard.pages.dev/tools/snippets/) can carry a place. `{location}` is where you are, in the default format, and `{location:link}` or any other format name picks one: `message`, `link`, `coordinates`, `dms`, `address`, `plus_code`, `custom`. `{place:Home}` is a saved place by name, and takes a format the same way, `{place:Home:address}`.
A snippet never waits on the satellites. `{location}` uses the reading the panel has from the last 15 minutes, or the one Android already holds from the last 30, and the address only when it was already looked up for that point. With no reading at all it expands to nothing, says so, and starts one for next time. The precision you picked applies here too.
## Photos with the place on them
The [camera tool](https://wmkeyboard.pages.dev/tools/camera/#putting-the-place-on-a-photo) can stamp the place onto a photo, and type it into the box as the photo's caption. The camera button on **Here** opens the camera with the stamp on for one photo. Both use this tool's precision, address lookup and default format.
## Weather and compass
The [weather and compass tools](https://wmkeyboard.pages.dev/tools/instruments/) share one saved place. Both now offer **Use my location**: on the weather panel when no place is saved, on the compass when the qibla needs one, and in the place dialog under Tools / Weather. It reads your position once, names it after the city when the address lookup is on, and saves it. Nothing updates it afterwards, so a forecast for home stays a forecast for home while you travel.
## What leaves the phone
The position itself never leaves the phone by the tool's hand. It goes where you send the text, and nowhere else, with one exception you control:
- **The address lookup.** With **Look up the address** on, the keyboard sends the shared point, at the precision you chose, to an address server once per share. The default is [Nominatim](https://nominatim.org), the geocoder OpenStreetMap runs. The request names the keyboard, as Nominatim asks, and is limited to one a second. Answers are kept in memory, so changing the format doesn't ask again. In the F-Droid edition you can point it at your own Nominatim server under [Servers](https://wmkeyboard.pages.dev/reference/settings/servers/). Turn the lookup off and nothing about your position goes anywhere until you send it yourself.
The reading stays in memory for 15 minutes, so reopening the panel doesn't ask again, and is then forgotten. It is never written to storage unless you save a place. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) and [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/).
## Options
Tools / Location
- **Default format**: Message. What **Insert** types. Holding a format in the panel sets this too.
- **Map link**: Google Maps. Which map a link opens: Google Maps, OpenStreetMap, Apple Maps, OsmAnd, Waze, a `geo:` link, which opens the reader's own maps app but isn't a web address, or [a WM Keyboard page](#a-link-anyone-can-open) with a button for each.
- **Precision**: Exact. See [Precision](#precision). The chip in the panel changes this same setting.
- **Look up the address**: on. See [What leaves the phone](#what-leaves-the-phone).
- **Locate when the tool opens**: on. Off, the panel waits for **Locate me** every time, even when Android already allows it.
- **Custom format**: a template for the **Custom** chip. Words in braces are replaced: `{name}`, `{address}`, `{coords}`, `{lat}`, `{lon}`, `{dms}`, `{plus}`, `{link}` (in the map you picked), `{google}`, `{osm}`, `{apple}`, `{osmand}`, `{waze}`, `{geo}`, `{card}`, `{accuracy}` and `{time}`. A word with nothing to put in its place disappears, and a word in braces the tool doesn't know is left as you typed it. An example under the field shows the template filled in for the Eiffel Tower.
- **Tile server**: blank. A `{z}/{x}/{y}` address, such as `https://tile.example.org/{z}/{x}/{y}.png`, for a map behind the dial. Use a server whose terms allow it; tiles are kept in memory only.
- **Saved places**: each place, to rename, move or delete, and **Add a place**.
- **Location access**: Precise, Approximate or Not granted. Tapping it asks Android, or, once precise is granted, opens the app's page in Android's settings, where it can be taken back.
Location is part of the **Weather and location** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on.
## Details & edge cases
- **A build can leave location out entirely.** Building with `-Pwmkb.noLocation=true` removes both location permissions. The tool then keeps its saved places, and **Locate me** and **Use my location** disappear. See [Building](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-location-permission).
- **It isn't available before your first unlock.** Like the other tools that read your settings and saved data, Location waits until you've unlocked the phone once after a restart.
- **The compass now points at true north once a place is saved.** The phone's sensor finds magnetic north, which is up to twenty degrees off in parts of the world. With a saved place the compass knows the difference and corrects for it, so the qibla marker lands where it should.
- **No live location.** A shared position is a snapshot. Following someone as they move needs a server between the two phones, and this keyboard has none.
- **No hardware-keyboard letter by default.** Bind one under [shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/) if you use it often, or reach it through the toolbox shortcut, double-tap Ctrl then T.
---
# Look up
URL: https://wmkeyboard.pages.dev/tools/look-up/
Section: Tools
Summary: Look up a word in any app on a card over that app, from the text selection menu or by tapping it, with readings, pitch accent and Anki cards.
Look up defines a word in any app without leaving it, the way Yomichan and Yomitan do in a browser. A card opens over the app with the word's meaning, its reading and pronunciation, and its translations. From the card you can hear the word, keep it in your vocabulary list, or make an Anki flashcard of it.
The card answers from the [Dictionary tool's sources](https://wmkeyboard.pages.dev/tools/search/#dictionary-sources), in the order you set there. Add [imported dictionaries](https://wmkeyboard.pages.dev/tools/search/#imported-dictionaries) for Japanese, Chinese or any other language you are learning, and the card shows their readings, pitch accent and frequency too.
## Opening the card
There are two ways to reach a word.
**From the selection menu.** Select the word in any app and pick **Look up** from the menu that appears over the selection. On some phones it is behind the menu's three dots, and some apps leave other apps' entries out of their menu entirely ([why](https://wmkeyboard.pages.dev/smart/selection-actions/#when-the-entry-is-missing)). Select more than one word and the card shows a chip for each, with the first one looked up; tap another chip to look that one up. This works in every build, Play Store included, and needs no permission.
**By tapping it.** Give the [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/) the **Look up a word** action, on a gesture or in its quick menu. Use it, and a hint at the top of the screen says "Tap a word to look it up". Tap the word and the card opens for it. A swipe instead of a tap, or 10 seconds with no tap, cancels it. This needs the floating button's screen reading service, so it is not in the Play Store build. See [How a tap finds the word](#how-a-tap-finds-the-word).
## The card
- The word, with its reading over it in small kana when it is written in kanji, and its IPA when the source has it.
- A speaker button. It plays a recording of a real person saying the word, from Wiktionary or, with your own key, Forvo, and shows a chip for every voice it found, so you can pick another. See [Recorded pronunciations](https://wmkeyboard.pages.dev/tools/search/#recorded-pronunciations). When nobody has recorded the word it plays the source's recording if there is one, and otherwise, or when the recording can't be fetched, says the word in the dictionary's language: with a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) for that language, or else the phone's text-to-speech voice.
- The pitch accent and frequency line from an imported dictionary, as in "たべꜜる [2] · Frequency 512". The ꜜ marks where the pitch falls; [0] is a flat word.
- The meanings, grouped by part of speech, with examples where the source has them.
- [Translations](https://wmkeyboard.pages.dev/tools/search/#translations) into your languages, as the Dictionary tool shows them.
- A line naming the source or the imported dictionary that answered.
Under it, a row of actions:
- **Add to Anki** makes a flashcard of the word. See [Anki](#anki).
- **Add to My words** adds the word to the [vocabulary tool's](https://wmkeyboard.pages.dev/tools/vocabulary/) My words list. Shown while the Vocabulary tool is on.
- **Copy** copies everything on the card as text.
- **Ask AI** opens an [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) about the entry. Shown while the AI tool is on.
Tap outside the card, or press Back, to close it.
### Sheet or popup
Tools / Dictionary / Look up in other apps / Card for a tapped word
By default the card is a short sheet at the bottom of the screen, with the app showing above it. For a tapped word you can have it **Beside the word** instead: a small card just below the word, or above it near the bottom of the screen, with the word itself marked in your accent colour. A word from the selection menu always gets the sheet, because the menu does not say where on the screen the word is. In an app that does not report where its letters are (see below), the card sits by the piece of text you tapped, with nothing marked.
## How a tap finds the word
The screen reading service finds the smallest piece of text under your finger and asks the app where each of its letters is. Ordinary apps answer, so the card knows the exact letter you tapped and can mark the word.
- **Words with spaces.** The word is the one the tapped letter belongs to, as Android splits words for that language.
- **Japanese, Chinese, Thai and other text without spaces.** Look up does what Yomichan does: it tries the longest stretch from the tapped letter that an imported dictionary knows, inflected or not, down to one character. Tap the start of 食べました and the card opens on 食べる. Without an imported dictionary, Android's own word splitting decides.
- **Apps that do not report their letters.** Some apps built with Jetpack Compose, and many web pages, give the text but not where each letter is. Then the card opens with a chip for each word in the text you tapped, so you pick the word yourself.
Only the text under your finger is read, once, when you tap. Nothing is read at any other time, password fields are skipped, and nothing is kept. See [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/).
## Anki
Tools / Dictionary / Look up in other apps / Add to Anki
**Add to Anki** sends the word to [AnkiDroid](https://github.com/ankidroid/Anki-Android). Without AnkiDroid installed, the button says so. There are three ways it can go:
- **Automatic** (the default): straight to the deck when AnkiDroid has let WM Keyboard add to it, and otherwise AnkiDroid's note editor, filled in, for you to save.
- **Straight to the deck**: the first time, WM Keyboard explains and asks for AnkiDroid's permission. After that, a tap adds the note and the button says **Added to Anki**, or **Already in Anki** when the deck has a note for that word.
- **AnkiDroid's editor**: always the editor, filled in. This needs no permission.
Notes added straight to the deck go to the deck named under **AnkiDroid deck**, "WM Keyboard" by default, which AnkiDroid makes the first time. They use a note type named **WM Keyboard Look-up**, also made on first use, with six fields: Word, Reading, Pitch, Meaning, Sentence and Source. The sentence is the one the word was in, when it came from a tap or a longer selection. The front of the card shows the word and its sentence, and the back adds the reading, pitch, meaning and source. You can change the note type's look in AnkiDroid like any other. Each note is tagged `wm_keyboard`.
Through the editor, the word goes in the note's first field and the rest, one item a line, in the second, in whichever note type AnkiDroid's editor opens with.
## Options
Tools / Dictionary / Look up in other apps
- **Look up in the selection menu**: on by default. Off, Look up leaves every app's selection menu.
- **Tap a word on the screen**: opens the floating button's settings, to give it the Look up action. Not in the Play Store build.
- **Card for a tapped word**: **Sheet at the bottom** (default) or **Beside the word**. Not in the Play Store build.
- **Add to Anki**: **Automatic** (default), **Straight to the deck** or **AnkiDroid's editor**.
- **AnkiDroid deck**: the deck notes go to, "WM Keyboard" by default.
- **Takoboto**, **Shinjikai**, **Jisho** and **Wiktionary**: off by default. Each one turned on is offered in the Dictionary's **Open in** menu, and opens that site on the word, in the browser or in the site's own app when the app claims its links. They're sites with no way for the keyboard to ask them directly, so the answer is the site's own page.
- **Your own website**: any other dictionary site, as its address with `%s` where the word goes, such as `https://example.com/search?q=%s`. It shows in the menu under its host name.
## Details & edge cases
- **What leaves the phone.** The same as for the Dictionary tool: the word goes to an online source only when the sources before it did not know it, in the order you set. Imported and offline dictionaries answer on the phone. The surrounding text never leaves it.
- **Long selections.** The card reads at most 2,000 characters of a selection and offers chips for its first 40 words.
- **Japanese inflections.** The dictionary form is found for polite, negative, past, て and conditional forms, the potential, passive and causative, たい, ている and its kin, and い-adjectives, including chains of them such as 食べられなかった. Irregular readings outside those rules may not be found.
---
# Media controls
URL: https://wmkeyboard.pages.dev/tools/media/
Section: Tools
Summary: Play, pause and skip whatever is playing (with album art) from the keyboard.
Media controls turns the keyboard into a transport for whatever is playing elsewhere on your phone: album art, track title, a seek bar, and play/pause/skip. It's the same session your lock screen and quick-settings tile already show, so it saves you from switching apps just to pause a song.
tools/media-now-playing — Media controls showing album art, track info, a seek bar, and the play/pause/skip transport.
## Opening the panel
Open **Media controls** from the [toolbar, or the toolbox](https://wmkeyboard.pages.dev/tools/overview/) if you haven't pinned it. It isn't pinned out of the box, so the first time you'll likely reach it through the toolbox. Once notification access is granted it pins itself while music is playing, which is how you'll usually reach it after that: see [Pinning while music plays](#pinning-while-music-plays). On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press M. Unlike many other tools it has no suggestion-strip keyword shortcut, so there's no word you can type to summon it.
The panel shows one of three things:
- **Now playing**: album art (or a music-note placeholder if the app didn't publish any), title, artist and album, with a seek bar and Previous / Play-Pause / Next buttons.
- **"Nothing plays now"**: shown when no app currently holds an active media session, with a line underneath telling you to start a song, video or podcast in any app. Do that and its controls appear here automatically.
- **A one-time access prompt**: shown until you grant notification access. See below.
### The transport
- **Play/Pause** toggles playback. **Previous** and **Next** skip a track. Both go dim and unresponsive if the current app's session doesn't advertise that action. Some players don't expose a "next track" action for the current content, during a live stream or a single long file for instance.
- The **seek bar** only appears once the session reports a track duration. Drag it and release to jump playback to that point. It's disabled the same way the skip buttons are, whenever the session doesn't support seeking.
- Controls act on whichever app currently owns the active media session, usually the one you left playing. If more than one app is publishing a session at once, it's the one actively playing. There's no in-panel app switcher, so open the source app directly to change what's playing.
## Options
Tools / Media controls
- **Enabled**: which way this starts depends on how you answered the setup wizard. Media controls is only in the "show me everything" starter set. Pick either of the two simpler answers and it arrives switched off, so you turn it on here. Turning it off removes Media controls from both the toolbar and the toolbox.
- **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are turned on globally (on by default). It overrides this tool's icon colour, purple out of the box.
- **Pin while music plays**: on by default. See below.
- **Music players**: which apps count as music. Only shown while the pin is on.
- **Notification access**: reports whether the grant is in place, and opens the screen that gives it. Same grant as the one the panel asks for, in a row you can check without opening the keyboard.
## Pinning while music plays
With **Pin while music plays** on, starting a song puts Media controls on the toolbar by itself, so pause and skip are one tap away instead of a trip through the toolbox. Stop the player and the pin goes with it.
It is deliberately unobtrusive:
- The tool goes on the end of the toolbar. Nothing you pinned yourself moves, and their [hardware digit shortcuts](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) keep the numbers you already know.
- If you have already pinned Media controls by hand, nothing happens. There is one slot for one tool, and your own pin wins.
- The pin is never written to your pinned-tools list. It is drawn, not saved, so it cannot survive a reboot or turn up in a backup.
- Pausing does not unpin. The pin arms the first time an app on your Music players list actually plays, then stays through a pause so you can still hit play again, and clears when that player gives up its session.
- Dragging the pinned icon around the toolbar pins it properly, the same as dragging any tool out of the toolbox. That is the way to make it permanent.
The pin needs [notification access](#granting-notification-access), same as the panel. Without it, the keyboard cannot see that anything is playing, so nothing is ever pinned.
[Power saving](https://wmkeyboard.pages.dev/typing/power-saving/) turns the pin off. Keeping it accurate means watching the media session for as long as the keyboard is on screen, which is the sort of background work a low battery should not pay for. The tool stays in the toolbox.
### Which apps count as music
Tools / Media controls / Music players
Any app can hold a media session: a video, a game, a browser tab with an autoplaying clip. Pinning a music transport because of one of those would be noise, so the pin only fires for apps on this list.
WM Keyboard ships it ticked for the players most people use, streaming services, offline players, podcast and audiobook apps. No such list can name every music app, so the picker is there to fix the gaps:
- The **Players** group leads with the installed apps that look like players, which means anything declaring a media browser service (the one Android Auto and Assistant use to find music apps), plus whatever you have already ticked.
- The search box below finds anything else on your phone, by app name or by package name.
- **Reset to the shipped list** puts back the ticks a fresh install starts with.
- An app you ticked and later uninstalled stays in the list, marked as not installed, so reinstalling it does not mean finding it again.
Untick everything and nothing ever pins, which is a perfectly good way to keep the pin switched on but silent for now.
## Granting notification access
The Android API that hands over another app's media session is `MediaSessionManager`, and it's gated behind notification-listener access, the same permission class notification-reading apps use. This tool never reads a single notification anyway. One other feature shares that grant: the [one-time code chip](https://wmkeyboard.pages.dev/smart/one-time-codes/), which does read notification text, and only while you have it switched on. See [privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for how this fits the keyboard's per-tool, opt-in approach to anything that touches data outside the keyboard itself. The panel explains the grant up front:
> WM Keyboard needs notification access to show and control the media. This tool reads no notification content, only the media session. The same access powers the optional one-time code chip, which reads notification text only while you have that turned on.
tools/media-access-prompt — The access prompt shown before notification-listener access is granted.
Tap **Allow access**. A short explanation appears first, restating what the access is for. Tap **Open system settings** on it. Your device's Notification access screen then opens on WM Keyboard's row. On Android 10 and older it opens the full list instead, so find WM Keyboard yourself. Look for **"WM Keyboard media controls and one-time codes."** That's the label the system lists it under, and it names both features because both ride on the one grant.
An IME cannot show its own permission dialog, which is why this detour through system Settings is needed. You do it once, and you can revoke it at any time from the same Settings screen. The keyboard re-checks the grant every time it returns to the foreground, so grant it in Settings and come back: the panel picks it up without being reopened.
Media controls are the **Media controls** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool, the pin and the music apps screen leave the keyboard and the app until the bundle is switched back on.
## Details & edge cases
- **Works with anything that publishes an Android media session**: music players, YouTube, podcast apps and the like. That isn't every app that makes sound. An app has to implement the platform's Media Session API for its controls to show up here. One that plays audio without publishing a session won't appear.
- The **listener service** that unlocks this (`MediaNotificationListener`) is one component worn by two features. For media controls it is an empty stub: the tool needs the grant, not the notifications, and nothing on that path reads a notification. The service's one working method belongs to the [one-time code chip](https://wmkeyboard.pages.dev/smart/one-time-codes/), and it returns immediately unless that chip is switched on. Never renamed, because Android stores the grant against the component name.
- The seek bar is touch-only: it doesn't participate in hardware-keyboard focus-ring navigation, so Tab/arrow-key cycling skips over it. The three transport buttons do take the ring, and Enter on a dimmed Previous or Next does nothing, exactly like a tap.
- Media controls has no direct-boot support. It isn't available on the lock screen before you've unlocked your device once after a restart, since the notification-listener grant and session data aren't available that early.
- This tool ships in every build. There's no build gating to worry about here.
---
# Offline voice (Moonshine)
URL: https://wmkeyboard.pages.dev/tools/moonshine/
Section: Tools
Summary: Quick English dictation on your phone with Moonshine Tiny, a model made for short phrases.
Offline Moonshine is an English dictation engine that runs entirely on your phone, with [Moonshine Tiny](https://github.com/moonshine-ai/moonshine), a 27-million-parameter model made for short phrases on small devices. Once the model is downloaded, no audio leaves the phone. While a layout in another language is in use, dictation uses the system recognizer instead.
## Setting it up
Voice typing
Add **Moonshine** to the **Recognition engine** list and move it to the top, then press its row to open its screen and download the model from the **Moonshine model** group there: 151 MB in three files, from Hugging Face at a fixed revision, each checked against its published SHA-256.
## How it works
Moonshine transcribes a phrase once you stop, and a phrase can run to 30 seconds. Where Whisper always pads what you said to a 30-second window, Moonshine reads only as much audio as there is, so a three-second phrase costs a three-second encode, which is what makes it quick on a phone. The text arrives punctuated and capitalised, then goes through the same spacing, spoken punctuation and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)).
## Moonshine, Asr4All or Whisper
For English, [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) shows the words while you speak, and the [whisper.cpp Whisper models](https://wmkeyboard.pages.dev/tools/whisper/) take your own words as hints and run to two minutes. Moonshine is the small, quick one: the least to download among them for its accuracy, and the fastest to answer a short phrase.
## Details & edge cases
**No words to listen for.** Moonshine has no way to take a list of names or terms, so **Words to listen for** does not show while it is the engine.
**Every build.** The runtime, [ExecuTorch](https://github.com/pytorch/executorch) built for the keyboard's models alone, is in every build; F-Droid compiles it from source. A build made without it does not offer the engine.
**The licence.** Moonshine is Useful Sensors' model, under the MIT licence; it is credited in the app's About screen.
---
# Music ID
URL: https://wmkeyboard.pages.dev/tools/music-id/
Section: Tools
Summary: Name the song that is playing from the keyboard through a recognition service of your choice, then type it into the chat or send it as a picture.
Somebody asks what is playing, or you want to tell them. **Music ID** listens for ten seconds, names the song, and puts it into the box you're typing in as text, or as a picture with the cover on it. It works the way Shazam does, without leaving the chat. The keyboard carries no recognition service of its own: you add one with a key of yours, or point it at a server you run, and the clip goes there.
Open **Music ID** from the toolbox, or type `shazam` and tap the chip that appears. Typing `what song is this` or `what's this song` offers it too, and keeps your words, so the answer goes in right after them. The panel has two tabs, **Listen** and **History**, and a shortcut to the tool's settings.
## Listening
Tap the big button and hold the phone towards the music. The ring around the button follows what the microphone hears, and the count under it says how long is left; ten seconds by default, which is what the services work best with. Tap again to ask early once four seconds are in. The keyboard records only while the panel listens, and never keeps the recording.
Before it listens, the tool looks at what the phone itself is playing. With [notification access](https://wmkeyboard.pages.dev/tools/media/#granting-notification-access) granted and a player running, the song comes straight from the player, with its cover, and no recording is made at all; **Listen anyway** records regardless, for the song playing in the room rather than in your headphones. Turn **Check what the phone plays first** off to skip this.
Then the clip is asked about: the recognisers on the tool's list, one after the other, until one names the song. The clip goes up compressed as Opus (about 25 KB) where the keyboard has that library, as a 16 kHz WAV (about 320 KB) otherwise. With no recogniser on, the panel says so and opens the settings.
## What you get
The song's cover, title, artist, album and year, and who named it. Under it:
- **Insert** types the song in the format picked in settings, **Title – Artist** out of the box. The chips beside it type the other shapes, each shown as it would read: artist first, with the album and year, the link alone, the title with the link under it, or `🎵 Now playing: …`.
- **Picture** opens a row of styles and sends the song as a picture: the **Cover** alone, a square **Card** with the words under the cover, a wide **Banner** with the words beside it, or a tall **Story**. The ground is coloured after the cover. The picture goes into the field the way every picture from the keyboard does: as a PNG, WebP or JPEG, whichever the app takes, and to the clipboard or the share sheet where it takes none. Without a cover the picture carries a note glyph instead.
- **Open in** opens the song in a store the recogniser linked: Spotify, Apple Music, Deezer, YouTube, a song.link page that lists every store, MusicBrainz, or Shazam. Which one comes first is **Open in** in settings.
- **Copy** puts the default text on the clipboard.
- The round button listens again.
A song that came without a cover or links (ACRCloud and AudioTag answer with the names alone) is looked up by its title and artist on the iTunes Search API and, failing that, Deezer, both without a key, to fetch the cover and a store link. The lookup sends the title and artist and nothing else, and only when data saving allows online recognition outright; see below.
## History
Every song recognised is kept in the **History** tab, newest first, on this phone only, up to 200. Tap one to have it back on the Listen tab with its inserts; the bin on a row forgets it. The same list is under Tools / Music ID / Recognised songs, where each row shows what it would type. **Keep a history** off keeps nothing new.
## Recognisers
Tools / Music ID / Song recognisers
A [provider list](https://wmkeyboard.pages.dev/tools/providers/). Every kind is listed but off until you give it a key and switch it on, since each is sent the clip; the tool stays off the toolbar until one is.
| Recogniser | Needs | Cost | What it is sent |
|---|---|---|---|
| **AudD** | a token from [dashboard.audd.io](https://dashboard.audd.io) | a few hundred recognitions free, then paid | the clip and the token |
| **ACRCloud** | a project's host, access key and access secret from [console.acrcloud.com](https://console.acrcloud.com) | a two-week trial, then paid plans | the clip, signed with the key and secret |
| **AudioTag** | a personal key from [audiotag.info](https://audiotag.info) | free for personal use, a handful a day | the clip; the answer takes a few seconds |
| **Shazam (unofficial, via RapidAPI)** | a RapidAPI key, subscribed to the Shazam listing | a free quota, then paid | the last five seconds of the clip, raw |
| **Your own recognition server** | its address, and a key if it wants one | yours | the clip, as AudD takes it |
Shazam's row is unofficial: Apple offers no API an open source keyboard could ship, so this one goes through a third party's listing, which can change or stop without notice. Its description says so.
Every row is skipped in a password field and while typing in incognito, and held back under [data saving](https://wmkeyboard.pages.dev/reference/settings/data-saver/): the **Song recognition online** row there asks before each clip on mobile data by default.
## Your own recognition server
A server you run that takes the clip and answers with the song. It speaks AudD's shape, so anything that does, or a thin wrapper around a fingerprinter such as [Panako](https://github.com/JorenSix/Panako), [Olaf](https://github.com/JorenSix/Olaf) or [audfprint](https://github.com/dpwe/audfprint), fits:
- The keyboard posts `multipart/form-data` to the address you give, with the clip in the `file` field (`clip.ogg`, Ogg Opus, or `clip.wav`, 16 kHz 16-bit mono WAV; a server that answers 400, 415 or 422 to the Opus upload is sent WAV from then on) and, when you set a key, `api_token`.
- A match answers `{"status":"success","result":{"artist":"…","title":"…","album":"…","release_date":"2019-05-17","label":"…","timecode":"00:36","song_link":"…"}}`; any of the fields may be left out. No match is `{"status":"success","result":null}`. A refusal is `{"status":"error","error":{"error_code":900,"error_message":"…"}}`, and the message is what the panel shows.
- AudD's optional `apple_music`, `spotify` and `deezer` objects are read for the cover and the links when present.
This is what the F-Droid edition's no-proprietary-services rule asks for: every online step has a self-hosted alternative. The iTunes and Deezer lookups can be pointed elsewhere under [Servers](https://wmkeyboard.pages.dev/reference/settings/servers/), under **Song recognition**.
## What leaves the phone
- **The clip** goes to each recogniser that is asked, with your key for it, until one answers. Each is its own row in the [network activity log](https://wmkeyboard.pages.dev/privacy/network/), so the log says which service heard it. The recording is never kept and never sent anywhere else.
- **The title and artist** of a song already named go to the iTunes Search API or Deezer for its cover, when the recogniser gave none.
- **The microphone** is used only while the panel listens, and released when the panel closes, the keyboard hides or the field goes.
- **Nothing at all** when the song came from the player on the phone, unless you ask for a cover it did not have.
See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) and [Permissions](https://wmkeyboard.pages.dev/privacy/permissions/).
## Options
Tools / Music ID
- **Song recognisers**: the list above.
- **Insert as**: Title – Artist. What **Insert** types.
- **Picture style**: Card. What the **Picture** chip makes first.
- **Open in**: the first the song has. The store a link points to, when the song is on it.
- **Listen for**: 10 s, between 6 and 12. Longer clips match more reliably and cost the same.
- **Check what the phone plays first**: on.
- **Keep a history**: on. **Recognised songs** opens the list.
Music recognition is the **Music recognition** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool and its history leave the keyboard and the app until the bundle is switched back on.
## Details & edge cases
- **Four seconds is the least it asks about.** Stopping earlier shows a notice and listens on.
- **A quiet room is reported as nothing heard**, not sent to a service: a clip below the keyboard's own silence floor would cost a request to be told "no match".
- **Voice typing and Music ID share the microphone.** While a dictation runs, Listen waits and says so.
- **The Quick Settings microphone tile** blocks the microphone for every app. The panel notices and says so rather than listening to silence.
- **No hardware-keyboard letter by default.** Bind one under [shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/), or reach it through the toolbox shortcut, double-tap Ctrl then T.
- **It isn't available before your first unlock.** The history lives in the keyboard's files, which are locked until then.
- **Humming is not recognised.** Every service here matches the recording itself; a cover version or a live take usually goes unmatched.
- **Nothing runs on the phone.** There is no built-in catalogue and no fingerprinting of your own files: a song you have downloaded is one you already know.
---
# Notes
URL: https://wmkeyboard.pages.dev/tools/notes/
Section: Tools
Summary: A notebook inside the keyboard: jot while you type, tag and pin, tick off checklists, lock the private ones, and keep every note in your backup.
The **Notes** tool is a notebook that lives in the keyboard. Ideas, a diary, a shopping list, the thing someone just said in a call: write it down without leaving the app you are in, and find it again from any other app. Notes are plain text that the keyboard draws as Markdown, so a heading, a list or a `- [ ]` task box looks like one, and a task box can be ticked with a tap. Tags are buttons along the top of the list, so a tap on **Work** shows only the notes you filed under it.
The same notes are in the settings app, where a full-screen editor with a real text field is the better place to write anything longer than a few lines. Both sides write the same file, so a note made on the keyboard is in the app when you come back to it, and the other way round.
## Opening it
Notes is also a bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/), as **Notes**. Off there, the tool, the quick note action and the notebook screens leave the keyboard and the app together; the notes stay on the phone.
Tap **Notes** on the toolbar, or in the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). Tap it again to close it. Typing `notes` into a text field offers the tool as a chip on the suggestion strip, like every other tool keyword.
In the settings app the notebook is one tap from the tool's own page:
Tools / Notes / Your notes
**Add to home screen**, under it, puts an icon for the notebook on your home screen, which opens it straight away without going through the settings.
The panel opens on the list by default. Tools / Notes / The keyboard panel opens on can make it open on the note you edited last instead, which suits a running diary or a list you add to all day.
### Quick note
The **Quick note** action opens the panel straight on a new note, with the keys already typing into its body, so a thought goes down in one press. Bind it to a flick, a key's long press, a toolbar button's hold or the floating button. It's an action rather than a tool, so it never shows on the toolbar or in the toolbox: you'll find it under **Other** in those pickers. Fire it again while the note is up to close the panel.
Under the editor's Markdown chips, a quick note adds a row of your latest notes, the one you touched last first, which you can scroll sideways. Tap one to switch to it with the caret at its end, ready to add another line to a list you keep going, like the bugs you run into in an app. **New note** at the start of the row goes back to a fresh one. The note you leave is saved as it is; a new one you never typed into is deleted rather than left behind as an empty note, and the same happens when you close the panel or the keyboard. Locked notes, and notes too long for the keyboard's editor, stay out of the row.
## The shelf and the tags
The list is a shelf. Pinned notes sit on their own row at the top, as wide cards you scroll sideways, each washed with the colour of its first tag and striped along its edge; a note with no tag takes the theme's accent. The rest fill a two-column grid under them. A card shows the note's title, a line or two of its text, when it was last edited, and a dot for each tag; a checklist shows a small ring of how much is ticked, and a locked note a padlock. Cards arrive one after another as the panel opens, and squash a little under your finger.
Along the top, next to Back, the filter chips scroll: **All**, **Pinned**, one chip per tag, **Archived** and **Trash**, then **Manage tags**. The tag chips are your tags, in your colours; there are none until you make one. The round search button at the end turns the header into a search pill, which searches titles, tags and text as you type into it, the way the clipboard's search works; the keys type into the search rather than into the app behind the keyboard, and the chips move down under the pill so a filter stays a tap away. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, notes that are about what you typed follow the matches, from the same chip; locked notes are left out of it.
Once the search holds two letters or more, the paragraphs that contain them come first, under **Passages**, above the notes. Each shows its paragraph with the match picked out and, under it, the note it comes from. Tap one to type that paragraph into the field as the note has it, without opening the note; hold it to open the note instead. A long paragraph is cut down to the line around the match, and a long line to the sentences around it. One note gives three passages at most, a search lists 24, and locked notes give none.
Tap a card to open it. Hold a card for its actions right there on the card: **Insert**, **Copy**, **Edit**, **Share**, **Pin**, **Archive**, **Lock** and **Delete**. Swipe a card to the left instead for **Pin**, **Archive** and **Delete** behind it; a long swipe past about half the card moves it to the trash at once, with the Undo bar to bring it back. In the trash, the swipe shows **Restore**. Holding a tag chip opens the tag manager in the settings app.
A **Jot something…** pill floats along the bottom of the shelf. Tap it, or its **+**, to start a new note; it steps aside while the Undo bar is up. On an empty tag, the empty list offers **New note tagged** with that tag already on it.
### The timeline
Tools / Notes / Keyboard panel layout swaps the shelf for a timeline: a rail of days down the left, **Today** and **Yesterday** first and dates after them, with one compact row per note beside it, newest first. A row leads with the checklist's ring, a padlock, or a text glyph in the tag's colour, then the title with a pin when the note is pinned, a line of its text with the checklist's count in front, its tag pills, and the time for today's notes or the weekday for the rest of the week. The chips, the search, the swipes and the holds work the same, and a round **+** button in the corner starts a new note. The shelf is the default.
## Writing a note
A new note opens its editor with the title line focused: type a title, press Enter, and you are in the body. A title is optional. A note with none is named after its first line, with the Markdown taken off, so `# Shopping` on the first line is a note called Shopping.
The editor collapses the panel so the key rows come back underneath it, the way the clipboard's clip editor does. Enter is a line break. Inside a list it carries the list on: `- ` after a bullet, `3. ` after `2. `, a fresh `- [ ] ` after a task box, and Enter on an empty item ends the list. A row of chips above the keys inserts the Markdown you reach for most: bold, italic, a bullet or a numbered list, a task box, a heading, a quote and code. Each one wraps whatever is selected, or toggles its prefix on the lines you selected. The row's end counts your words and characters.
Everything you type is saved as you go. There is no Save button and nothing to lose when the keyboard closes or another app takes the field: the draft is written first. **Done** keeps the note and goes back to reading it; **Cancel** puts the note back the way it was when you started, and throws away a new note altogether. A new note left blank is not kept.
Tags sit above the body. Tap one to take it off the note, or **+ Tag** for the picker: your other tags first, then a few starters (Important, Work, School, Study, Personal, Ideas) until you have tags of your own, and **New…** to type a name. A tag is made the first time it goes on a note.
## Reading a note
An open note is its card grown to fill the panel: its tag pills along the top with when it was edited at the right, the title large, and the note under it. Tapping a tag pill filters the list by that tag. Along the bottom sit **Insert**, **Edit**, **Copy** and **Share**; in the header, **Pin**, **Lock**, the arrows that make the note taller, and **More**, which unfolds a row with **Pin**, **Archive**, **Lock** and **Delete**. Back shrinks the card into the shelf again.
The reading view draws the note as Markdown: headings (including the `===` and `---` underline kind), lists, nested quotes, tables, footnotes, strikethrough, links, code blocks with their keywords, strings and comments coloured, LaTeX maths between `$` or `$$` signs, and task boxes you can tick and untick. A link opens in your browser; a bare address like `https://example.com` is a link too. An image shows its description until you tap it, and only then is the picture downloaded, so reading a note never contacts a server on its own. Ticking a box rewrites that one line of the note, so the text behind the view always says what the view shows. Tap anywhere in the text to edit from that line. Hold a word to select it, as in the [Wikipedia](https://wmkeyboard.pages.dev/tools/search/) reader: drag the handles to take more, then **Copy**, **Insert**, **Select all**, or **Ask AI** about the passage when the AI tool is on. The settings app's Preview selects text the same way, with the phone's own Copy bar. The bar under the note holds the same actions the list offers: **Insert** types the note into the field behind the keyboard, as it was written; **Copy** puts it on the clipboard; **Share** hands it to another app through the [share sheet](https://wmkeyboard.pages.dev/reference/settings/share-sheet/), whose **Insert** types it into the field too. The arrows in the header make the note taller, up to most of the screen, for reading something long. A locked note, or one in the trash, keeps a row of chips instead, with only what it may do.
A long note shows its first part and a **Show more** chip. A very long one, past twenty thousand characters, still reads here but edits in the settings app, where the Edit chip takes you.
Tools / Notes / Format Markdown turns the formatting off, for anyone who would rather see exactly what they typed.
## Pin, archive, trash
**Pin** keeps a note at the top of every list, whatever the sort order. **Archive** takes a note out of the main list without deleting it: it waits under the **Archived** chip and still turns up in a search of that chip. **Delete** moves a note to the **Trash**, and an Undo bar gives you a few seconds to change your mind; the trash itself is the longer undo, with **Restore** on every note in it. Notes in the trash are deleted for good after thirty days, or after seven or ninety, or never, under:
Tools / Notes / Keep trashed notes for
**Delete forever** and **Empty trash** ask for a second tap. In the settings app the same actions are on each note's menu, and the list there has a select mode: hold a note, tick the others, and pin, archive, tag, lock or trash them together. Swiping a note to the left shows **Pin**, **Archive** and **Delete** behind it for that one note, and a long swipe past about half the row moves it to the trash straight away, with the same Undo bar. In the trash, the same swipe shows **Restore** and **Delete forever**.
The sort order, last edited by default, is Tools / Notes / Sort notes by.
## Locked notes
A note can be locked, from its actions on the keyboard or from the menu in the app. A locked note shows its title and nothing else in every list, and the keyboard never shows, inserts, copies or shares its text. In the settings app it opens only after the same check the [app lock](https://wmkeyboard.pages.dev/reference/settings/app-lock/) uses: your fingerprint or screen lock, or the PIN, password or pattern you set there, and taking the lock off asks too, whether from the note's menu or for several notes at once in select mode. Opening a locked note asks once; if the whole Notes screen is already behind the app lock, it does not ask again.
This is a lock on the screen, not on the data. The note is stored like any other note, so a backup file, Android's own backup, or a computer with USB debugging still reach it. To keep a locked note out of the backup file, see [Backups](#backups) below. On a phone with no fingerprint and no screen lock set up, there is nothing to ask for, and locked notes open with a caption that says so.
## Save a selection as a note
With [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) on, selecting text in any app offers **Save as note** on the bar. It saves the selection as a new note and says so with a toast, leaving the field as it was. Tools / Notes / Save as note from a selection can make it add the selection to the end of the note you edited last instead, which turns a day of selections into one running note.
Nothing is saved from a password field, and nothing can be saved before the phone's first unlock after a restart, since the notes file is behind the lock screen until then.
## Save from another app's share sheet
Tools / Notes / Save to notes from other apps
Share text from any app and pick **Save to notes**: it becomes a new note, with the share's subject, when there is one, as the title. This works with the keyboard closed, and with no selection actions needed.
Share a link instead, a page from the browser's Share menu for instance, and it becomes a read-later note. The keyboard reads the page once, keeps its title and its text with the layout taken off, puts the link at the top, and tags the note **Read later**, so the tag's filter gathers everything you saved for later. The [Read back](https://wmkeyboard.pages.dev/tools/read-back/) tool can then read it to you. There is no JavaScript, so a page that draws itself with scripts comes back short; a page that cannot be read at all, or the build with no internet, saves the link alone, and the toast says which. The page request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Notes: a shared page**. Data saver leaves it alone, since it is one page you asked for.
The switch above takes **Save to notes** out of the share sheet. It is on by default.
## Export, share and import
In the settings app, a note's menu shares it as a Markdown or a plain text file, named after its title. **Export all as a zip** under the list writes one Markdown file per note, archived notes included and locked notes left out, with the note's tags on a `Tags:` line at the end. **Import text files** reads `.txt` and `.md` files back as notes, one per file, skipping any that already exist word for word, and reads a `Tags:` last line back as tags. A note holds up to 100,000 characters, and a longer file is cut there.
Importing is also the way to keep texts you quote often within reach: a speech, a book's chapter, a set of answers you give again and again. Give them a tag such as **Quotes**, and the search's [passages](#the-shelf-and-the-tags) type a quote from them straight into a chat. Pick the tag's chip while you search to look in those notes only. The [clipboard's search](https://wmkeyboard.pages.dev/tools/clipboard/#searching-your-notes-too) lists the same passages above your clips, so you can reach a quote without leaving the clipboard.
**Export to an Obsidian vault** asks for the vault's folder and writes each note to a `WM Keyboard` folder inside it, with its tags and dates in front matter so Obsidian sees them, and any space in a tag turned into a dash. It leaves out locked notes the same way. Export again to update the files: a note whose file is there overwrites it, so edits made to that file in Obsidian are lost, and a note you renamed gets a new file beside the old one. It runs when you tap it, not in the background. Import reads those files back, tags and dates included, and leaves front matter with any other key in the note's text.
## Backups
Notes are a section of the [backup file](https://wmkeyboard.pages.dev/privacy/backup/), on by default for the automatic backup and for sync between phones. Every note, its tags and the trash travel in it, except locked notes, which stay out unless **Include API keys** is on: a backup file is easy to send yourself or drop in a shared folder, and a locked note is one you asked to keep from view. Restoring a backup that left locked notes out keeps the ones already on the phone.
## Privacy
Notes stay on the phone. Nothing is sent anywhere unless you tap an image in a note to load it, share a link to **Save to notes** (which fetches that page), share a note, export the zip or to a vault, or back the file up to a location you chose. The keyboard never learns from a note: what you type into it does not reach your personal dictionary, your word pairs or your typing statistics. The tool is not available before the first unlock after a restart, when the keyboard has no access to its files.
---
# Offline voice (Omnilingual)
URL: https://wmkeyboard.pages.dev/tools/omnilingual/
Section: Tools
Summary: One offline model for about 380 keyboard languages, many with no other voice typing: Meta's Omnilingual ASR on your phone.
Offline Omnilingual runs [Omnilingual ASR](https://github.com/facebookresearch/omnilingual-asr), Meta's speech recognition model for more than 1,600 languages, on your phone with ONNX Runtime, the engine [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) runs on. One model covers every language it knows, and it works out which one you speak by itself, while the keyboard keeps what it writes to your language's script. About 380 of the keyboard's languages are among them: Hindi, Tamil, Swahili, Yoruba and Persian, but also hundreds that no other engine here can take, such as Nuer, Nande or Tarifit. Once the model is downloaded, no audio leaves the phone.
## Setting it up
Voice typing
1. Add **Omnilingual** to the **Recognition engine** list and move it to the top, or pick it for one language under **Engine for each language**. Picking it per language is the better fit when another engine already serves your main language well.
2. Press **Omnilingual** on the list to open its screen, which holds the **Omnilingual model** group. The first time, a banner offers ONNX Runtime, the engine it runs on, shared with Sherpa: a one-time download of about 33 MB (arm64), from Google Play on the Play build and from Maven Central on the GitHub and F-Droid builds; the offline build carries it already. The F-Droid build asks you to turn on **Download feature libraries** first.
3. Download **Omnilingual ASR 300M**: 367 MB from Hugging Face at a fixed revision, every file checked against its published SHA-256. On mobile data it asks first.
| Model | Languages | Download | Licence |
|---|---|---|---|
| Omnilingual ASR 300M | about 380 keyboard languages (1,600 in all) | 367 MB | Apache-2.0 |
The download includes Silero VAD (2 MB), the voice detector that cuts your speech into phrases.
## How it works
Omnilingual is not a streaming model: it reads a whole phrase at once. While you talk, Silero listens for the short pause at the end of each phrase (half a second) and hands that phrase to the model while you go on to the next one. **Phrase end detector** on the Voice typing screen can put FireRedVAD, plain loudness or a model of your own in Silero's place; see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase). The words therefore arrive a phrase at a time, as underlined composing text in the field and on the line under the microphone, and not word by word as they do with Sherpa. When the dictation ends, the text goes through the same spacing, spoken punctuation, casing and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)).
**When dictation ends.** With **Stop after a pause** set, a longer pause of that length ends the dictation; with **Keep listening** on, the next one starts at once. With the setting off it runs until you press the microphone or two minutes pass. A phrase that runs past 20 seconds without a pause is cut at the next short breath, because the model reads at most 40 seconds at a time.
**Plain words.** The model writes words without punctuation or capitals. With [Add punctuation and capitals](https://wmkeyboard.pages.dev/tools/voice/#adding-punctuation-and-capitals) on, the default, a punctuation model for 88 languages puts in full stops, commas, question marks and sentence capitals once it is downloaded from that setting's row on the Voice typing screen. Without it the keyboard capitalises the start of a dictation, and spoken punctuation ("comma", "full stop") works as usual.
**Memory and speed.** The model takes about 1 GB of memory while it is open, so it suits a recent phone. The keyboard frees it when Android asks for memory back, and opening it closes Sherpa's model first. Measured on a Mac, it needs about 2.1 billion processor cycles per second of speech on one core; with four threads that comes to an estimated 0.3 seconds of waiting per second of speech on a flagship phone and about 0.5 on a mid-range one.
## Omnilingual, Whisper or the others
Use Omnilingual for a language that has no other offline engine here. Where one exists, it is usually the better choice: [Whisper](https://wmkeyboard.pages.dev/tools/whisper/) is more accurate on the languages it was trained hardest on, [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) streams its 13 languages word by word, and [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) covers 25 European ones. On 12 FLEURS test clips per language, Omnilingual's character error rate was 14.3% for Hindi, 19.4% for Tamil, 5.3% for Swahili and 21.0% for Yoruba.
Meta's larger MMS-1B model was measured too and was more accurate (6.4% for Hindi), but at 1.9 GB of memory and about two and a half times the work per second of speech it is too heavy for a keyboard, so it is not offered.
## Details & edge cases
**No language to choose.** The model has no way to be told the language, so it guesses from the sound. A dictation that listens for several languages at once works the same way, as long as Omnilingual knows all of them.
**Kept to your script.** Left to itself the model spells by sound in whichever script it thinks it hears, so Hindi often comes out in Urdu letters and an English phrase now and then in Arabic ones. The keyboard therefore lets it write only the letters of the script of the language you dictate in, plus digits, spaces and punctuation. Where the model reaches for another script's letter, the letter of your script that it rates best for that sound goes in instead: Urdu ب becomes ब, Arabic ل becomes l. A dictation in several languages may use the scripts of all of them, so Hindi with English keeps both Devanagari and Latin. A phrase the model heard as the neighbouring language can still be spelled the way that language writes it. Urdu leaves out most short vowels, so a Hindi word may now and then miss a vowel sign, and English heard as Arabic loses some vowels the same way. For a language whose script the keyboard has no Unicode name for, or a notation such as music or Braille, the model writes as it likes.
**Scripts.** It writes each language in the script it was trained on. Languages it only writes in a different script than the keyboard's are left out of its list, so they go to another engine: Crimean Tatar (Cyrillic, not Latin), Manipuri (Bengali, not Meetei Mayek), Tulu (Malayalam, not Kannada) and a few others.
**No words to listen for.** The model takes no list of names or terms, so **Words to listen for** does not show while it is the engine.
**The licences.** Omnilingual ASR is Meta's under Apache-2.0, converted for sherpa-onnx by k2-fsa. sherpa-onnx is Apache-2.0, ONNX Runtime is Microsoft's under MIT, and Silero VAD is MIT. All of them are credited in the app's About screen.
---
# Offline voice (Parakeet)
URL: https://wmkeyboard.pages.dev/tools/parakeet/
Section: Tools
Summary: Dictation in 25 European languages on your phone, with NVIDIA's Parakeet TDT model telling them apart.
Offline Parakeet is a dictation engine that runs entirely on your phone, with NVIDIA's [Parakeet TDT 0.6B v3](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3), one of the most accurate open speech recognition models of its size. It transcribes 25 European languages and works out for itself which one you're speaking, so a layout in any of them can dictate without choosing a model per language. Once the model is downloaded, no audio leaves the phone.
The languages: Bulgarian, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hungarian, Italian, Latvian, Lithuanian, Maltese, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish and Ukrainian. While a layout in another language is in use, dictation uses the system recognizer instead, the same way [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/) hands over its other languages.
## Setting it up
Voice typing
Add **Parakeet** to the **Recognition engine** list and move it to the top, then press its row to open its screen and download a model from the **Parakeet model** group there. Each is large: 719 MB, the 4-bit build made for phones, and suits a recent phone with plenty of memory. On mobile data the download asks first. It comes from Hugging Face at a fixed revision, and both of its files are checked against their published SHA-256 before they count.
There are two models, the same size and speed:
- **Parakeet Ultra**, [Moondream's](https://huggingface.co/moondream/parakeet-ultra) further-trained copy of v3, with the same 25 languages. On Moondream's own tests it gets about a sixth fewer words wrong across the 25 languages (FLEURS: 9.55% word error rate against v3's 11.62%), and does better in background noise. The keyboard downloads its own [ExecuTorch export](https://huggingface.co/wasi-master/parakeet-ultra-executorch) of it.
- **Parakeet TDT 0.6B v3**, NVIDIA's original.
With both downloaded, dictation uses Ultra, and v3 can be deleted to free its space. [Engine for each language](https://wmkeyboard.pages.dev/tools/voice/#an-engine-for-each-language) can still give a language v3.
## How it works
Parakeet doesn't write its answer a word at a time: an encoder reads a whole clip, and a small transducer then walks through it, writing a word piece where it hears one and skipping ahead where the model's own duration head says nothing more is coming. That is most of why it's quick for its size. A phrase can run to 50 seconds. The first phrase after the keyboard starts takes a few seconds longer while the model's weights are prepared.
The words show while you speak, as they do with [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) and [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/): under the microphone, and in the field as underlined text in the default voice typing mode. Parakeet isn't a streaming model, so the keyboard gets them by transcribing the phrase as it stands, again and again while it grows. A new pass starts once there is more than half a second of new audio, and never sooner than three times as long as the last pass took, so a slower phone shows the words less often rather than falling behind. The words can still change as the rest of the sentence arrives. When the phrase ends, one last pass over all of it gives the text that goes in, the same text Parakeet gave before it showed words as you spoke. Each pass covers the whole phrase, so the phone works harder during a long one; a pause that ends the phrase (see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase)) keeps phrases short.
The text arrives punctuated and capitalised by the model itself, and then goes through the same spacing, spoken punctuation and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)).
## Ending a phrase
Voice typing / Stop after a pause
A phrase ends when you stop talking for as long as **Stop after a pause** says, heard by the **Phrase end detector** you picked (see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase)). Both are also in the **End of a phrase** group on Parakeet's own page, under its models. **Stop after a pause** is off by default; while it is off, a phrase only ends when you press the microphone or it reaches 50 seconds. Set it to a second or so and each phrase goes in at the pause, and with **Keep listening** on the next one starts by itself.
Parakeet has no detector of its own, so **Auto** gives it Silero once ONNX Runtime and a Silero copy are on the phone, and loudness until then. Silero is the better choice in a noisy room, where loudness can keep a phrase open.
Moondream's Parakeet Ultra carries a small voice activity head of its own, which their Photon runtime uses to cut long recordings at pauses. The keyboard's ExecuTorch export of Ultra was made with the four parts the Parakeet runner uses and left that head out, so it is not used yet; the phrase end detector does that job. Using it would take a new export of Ultra with the head as a fifth part, and a new download.
## Details & edge cases
**Several languages at once.** A dictation that listens for several languages (see [Language and engine](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine)) is Parakeet's when every one of them is among its 25; it tells them apart on its own.
**No words to listen for.** Parakeet has no way to take a list of names or terms, so **Words to listen for** does not show while it is the engine.
**Every build.** The runtime, [ExecuTorch](https://github.com/pytorch/executorch) built for the keyboard's models alone, is in every build; F-Droid compiles it from source. A build made without it does not offer the engine.
**The licence.** Both models are under CC-BY-4.0: v3 is NVIDIA's, and Ultra is Moondream's, built on it. Both are credited in the app's About screen.
---
# Providers
URL: https://wmkeyboard.pages.dev/tools/providers/
Section: Tools
Summary: How tools that can ask more than one service keep a list of providers you add, order, switch off and set up, with fallback between them.
Some tools can get the same thing from more than one service. GIFs can come from KLIPY, GIPHY, Wikimedia Commons, GifCities or packs on your phone. Each of these tools keeps a **Providers** list near the top of its settings page, and every list works the same way, much like the locations of [backups](https://wmkeyboard.pages.dev/privacy/backup/).
## The list
The providers that are **on** come first, in the order the tool asks them. Each row shows its place in the order, its logo, its name, one line saying what it is set up with, a chevron, and its switch behind a thin rule.
The line comes from the provider's own settings, so it changes as soon as you change them on the provider's screen:
| Provider | The line says, for example |
|---|---|
| A voice engine or the on-device AI | *Model:* and the model it uses, then its size. The engines that pick their own model name the biggest one on the phone and count the rest. While a model downloads, how far it is; with no model, *No model downloaded*. |
| The phone's speech recognizer | *Model on the phone first* or *Always the recognizer service* |
| An on-device translator | How many languages are on the phone, English not counted |
| Your own server, or a front end | *Server:* and its address, then its engine or model |
| An AI service | Its model, or *Default model*, and its key |
| A service with a key | *Key set*, *Key ending* and the last four characters, *Key in this build*, *No key*, or *No key needed* |
| DeepL, Tavily | Their key, then *DeepL Write on*, or Tavily's search depth when it isn't Basic |
| Harper | The kind of English it checks |
| Offline GIF packs, vocabulary packs, imported or offline dictionaries | How many are on the phone, or whether the dictionary is downloaded |
- **Drag the number** at the start of a row (the grip under it) to change the order. Only the providers that are on move, since the tool asks no other. With TalkBack, the row's actions include **Move up** and **Move down**.
- **Press a row** to open that provider's own screen. Everything the provider has is there, see below.
- **Switch a row off** to skip it without losing its setup. It moves down to the providers that are off.
- **Add a provider**, under the list, opens a sheet of every kind this build can use, in the same order on every build. A kind that needs nothing typed in is added at once. The rest open the setup first.
The providers that are **off** sit under the list, each folded to its name, a short description and a switch. When they run in more than one kind of place they are sorted under **On the phone**, **Online services** and **Your own servers**. Press one to set it up before you switch it on.
**List settings**, a fold at the end, holds how the tool uses its list (see below), **Two of the same provider** when a kind is on the list twice, and **Reset to default** once you have changed the list.
A row with a problem says what it needs in red: an API key, a server address, a model name, an imported GIF pack, or a model to download. Such a row is skipped until it is fixed.
## A provider's own screen
Pressing a row opens a screen for that one provider, with its description and tags at the top:
- **Use this provider**: the same switch as on the row.
- **Setup**: its key, its server address, a model, an engine, a nickname, or a sign-in. The line under it says what is set now, and a press opens the setup sheet with its **Test** button. A provider with nothing to set up has no such row.
- The provider's **own settings**, where it has any: a voice engine's models, the on-device AI model, the translation models, the offline GIF packs, the offline dictionaries, DeepL Write, Tavily's search depth. On the F-Droid build the provider's server address is here too.
- **Usage**, for the services that charge: Brave Search, Tavily and the AI services. See [Usage and credit](#usage-and-credit).
- **Shared with**: the other tools that list the same provider and use its setup.
- **Remove** takes the provider out of this tool's list. If no other tool uses its key or server, the key and address are deleted too, and the dialog says so first.
A problem shows as a banner at the top with a **Set up** button. A search result or a link can open a provider's screen before it is on the list; the banner then offers to **Add** it.
## Usage and credit
The screen of Brave Search, Tavily, or any AI service except the model on your phone has a **Usage** group. It keeps two kinds of number apart, because only one of them comes from the service:
- **This month on this phone** is what the keyboard counted itself: the requests it sent, Tavily's credits (one per search, two at **Advanced** depth), and for an AI service the tokens it read and wrote, as the service reported them for each answer. Anthropic, Gemini, Ollama, OpenAI, xAI and DeepSeek report tokens; another OpenAI-compatible service may not. The count starts again on the first of each month. **Start the count again** clears it sooner and doesn't touch your account.
- **What the service reports** is the service's own number, and when the keyboard last saw it:
- **Brave Search** sends the month's remaining searches with every answer, so this keeps itself up to date. With the key built into the app, it's the shared key's quota, not yours, and the row says so.
- **Tavily**, **DeepSeek** and **OpenRouter** (as an OpenAI-compatible service at `openrouter.ai`) answer **Check what is left**: Tavily with your plan's credits left, DeepSeek and OpenRouter with your balance. The keyboard asks only when you press it.
- **Anthropic, OpenAI, xAI and Groq** report a per-minute token allowance with each answer, shown as such. None of them tells an app your account's balance.
- The others report nothing, and the phone's own count is all there is.
The web search panel shows what's left beside the service's name, as in "via Tavily · 812 left", once Tavily or your own Brave key has reported it. To keep one request from running long, set **Max response length** in the [AI tool's options](https://wmkeyboard.pages.dev/tools/ai/#options); **Stop** on the panel or in the chat ends an answer and its billing at once.
## Finding a provider in search
Every provider is a result in the settings search under its own name: search for *DeepL*, *Whisper* or *Ollama* and the result opens that provider's screen, with the path of the list it is on (*Tools › Translate*) under it. A provider that can join several lists, such as Wiktionary, has a result for each. A second server or key, and any provider you gave a nickname, is a result of its own, found by that nickname and showing its server or key under it. Providers this phone cannot run are left out, unless one of your lists has them.
Rows show the service's own logo where the app carries one. The logos are small vector drawings from [Simple Icons](https://simpleicons.org/) or from each project's own repository, and are in the app itself, so drawing them never reaches the network. A one-colour logo is lightened on a dark theme when the brand's colour is close to black, and a logo whose colours would vanish on one theme (KLIPY, Lingva, Tavily) sits on a small plate of its own.
## Shared keys and servers
A provider is set up once, and every tool that lists it uses the same setup. A KLIPY key added on the GIF page also works for stickers. The provider's screen says which other tools use it, under **Shared with**. Edit it on either page and both change.
Server kinds can be added more than once, each with its own address and nickname: two Wikimedia wikis, say. When you add one on another tool's page, the add sheet offers the servers you already set up, under **Use**, so you don't type the address twice.
## Two of the same provider
A service you set up with a key can also be added a second time, with another key or another account: two KLIPY keys, two DeepL accounts, a Brave key of your own beside the one in the build. Tap the kind again in **Add a provider**. The second one always needs its own key, since the key in the build already belongs to the first. Give each a **Nickname** so the list and the keyboard can tell them apart; a row without one shows the last four characters of its key.
This works on the GIF, sticker, translate, web and image search, online text scanning, photo translation, AI and sticker generator lists. Background photos, currency rates and the word sources ask a service by its name, so they keep one of each.
When a list asks **One at a time**, the second one is simply the next provider down. On the GIF and sticker lists in **Tabs** or **Mixed**, and on any list set to **Choose in the panel**, a setting called **Two of the same provider** appears in **List settings** as soon as one kind is on the list twice:
- **As backups** (the default for a second key): they act as one provider, with one chip and one entry in the panel. The first is asked, and the next only when it fails, for example when a key runs out of quota.
- **Separately**: each one is its own provider, with its own chip, its own entry in the panel, and its own share of a mixed grid. Two servers of one kind, like two Wikimedia wikis, start out this way.
## How a tool uses its list
Most tools ask **One at a time**: the first provider that is on, and the next one only if the first fails or has nothing. The GIF and sticker panels can also show their providers as **Tabs** (a chip for each) or **Mixed** (all of them at once, in one grid). The smart home list is always mixed: every home on it is connected, and the order is the order their rooms appear in. Packs on your phone always get a chip of their own, and GIPHY keeps its own chip in Mixed mode, because its terms do not allow mixing its results with another service's.
The AI list is always **Choose in the panel**: one service answers at a time, the one picked under **In use** on the AI page or with the AI panel's model picker, and that pick is kept. The voice typing list is always one at a time: dictation uses the first engine that is on and can run, and a language can be given another engine of its own.
Translate, the dictionary, web and image search, online text scanning and photo translation can instead **Choose in the panel**. The panel then shows a chip with the provider in use, and tapping it lists the providers that are on. Only the one you pick is asked, with no fallback to the next. Picking another one runs the same request again with it. The choice lasts until the keyboard restarts, and then the first provider in the list is the one in use again. With only one provider on, there is nothing to choose and the chip stays hidden. The AI's own tools always use the list in order, whatever the mode.
A provider that can't be reached (no answer, a server error, too many requests) is skipped for a minute, so the next one answers at once instead of after a timeout. If every provider is in that state, they are all asked anyway. Editing a provider clears this, so a fixed key is tried straight away.
## What each build offers
Every build offers every kind it can run, your own servers included, in the same order and with none put first. The tags under each kind (free software, on the phone, your own server, needs your key) say what it is. What changes between builds is the list a fresh install starts with.
| Build | Starts with |
|---|---|
| Google Play and GitHub | The services this build has keys for |
| F-Droid | Services that need no proprietary key |
| No internet | Only what runs on the phone; nothing else can be added |
Updating the app does not change your lists. Until you edit a list, it is worked out from your earlier settings, so the same providers are asked in the same order with the same keys. When an update adds a word source that needs no setup (a new dictionary, say), it joins the end of the dictionary, synonym or vocabulary list, switched on. One you removed stays removed.
## Tools with a provider list
| Tool | Kinds | Settings |
|---|---|---|
| [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/) | KLIPY, GIPHY, Wikimedia Commons or another MediaWiki, GifCities, offline GIF packs | Tools / GIFs / Providers |
| [Stickers](https://wmkeyboard.pages.dev/emoji/stickers/) | KLIPY, GIPHY, your sticker packs | Tools / Stickers / Providers |
| [Translate](https://wmkeyboard.pages.dev/tools/translate/#providers) | On device (ML Kit not on F-Droid), Google Translate, DeepL, LibreTranslate, Mozhi, SimplyTranslate, Lingva, Translite, your own translation server | Tools / Translate / Providers |
| [Dictionary](https://wmkeyboard.pages.dev/tools/search/#dictionary-sources) | Vocabulary packs, Wiktionary, Wiktionary definitions, Free Dictionary API, the offline dictionaries, imported dictionaries, Urban Dictionary | Tools / Dictionary / Dictionary sources |
| [Synonyms](https://wmkeyboard.pages.dev/smart/personal-dictionary/#from-the-suggestion-strip) | Vocabulary packs, Datamuse, Wiktionary, Free Dictionary API, Similar words | Typing / Suggestions / Synonym sources |
| [Web and image search](https://wmkeyboard.pages.dev/tools/search/) | SearXNG, Tavily, Brave Search | Tools / Web search / Providers |
| [Background photos](https://wmkeyboard.pages.dev/themes/online-photos/) | Unsplash, Pexels, Wikimedia Commons or another MediaWiki | Themes / Photo services |
| [Currency converter](https://wmkeyboard.pages.dev/tools/utilities/) | ExchangeRate-API, Frankfurter, AllRatesToday, Coinbase, currency-api; for coins Coinbase, currency-api, CoinGecko | Tools / Currency converter / Currency rates from |
| [Text scan, online](https://wmkeyboard.pages.dev/tools/scanner/#reading-text-online) | Your vision model, Rens | Tools / Text scan (OCR) / Online text reader |
| [Camera, Translate](https://wmkeyboard.pages.dev/tools/camera/#translating-a-photo) | Rens | Tools / Camera / Translate photos |
| [Vocabulary](https://wmkeyboard.pages.dev/tools/vocabulary/) | Wiktionary, Wiktionary definitions, Free Dictionary API, for words no pack knows | Tools / Vocabulary / Providers |
| [AI](https://wmkeyboard.pages.dev/tools/ai/#choosing-where-it-runs) | Anthropic (Claude), OpenAI, Google Gemini, xAI (Grok), DeepSeek, Brave Answers, Ollama, LM Studio, an OpenAI-compatible service, On your device (not on F-Droid) | Tools / AI writing tools / AI providers |
| [Sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/#engines) | On this phone, Pollinations, AI Horde, OpenAI Images, Gemini Images, Grok Imagine, Stability AI, Black Forest Labs (FLUX), Recraft, Replicate, fal.ai, Together AI, Hugging Face, Cloudflare Workers AI, Automatic1111 / Forge / SD.Next, ComfyUI, an OpenAI-compatible image service; Pollinations and AI Horde start switched off | Tools / Stickers / Sticker packs / Create stickers / Engines |
| [Internet speed test](https://wmkeyboard.pages.dev/tools/speed-test/#servers) | Cloudflare, LibreSpeed servers (your own, or picked from the public list) | Tools / Internet speed test / Servers |
| [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/#checkers-harper-and-languagetool) | Harper (on the phone, English), LanguageTool (the public server, a Premium account, or your own server); LanguageTool starts switched off | Tools / Grammar check / Checkers |
| [Voice typing](https://wmkeyboard.pages.dev/tools/voice/#options) | Phone's recognizer, Whistle, Whisper, Asr4All, Parakeet, Moonshine, Sherpa, Omnilingual, Vosk, transcription servers | Voice typing / Recognition engine |
| [Smart home](https://wmkeyboard.pages.dev/tools/home-assistant/) | Home Assistant, Google Home, SmartThings, a Philips Hue bridge, openHAB, Smart Life (Tuya), SwitchBot; every home on the list is connected at once, and its sign-in or pairing lives in the row's setup | Tools / Smart home / Homes |
| [Music ID](https://wmkeyboard.pages.dev/tools/music-id/#recognisers) | AudD, ACRCloud, AudioTag, Shazam via an unofficial RapidAPI listing, your own recognition server; every kind starts switched off until it has your key | Tools / Music ID / Song recognisers |
---
# Read back and offline voices
URL: https://wmkeyboard.pages.dev/tools/read-back/
Section: Tools
Summary: Hear what you typed, read aloud in the keyboard's language, with Kokoro's natural voices for English and Mandarin and Meta's MMS voices in more than a thousand languages, offline.
**Read back** reads your text aloud. With something selected it reads the selection; with nothing selected it reads everything in the field, up to about 4,000 characters. It reads in the keyboard's current language, so switching to Bangla and tapping it reads Bangla. Tap it again while it reads to stop. The tool stays lit while it is reading. It never reads a password field.
Add it from the toolbox like any other tool. It sits in the **Create and convert** group on the Tools screen, and its page there is Tools / Read back.
## Kokoro voices
Tools / Read back / Kokoro offers [Kokoro](https://huggingface.co/hexgrad/Kokoro-82M), a small open speech model (Apache-2.0) that reads English and Mandarin in voices close to a person's. It is one download of 132 MB, about 160 MB once unpacked, that holds every voice: 20 American English, 8 British English and 8 Mandarin. With it downloaded, it reads those languages ahead of the MMS voices below and the phone's own voice; other languages carry on as before.
Once it is here, the group shows a row of voices for each language. Tap a name to pick it; each is marked female or male. British English reads keyboards set to the UK, Ireland, Australia, New Zealand, South Africa or India, and American English the rest.
The download is sherpa-onnx's packaging of Kokoro v1.0, checked against its SHA-256 and resumed if it stops. It runs on the same ONNX Runtime as the MMS voices, so the GitHub and Play builds fetch that first if it is not on the phone yet. Swipe or tap the bin to delete it; it also shows under Storage / Voice models.
Kokoro reads from pronunciation dictionaries, and a word missing from them, such as an unusual name, is skipped. Before it reads English, the keyboard spells out numbers ("$12.50" becomes "twelve dollars fifty cents", "3rd" becomes "third") and splits words run together in camel case. Making a sentence takes about as long as saying it on a recent phone, and longer on an older one. The model holds a few hundred megabytes of memory while it reads, and the keyboard unloads it with its other models when it has been hidden for 2 minutes.
## Offline voices
The phone's own text-to-speech voice reads by default. Many phones have no voice for smaller languages at all, and that is what the offline voices are for: Meta's [MMS-TTS](https://huggingface.co/facebook/mms-tts) voices, one per language, for more than 1,100 languages. A downloaded voice reads its language on the phone, with no connection.
Tools / Read back / Offline voices lists a voice for each of your keyboard languages that has one, with a Download button. Each voice is about 114 MB. Languages with no MMS voice are named under the list; the phone's voice keeps reading those.
1. The first download asks you to accept the voices' licence, Creative Commons Attribution-NonCommercial 4.0: free to use, not for commercial purposes. The voices come from Hugging Face under that licence, not from the keyboard, so you agree to it once, before anything downloads.
2. On the GitHub and Play builds a banner offers ONNX Runtime first, the engine the voices run on, a one-time download of about 33 MB that [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) uses too. The offline build carries it already. The F-Droid build asks you to turn on **Download feature libraries** first.
3. Download the voices you want. Each file comes from a fixed revision of the ONNX conversion of Meta's models and is checked against its SHA-256 before it is used. A download that stops resumes where it left off.
Swipe a downloaded voice's row, or tap its bin, to delete it. The voices also show under Storage / Voice models.
### Where the voices read
Once a language's voice is downloaded, it reads that language everywhere the keyboard speaks:
- **Read back**.
- The speaker button on [vocabulary](https://wmkeyboard.pages.dev/tools/vocabulary/) cards, when there is no recording to play.
- The speaker button on the [Look up](https://wmkeyboard.pages.dev/tools/look-up/) card, in the dictionary's language.
- **Speak** in [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/), for Bengali and Devanagari text and for text in the phone's own language.
A recording, when a card has one, still plays first. The speech rate is the one under Tools / Vocabulary; MMS voices have no pitch setting, so the pitch slider only changes the phone's voice.
### What to expect
MMS voices read letters, not phonemes, and they were trained on Bible readings, so they sound clear but plain, a little like someone reading a passage aloud. They read words as written: digits and symbols the voice has no letter for are skipped, so in most languages "3 kg" comes out as "kg". Each sentence is made while the one before it plays, so a long text starts once the first sentence is ready. Making speech is heavy work: a second of speech takes about a second of one fast processor core, so on a phone expect a pause of a second or so before the first sentence, and on a slower phone short gaps between sentences.
Voices that need Meta's romaniser, which spells a language in Latin letters before it is read, are not offered; for those languages the phone's voice reads.
## Privacy
Reading happens on the phone. A downloaded voice sends nothing anywhere. The phone's own voice is whatever text-to-speech engine you picked in Android's settings, and some of those engines use the network.
Read back is the **Read back** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on; downloaded voices stay.
---
# Resize the keyboard
URL: https://wmkeyboard.pages.dev/tools/resize/
Section: Tools
Summary: Drag the keyboard's height, position and side margins in place, over the keys, instead of hunting for sliders in Settings.
**Resize** is the tool for the moment you think "this keyboard is a bit too short," without wanting to go digging through Settings for a slider. Tap it and the keyboard dims, an outline appears around it with a handle on each edge, and you drag until it looks right. Nothing is saved until you press **Done**.
tools/resize-overlay — Resize mode: the keyboard dimmed behind its outline, with drag handles on all four edges and the Reset / move / Done row in the middle.
## Turning it on
Tools / Resize
Resize is one of the 72 toolbar tools, filed under **Keyboard modes** on the Tools screen next to Modes, One-handed, Split and Floating. Its row is just a switch: everything it changes already has a slider on the Layout & size screen, and the tool is only the in-place way to drag them.
You don't need the tool switched on to use resize mode once: **Resize by dragging** at the top of Layout & size / Size and position opens the test field with the keyboard already in resize mode.
Once it's on, it waits in the toolbox like any other tool, and you can drag it up onto the toolbar if you resize often enough to want it one tap away. See [The toolbar](https://wmkeyboard.pages.dev/tools/overview/) for how pinning works.
## Dragging
Five things move:
- **The top handle** scales the keys. Both the key height and the number row height change together, keeping their ratio, so a taller keyboard doesn't leave the number row looking stranded. The bottom edge stays put.
- **The bottom handle** moves the bottom edge and leaves the top edge where it is. Drag it down and the keys grow into the gap below the keyboard; drag it up and the gap grows while the keys shrink. It stops when the gap runs out either way, so it can never lift the top edge.
- **The left and right handles** set the margin on that side of the keyboard, the same **Left padding** and **Right padding** values the Layout screen has as sliders. Each side is independent, so a right-handed thumb can shave the left edge alone.
- **The round move button** in the middle slides the whole keyboard up and down without changing its height, so you can lift it off the navigation bar with a thumb in the middle of the board rather than reaching for its edge.
While the tool is open the keyboard area on screen is held at the tallest the keyboard could reach, or the tallest window your phone will actually give the keyboard, so nothing moves under your finger mid-drag. Where that window is short, the held space shrinks to fit it rather than pushing the keys off the bottom of the screen. The app above doesn't move at all: it stays laid out where the keyboard's top edge was when you entered, dimmed behind that held space, and a keyboard you drag taller grows over it. The app catches up once, when you press Done.
Either side of the move button:
- **Reset** puts everything back to the values the tool opened with. It doesn't reset to the app's defaults, just to where you started this session, so a drag you regret costs you nothing.
- **Done** saves and leaves.
Below those, once the keys are narrower than the screen, sits a **Full-width toolbar** chip. Tap it and the toolbar and suggestion strip stretch back across the whole screen while the keys keep the width the side handles gave them, which is the tablet arrangement Samsung Keyboard uses. Tap it again to narrow them with the keys. Like the drags, it only previews until **Done**, and it saves to the same **Full-width toolbar** switch as the Layout screen, for the screen shape you're in.
Everything behind the outline is covered by a scrim that swallows every touch, so you cannot type by accident while you're dragging. Push a handle as far as it goes and it turns red and buzzes once, which is how you know you've hit the limit rather than lost the drag.
The limits are the same ones the sliders in Settings use: key height and number row height run from **32dp to 100dp**, the gap under the keyboard runs from **0dp to 160dp**, and each side margin runs from **0% to 30%** of the screen width.
## What gets saved, and where
Done writes to whichever screen shape you're currently in. In portrait, that's the plain **Key height**, **Bottom padding**, **Left padding** and **Right padding** values under Layout & size / Size and position, plus the **Number row height** slider that sits one group higher under Layout & size / Number row. In landscape, or unfolded, it writes that shape's own override instead, exactly as if you'd opened the per-screen group in Settings and moved the sliders there yourself. Resizing in landscape therefore never disturbs your portrait sizing. See [Size, position & one-handed use](https://wmkeyboard.pages.dev/typing/size-position/) for how the per-screen overrides work.
Only the values you actually moved get written, and the **Full-width toolbar** chip counts as one of them only if you left it changed. Nudge the keyboard up without touching the top handle and no key height is saved at all, so a tablet still keeps its larger shipped default rather than being frozen at whatever number the drag happened to start from.
## Details & edge cases
- **Tapping the tool again cancels.** A second tap while resize mode is open closes it without saving, which is also what happens if you reach it twice from a hardware shortcut.
- **Done is the only thing that saves.** Anything else that takes the keyboard down drops the session unchanged: switching apps, hiding the keyboard, or the system **Back** gesture, which just dismisses the keyboard here. So does rotating, folding, unfolding, or moving to another display, since that swaps the screen shape out from under a preview that belongs to the old one.
- **It only works docked.** With the [floating keyboard](https://wmkeyboard.pages.dev/typing/size-position/) on, the tool refuses and says "Dock the keyboard to resize it": floating has its own drag grip on the frame, and these handles are laid out against the docked shape.
- **One-handed mode switches off when you enter.** So does any open panel, which closes through the normal path rather than just vanishing, so nothing is left behind it.
- **It does nothing while the voice bar is up.** Finish or dismiss the dictation strip first.
- **It works before you unlock.** Resize is one of the 45 tools available during [direct boot](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone), since it only touches the keyboard's own geometry.
- **A theme that pins its own key height wins afterwards.** Some themes ship a key height as part of their look. The drag previews fine and the value is saved, but the theme's number is what gets drawn once you're out of resize mode. A per-screen override beats the theme, so the same drag does stick in landscape or unfolded.
- **No hardware shortcut letter by default.** About two dozen tools have a default leader-key letter and Resize isn't one of them, but you can bind one under Typing / Physical keyboard. See [Hardware keyboards](https://wmkeyboard.pages.dev/typing/hardware-keyboard/).
- **The pill toolbox draws no arrow on it.** Resize doesn't open a panel or an activity, so it reads as an action rather than a destination, alongside Undo and Flashlight.
## Related pages
- [Size, position & one-handed use](https://wmkeyboard.pages.dev/typing/size-position/): every sizing slider the tool drags, plus per-screen overrides, split and floating.
- [The toolbar](https://wmkeyboard.pages.dev/tools/overview/): pinning, the toolbox, and direct boot.
- [Tools](https://wmkeyboard.pages.dev/reference/settings/tools/): the master list of all 77 tools and what each row on the Tools screen does.
---
# Scanner (OCR, QR, documents)
URL: https://wmkeyboard.pages.dev/tools/scanner/
Section: Tools
Summary: Point the camera at text, codes, or paper and get the result in your field.
Three camera-driven tools live under one Scanners group. **Text scan (OCR)** reads printed words into your field. **QR and barcode scanner** decodes a code and inserts or opens its value. **Document scanner** captures a paper document as a cropped, cleaned-up image. All three are in every build. On F-Droid's, text scan reads with PaddleOCR alone, the QR scanner with zxing-cpp, and the document scanner is the built-in one. The QR scanner reads with Google ML Kit or zxing-cpp; text scan reads with ML Kit, PaddleOCR or Tesseract, depending on the language. The document scanner uses Google's scanner where Google Play services can run it and the keyboard's own everywhere else.
The three scanners are also modes of the [Camera tool](https://wmkeyboard.pages.dev/tools/camera/#scanner-modes). A rail down the right edge of the camera, the QR scanner and Text scan switches between Photo, QR code, Text and Document, so a single Camera button on the toolbar reaches every one of them. Each scanner's own tool opens the camera straight in its mode.
## Scanning text (OCR)
Open **Text scan (OCR)** from the toolbar or toolbox to replace the keyboard with a full-bleed camera view. It covers the toolbar row too, not only the key area. Point it at printed text and it recognizes words on-device. A torch toggle appears if your device has a flash.
### Which languages it reads
Three engines read the text on your device, and a fourth, which you set up yourself, reads it online:
- **ML Kit** reads Latin-script text: English, French, Spanish and the like. It needs no download. On a phone with Google Play services it also reads Chinese (simplified and traditional), Japanese, Korean and Devanagari (Hindi, Marathi, Nepali and the like), and those recognizers read Latin words mixed into the text as well. Google Play services downloads each of them the first time you scan in that script; until it arrives, the panel asks you to try again in a minute.
- **PaddleOCR** reads Chinese (simplified and traditional) and Japanese, including vertical columns, with the English words mixed into them. It uses the PP-OCRv5 mobile models from Baidu's PaddleOCR project, which read these scripts far better than Tesseract does. Both languages share one download of 11 MB. It has no setting of its own: **Automatic** uses it. On the F-Droid build, which has neither ML Kit nor Tesseract, it reads Latin-script text too (English, French, Spanish and the like), and its download there is 21 MB; see [On the F-Droid build](#on-the-f-droid-build).
- **Tesseract** reads more than 100 languages in most scripts, for example Bangla, Hindi, Arabic, Russian, Greek, Thai, Chinese, Japanese and Korean. Each language needs a one-time download of its data, from 0.4 MB to 12 MB.
- **Online model** sends the photo to an online reader and shows the text it sends back. It reads any script, mixed scripts and handwriting, with no language data to download. The reader is one you add: a model that can read images, through any service with an OpenAI-compatible API (OpenAI, OpenRouter, Gemini, Mistral, or Ollama or LM Studio on your own computer), or a [Rens](https://git.maid.zone/stuff/rens) server, a free front end to Google Lens. See [Reading text online](#reading-text-online).
With **Text recognition** on **Automatic** (the default), every script ML Kit reads goes to ML Kit, Chinese and Japanese go to PaddleOCR where ML Kit can't read them, and every other script reads with Tesseract. So on a phone with Google Play services, Chinese, Japanese, Korean and Devanagari text reads with ML Kit. On a phone without it, Chinese and Japanese read with PaddleOCR and Korean and Devanagari with Tesseract. If you downloaded Tesseract's Chinese or Japanese data before PaddleOCR came, the scanner keeps reading with it until PaddleOCR's download is on your device too. The scanner reads in the language you're typing in when you open it. If you've turned on more than one language, a chip at the bottom left of the viewfinder names the text's language, and tapping it steps to the next one without switching your keyboard. All your languages that read the same way share one stop on the chip: on Automatic, every Latin-script language is one stop.
On the F-Droid build, **Text recognition** offers only **Automatic** and **Online model**. Automatic reads Chinese, Japanese and every Latin-script language with PaddleOCR, so they all share one stop on the chip. A language in any other script, Bangla or Russian for example, shows a message in place of the viewfinder saying that nothing on the phone reads it, and reads only with Online model.
A language without Tesseract data of its own reads with the main one for its script: Maithili and other Devanagari languages read with Hindi's, and a Latin-script language with no data of its own reads with English's.
The first time you scan in a language that needs Tesseract or PaddleOCR data, the viewfinder is replaced by a **Download** button that says how big the data is. The download shows its progress in the panel and can be cancelled there. With [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) set to ask, the first press explains that it uses mobile data and a second press starts it. Set to block, the button says why it can't. Tesseract's data comes from the `tessdata_fast` 4.1.0 set of the Tesseract project on GitHub. PaddleOCR's is three files, a text detector, a line recognizer and its list of characters, converted to run on the same LiteRT engine as [offline Whisper voice typing](https://wmkeyboard.pages.dev/tools/whisper/) and published on GitHub by the PPOCRv5-Android project; the download is pinned to one version and each file is checked against its checksum. The F-Droid build fetches only the list of characters from there. Its detector and recognizer are PaddlePaddle's own models exported for MNN and published in WM Keyboard's data repository, checked the same way; on a set of test pages they read exactly the text the LiteRT ones do. Both stay in the app's private storage. You can also download or delete each language on the tool's settings page, and delete it under [Storage](https://wmkeyboard.pages.dev/privacy/data/). On a phone that cannot download, **Get the files** on that page links each language's `.traineddata` file, or PaddleOCR's three files, and **Import** installs ones fetched elsewhere ([Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/)).
After a capture, the recognized words appear as tappable chips grouped by line:
- With **Start with everything selected** on (the default), every word starts selected and tapping a chip deselects it, so you can trim the capture down to what you need. Turn it off and chips start empty, so you tap to pick words one at a time instead.
- **Copy** and **Insert** act on whatever's currently selected.
- **Translate** opens the [Translate](https://wmkeyboard.pages.dev/tools/translate/) tool on the selected words, translated straight away into its language. Pick only the line you need first, and only that line is translated.
- **Scan again** discards the capture and returns to the viewfinder. A select-all/deselect-all toggle flips every chip at once.
PaddleOCR finds each line of text first and then reads the lines one by one, so a line that's tilted, or a column of vertical Japanese, comes out in reading order: rows top to bottom, then columns right to left. Each line becomes one chip, since Chinese and Japanese put no spaces between words.
Tesseract reads each photo twice, once with a threshold set from each small area of the photo and once with one threshold for the whole photo, and keeps whichever read found more text. The first copes with uneven light: a shadow across the page, the dark corners of a phone photo, or the torch's bright spot. The second copes with light text on a dark screen. "No text found" means both reads came back empty. If recognition itself fails, the panel says **Could not read** instead. If a downloaded language won't load, with Tesseract or PaddleOCR, the scanner deletes that language's data and offers the download again.
If your device has no camera, the panel shows a message instead of a blank viewfinder.
To read a photo that's already on your phone, such as a screenshot, tap the picture button in the top-right corner of the camera view. The keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) opens, or Android's photo picker in the Play Store build, and the photo you choose is read in the language on the chip, then lands on the same chips as a capture. It is shown whole rather than cropped to the viewfinder.
A picture you copied can be read the same way, with no camera at all: press and hold it in the [clipboard panel](https://wmkeyboard.pages.dev/tools/clipboard/) and tap **Extract text**. The scanner opens on the picture and reads it at once, and its back button returns to the clipboard. **Scan again** shows the picture with the language chip and the shutter, so you can read it again in another language. The button is only there while Text scan is on in your tools.
## Scanning QR codes and barcodes
**QR and barcode scanner** is also full-bleed and decodes continuously as you point the camera, with no shutter tap needed. It reads QR codes plus the common barcode formats: Aztec, Codabar, Code 39/93/128, Data Matrix, EAN-8/13, ITF, PDF417, and UPC-A/E, labeled by format in the result.
The first code it decodes freezes into a result card with **Scan again**, **Copy**, **Share** and **Insert**. If the decoded value is a plain URL (`https://…`, `http://…`, or `www.…`), an **Open** button appears above that row. Anything else is text only, so a scanned value like a Wi-Fi network payload inserts as raw text rather than turning into a connect action. Pinch to zoom while scanning, and a small pill reads out the zoom ("1.2x" style) above roughly 1.05x. One particular link gets a little extra recognition on the result card, over on [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#scan-the-inevitable-qr-code).
To read a code that's already on your phone, such as a screenshot of a QR code, tap the picture button in the bottom-left corner of the camera view. The keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) opens, or Android's photo picker in the Play Store build, and the photo you choose is read the same way, on-device, landing on the same result card. When the picture holds no code, a short message says so and the camera view stays up.
### Which reader decodes codes
Two readers can decode what the camera sees, and the result card is the same with either.
- **ML Kit** is Google's barcode reader. It is the default wherever it can run: the GitHub builds, which carry its model, and the Google Play copy on a phone with Google Play services, which supplies the model.
- **zxing-cpp** is an open-source reader (Apache-2.0). The F-Droid build reads with it, since ML Kit is a Google library, and so does the Google Play copy on a phone without Google Play services. Anywhere else, turn on **Read codes with zxing-cpp** in the options below to use it instead of ML Kit. Besides the formats above it reads Micro QR, rMQR, MicroPDF417, MaxiCode, GS1 DataBar, Telepen and the Code 39 variants, which the card labels "Barcode" when it has no name for them. It also tries light codes on a dark background, such as a QR code on a phone in dark mode.
zxing-cpp is a separate download of about 1 MB, fetched the first time the scanner opens with it: from Google Play on the Play build, and from GitHub elsewhere, which on F-Droid's build waits until you turn on [Download feature libraries](https://wmkeyboard.pages.dev/reference/settings/privacy/#downloads). Until it's on your phone, the scanner shows its size and a **Download** button in place of the camera. A UPC code reads as its own 12 or 8 digits with either reader.
## Options
All three are one bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/), **Scanners**. Off there, the three tools and the Scanners group of the Tools list are gone until it is switched back on.
All three scanners have their own settings screen, and all three start with the same header rows before anything tool-specific:
- **Turn on**: Text scan is in the "recommended" and "show me everything" starter sets that setup lands, so it arrives on unless you picked the simplest answer. Neither QR and barcode scanner nor Document scanner is in any starter set, so setup leaves both switched off unless you pressed **Everything** on its tools page. Turn them on here.
- **Icon colour**: only shown while [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) is on globally, which it is by default. Text scan is green, QR and barcode teal, Document scanner a light blue.
- **Trigger words**: under a **Keyword shortcut** heading. It's `ocr` for Text scan, `scan` for QR and barcode, and `docscan` for Document scanner. Type one of them on its own and the suggestion strip offers to open that tool. Commas separate several words, and an empty field means the tool never offers itself.
- **Match capitals**: off by default on all three, so a trigger word matches however you capitalise it. Turn it on and only the exact spelling counts.
- **Reset to defaults**: joins the group once you've edited that tool's trigger words, and puts them back.
Tools / QR and barcode scanner / Options
- **Read codes with zxing-cpp**: off by default, and shown only where ML Kit's reader can run. Decodes with the open-source reader instead; see [which reader decodes codes](#which-reader-decodes-codes). The caption above the rows names the reader in use.
- **Insert automatically**: off by default. With it on, the scanned code's text types into your field the instant it's spotted, with no confirm tap.
- **Vibrate on detection**: on by default. A short buzz when a code is spotted.
- **Load link details**: off by default. When a scanned value is a web link, it fetches the page's title and description to show above the result. That needs a network connection. [Power Saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) force-disables this row while its background-network toggle is on.
Recognition for both OCR and the QR/barcode scanner runs entirely on-device, so no photo is uploaded and both work offline once any Tesseract or PaddleOCR data they need is downloaded. The one exception is **Text recognition** set to **Online model**, which sends each photo you scan to the service you named. Link previews are the one exception, since fetching a page's title needs the network. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what else does and doesn't touch the network.
Tools / Text scan (OCR) / Options
- **Start with everything selected**: on by default, described above.
- **Text recognition**: **Automatic** (the default), **ML Kit**, **Tesseract** or **Online model**. See [which languages it reads](#which-languages-it-reads). The online model ignores the text's language, so the viewfinder shows no language chip with it. With ML Kit the chip picks its recognizer, and a language in a script ML Kit has no recognizer for reads with the Latin one. Tesseract reads Latin script too, usually slower than ML Kit.
### Reading text online
With **Text recognition** on **Online model**, an **Online text reader** group appears under it. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/): add readers with **Add a provider**, drag them into order, and switch them off. The first reader that reads the photo is used, and the next one is asked only if it fails. With **How to use them** set to **Choose in the panel**, a chip at the bottom right of the viewfinder names the reader, and tapping it picks another. Two kinds of reader can be added, each more than once:
- **Your vision model**, a model that reads images through an OpenAI-compatible API:
- **Server address**: such as `https://api.openai.com/v1`, `https://openrouter.ai/api/v1`, or `http://192.168.0.10:11434/v1` for Ollama on your computer. The full address ending in `/chat/completions` works too.
- **Model**: required. The name of a model that reads images, as the service spells it, for example `gpt-4o-mini`.
- **API key**: the service's key. Leave it blank for a server of your own that asks for none. It's kept with your other API keys, and leaves the phone in a backup only with **Include API keys** on.
- **Rens**, the address of a Rens server, your own or [a public one](https://git.maid.zone/stuff/rens#instances). Rens passes the photo to Google Lens and sends back the text Lens found. It strips the photo's metadata and gives it a random name first, and Google sees the server, not your phone. **Test** checks that the address is a Rens server without sending a photo.
A reader that was set up before the list existed is in it already, as **Your vision model**.
Each scan sends the photo, scaled down to 1600 pixels on its longer side. A vision model gets a request to write out exactly the text in it and nothing else; Rens gets the photo the way its own web page sends it. The text comes back as the same word chips the other engines give. A paid service may charge for each scan. With no reader on, the panel says so after the shutter, and an error from the reader, such as a wrong key or a model that can't read images, shows as a message rather than as "No text found". Every scan shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) as **Scan text, online**.
Tools / Text scan (OCR) / Language data
One row for each download the languages you type in need, with its size, a **Download** button, its progress, or a delete button once it's on your device. Two languages that read with the same data share one row, for example Norwegian Bokmål and Nynorsk, or Chinese and Japanese, which on **Automatic** share PaddleOCR's row (named "with PaddleOCR") wherever ML Kit can't read them. On **Tesseract** the list shows Tesseract's own Chinese and Japanese data instead. The list is hidden while **Text recognition** is on ML Kit or Online model. On the F-Droid build it has one row, PaddleOCR's, for every language PaddleOCR reads.
## Scanning a document
**Document scanner** has two scanners behind it, and both work the same way from the keyboard's side: the scan runs full screen, outside the keyboard (an IME can't host that kind of flow itself), and the pages come back as JPEGs. They go into the field you scanned for, one after another. Some chat apps take only the first: WhatsApp opens its own send screen on it. Whatever pages the field didn't take then open together in that app's share screen, so WhatsApp sends them all as one album, and a toast says how many went that way.
- **Google's scanner** is the default on a phone with Google Play services. Edge detection, cropping and cleanup happen in Google's own UI, and gallery import is allowed alongside the camera.
- **The built-in scanner** is the keyboard's own, built on [OpenCV](https://opencv.org/). It is what the F-Droid build and phones without Google Play services use, and on any other phone you can pick it with **Use the built-in scanner**. If Google's scanner fails to start, the built-in one opens in its place.
The built-in scanner goes through three steps:
1. **Capture.** The camera outlines the page it sees as you aim. Press the shutter, or the photo button to pick a picture you already have. The light button turns on the flash as a torch.
2. **Corners.** The scanner places the four corners on the page it found. Drag any that missed; a magnifier shows the spot under your finger while you drag. When it finds no page, the corners start just inside the edges of the picture. The button beside the tick swaps between the corners it found and the whole picture. A red outline means the corners cross over each other, and the tick waits until they don't.
3. **Page.** The flattened page, with four looks: **Original**, **Colour** (the default: paper turned white and shadows lifted, ink and coloured areas kept), **Grey**, and **Black and white**. The turn button rotates it a quarter turn at a time. **Add page** keeps it and goes back to the camera for the next one; **Done** keeps it and sends every page.
Back steps back through those three. Leaving the camera with pages kept asks whether to send or discard them. The camera's permission is asked for on the scanner's own screen; without it, the photo button still works.
The built-in scanner needs its library, about 2.5 MB. Builds that don't carry it fetch it the first time you scan (or from the banner on the tool's settings page), the same way as the keyboard's other feature libraries: from Google Play on the Play build, and from GitHub elsewhere, which on F-Droid's build waits until you turn on [Download feature libraries](https://wmkeyboard.pages.dev/reference/settings/privacy/#downloads). Everything runs on your device.
Once you're back in your app, each captured page is inserted exactly like a Camera-tool photo, sent to the field one page at a time.
Tools / Document scanner / Options
- **Use the built-in scanner**: off by default, and shown only where Google's scanner can run. Scans with the keyboard's own scanner instead.
- **Save to the gallery**: off by default. Also keeps scanned pages in the **WM Keyboard** album under Pictures.
## Details & edge cases
- **Camera permission** works differently across the three tools. OCR and QR/barcode check for it themselves, and when it isn't granted they offer an **Allow the camera** button. That opens the same disclosure-then-system-prompt trampoline the [Camera tool](https://wmkeyboard.pages.dev/tools/camera/#granting-camera-access) uses, and returns you to the panel. The document scanner skips that prompt: Google's scanner manages its own camera permission, and the built-in one asks on its own screen.
- None of the three scanners work before you've unlocked your device once after a restart (no direct-boot support), unlike some other tools that do work at the lock screen.
- The trigger words in [Options](#options) are what make a standalone `ocr`, `scan` or `docscan` surface a chip into the matching tool. [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) covers that mechanism in full.
- Open any of the three from Settings → **Tools** → **Scanners**, or by typing its keyword as described above.
## On the F-Droid build
Elsewhere text scan leans on Google: ML Kit's text-recognition library (and its Chinese, Japanese, Korean and Devanagari ones, whose models Google Play services fetches), and the LiteRT engine that offline voice typing also uses, for PaddleOCR. Beside them sits a Tesseract library built into the app, about 1.7 MB on a 64-bit phone. None of the three is in the F-Droid build. ML Kit and LiteRT are Google's, and Tesseract's library is one F-Droid can't build from source.
So the F-Droid build runs PaddleOCR on [MNN](https://github.com/alibaba/MNN) instead, an open-source engine from Alibaba that the build compiles from source for the [sticker generator](https://wmkeyboard.pages.dev/emoji/sticker-generator/). Both features share that one library, which arrives with the first download after you turn on [Download feature libraries](https://wmkeyboard.pages.dev/reference/settings/privacy/#downloads). It reads Chinese, Japanese and Latin script. Anything else needs the Online model reader. Google's document scanner depends on Google Play services' document scanner module, so F-Droid's document scanner is the built-in one, and its QR scanner reads with zxing-cpp; neither has any Google code in it. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're deciding between builds.
---
# Search, Wikipedia & dictionary
URL: https://wmkeyboard.pages.dev/tools/search/
Section: Tools
Summary: Web and image search, a three-tab Wikipedia reader, and instant word definitions: four lookup tools built into the keyboard.
This page covers three tools, each with its own panel and its own settings screen: **Web search**, with its **All** and **Images** tabs (backed by Brave, by Tavily with a key of your own, or by a SearXNG server you name on the F-Droid build), a three-tab **Wikipedia** reader, and an **English dictionary**. Web search and Wikipedia never read what's already in your field. You type a query into the panel's own search bar, and nothing out of the field you're typing in is sent anywhere. Dictionary is the one exception. With its auto-lookup setting on, which is the default, opening the tool reads your current selection or the word at the cursor and looks that up. See [Looking up definitions](#looking-up-definitions) below.
tools/web-search-results — Web search results. Tap a row for its menu: open it in your browser, insert its link, or copy the link.
## Web and image search
Open **Web search** from the toolbar, or from the toolbox if you haven't pinned it. It isn't pinned by default. On a hardware keyboard, double-tap Ctrl for the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/), then press W.
Results come in two tabs under the search bar: **All**, the web results, and **Images**. Switch tabs after a search and the same search runs on the other tab, once; switching back shows what was already there. Image search used to be a tool of its own. Its **Image search** button is still in the toolbox and opens this panel on the Images tab, and so do the I leader key, a key or gesture bound to it, and a smart chip for "images". Its settings are all on web search's page now.
Both tabs ask the providers in their **Providers** list, in order: the Brave Search API by default, [Tavily](#searching-with-tavily) with a key of your own, or a SearXNG instance you name, on any build. If the first one fails, the next one that is on answers. Typing routes into the panel's own header search bar rather than your field, the same key-rerouting trick the GIF, sticker, and Wikipedia panels use. Unlike GIFs and stickers, the search doesn't run live as you type. It waits for you to press the search action before firing a request. To search in your browser instead, tap the open-in-browser icon at the end of the row of tabs: it opens the typed query as a search page on your SearXNG instance when that is your first provider, on Brave Search when Brave is, and on DuckDuckGo otherwise. Nothing is fetched by the keyboard for it.
tools/image-search-grid — The Images tab. Tap a thumbnail for its menu: insert the picture, save it, or open, insert or copy its link.
- **All**: each row shows the title, host, and an HTML-stripped snippet. From a SearXNG instance the host is followed by the engines that found the result, such as `en.wikipedia.org · google, duckduckgo`. Tap a result for its menu: **Open in the browser** (which leaves the keyboard), **Insert the link** at the cursor, or **Copy the link**. **Tap a result** in the options can skip the menu: set to **Insert**, a tap inserts the address and an open icon on each row opens the page; set to **Open in the browser**, it's the other way round. Press and hold a result for its menu whatever that says.
- **The search engine's answer**: when the backend writes a short answer to your query, it sits in a box above the results. Tavily writes one from the pages it found, and some SearXNG instances give instant answers; Brave doesn't with a normal search key. It's drawn the way the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) draws an answer: LaTeX formulas are typeset, Markdown shows as headings, lists, bold and links, and a code block sits in its own box with **Copy** and **Insert** for just that code. The box shows about four lines and a tap shows the rest. Two icons beside its label act on the whole answer: the copy icon puts it on the clipboard as written, and the insert icon puts it in your text without the Markdown marks. Press and hold either to see its name. Under the box, a row of up to five pictures for your query: Tavily sends them with its answer at no extra cost, and with another provider the row shows the Images tab's pictures when that tab already searched the same words. A picture there works as it does on the Images tab. Turn **Show the answer from the search engine** off to hide it.
- **Images**: tap a thumbnail for its menu. **Insert the picture** downloads the full image and inserts it directly, on the same `commitContent` path the clipboard and GIF tools use. **Save to the gallery** downloads it into Pictures/WM Keyboard. **Open in the browser** opens the page the picture is on, or the picture itself when the search didn't say which page that is. **Insert the link** and **Copy the link** give the picture's own address. With **Tap a result** set to **Insert**, a tap inserts the picture, and set to **Open in the browser** it opens its page; press and hold for the menu either way. The grid dedupes results by image URL before drawing them, since Brave can return the same full-size image for more than one widely-reposted result.
- **More results**: at the end of either tab, it runs the same search again for the next page and adds what it finds under what you have. Press it again for the page after, as often as you like; when a page brings nothing new it reads "No more results" instead. Each press is one more request, which counts against your quota like a search. Tavily has no pages, so the keyboard asks it again without the sites already shown, and without the answer, which came with the first page. Brave gives ten pages of web results and up to 200 pictures, and a SearXNG instance pages as far as its engines go. With [data saving](https://wmkeyboard.pages.dev/privacy/data-saver/) holding searches back on mobile data, the button says so and fetches nothing.
To search with a picture instead of words, tap the **camera button** at the right of the search bar. It opens the [camera tool](https://wmkeyboard.pages.dev/tools/camera/#searching-with-a-photo), and after the shutter the photo goes to Google Lens, a search site, or the share sheet, whichever **Search with** names on the camera tool's settings page. Back from the camera returns you here.
Results stay put after you leave. Open one link in your browser, come back, and open the tool again: the same list is there under the same search, so reading several results costs one request, not one per visit, and the list can't change under you. Tap the **✕** at the end of the search bar to clear it and start a new search, or tap the bar itself to edit the search you ran. The two tabs each keep their own results. They're held in memory only, so they're gone once Android closes the keyboard.
tools/search-needs-key — The web search panel before a Brave API key is configured.
Without something to search with, the tool doesn't appear on the keyboard at all. There's no button that opens a panel with nothing behind it. Until then Web search and its Image search button are off the toolbar, out of the toolbox, and out of the smart chips, and a hardware shortcut bound to either one does nothing. On the **Tools** screen they're still listed, with their switch greyed out and a reason in place of the usual description. Everywhere except the F-Droid build, that reads "Needs an API key. Press this row to add one." The F-Droid build reads "Needs a SearXNG instance" instead, since it offers a self-hosted server as another way in. Add a provider to the list and both come straight back.
### Options
Tools / Web search
Every setting for both tabs is on **Tools / Web search**. **Tools / Image search** only has the switch and icon colour for its button, and a row that leads here.
- **Turn on**: already on, like every tool. Switching it off removes that tool from both the toolbar and the toolbox. The switch can't be pressed while there's nothing to search with, since there's nothing to turn on.
- **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are turned on globally.
- **Providers**: who answers, in order. It is a [provider list](https://wmkeyboard.pages.dev/tools/providers/), shared by both tabs: drag to reorder, switch a row off, or add **SearXNG**, **Tavily** or **Brave Search**. A fresh install starts with Brave when the build has a Brave key, and empty otherwise. Updating keeps the order your earlier settings gave: a SearXNG instance first, then Tavily, then Brave.
- **SearXNG**: the address of a SearXNG server, your own or one you trust, on any build. Add more than one, and the next is asked when one refuses. The instance has to allow its JSON API (`json` under `search.formats` in its `settings.yml`), which most public instances don't, and public instances also rate-limit anything that isn't a browser. The keyboard sends the headers a browser would, so SearXNG's own bot check doesn't refuse it outright, but a busy public instance can still answer "too many requests" (HTTP 429). Some public instances also sit behind a challenge page (Anubis or similar) that only a real browser can pass; the panel then says the instance answered with a web page, and the fix is another instance. One you run yourself, with its limiter off, never refuses.
- **Brave Search**: its setup takes a key from `api-dashboard.search.brave.com` to use your own quota. Leave it blank and the app uses a key baked into the build, if this build ships one; the row then says "Uses the key in this build".
- **Tavily**: its setup takes your key from `app.tavily.com`. No build ships a Tavily key.
- **SafeSearch**: on by default. It filters explicit results. On the All tab it maps to Brave's "moderate" filter. Brave's image search only offers strict or off, so the same toggle maps to "strict" on the Images tab. Tavily has one level, on or off.
- **Results for each search**: a slider from 1 to 20, defaulting to **8**. ("Each search uses one API request either way," the app notes.) 20 is the most the Brave and Tavily APIs give for one request.
- **Show the answer from the search engine**: on by default. Shows the box described above when the engine sends an answer. On Tavily the answer costs no extra credits.
- **Tap a result**: **Show the menu** by default. **Insert** puts the link in your text (on the Images tab, the picture itself), and **Open in the browser** opens the page. Press and hold a result for its menu whichever you pick. If you had turned on **Open results in the browser** before this replaced it, it starts on **Open in the browser**.
- **Search depth**: on Tavily's own screen (press its row on the list), **Basic** by default. Tavily's API has four depths:
- **Ultra fast**: the quickest answer, with one short summary of each page. 1 credit.
- **Fast**: quicker than Basic, with results nearly as good. 1 credit.
- **Basic**: the balance of speed and good results. 1 credit.
- **Advanced**: the deeper search, which picks the parts of each page that match your query more closely. 2 credits.
Tavily takes no SafeSearch at the two fast depths, so results aren't filtered there whatever **SafeSearch** says.
- **Images per row**: 3 by default. How many images sit side by side on the Images tab, from 2 to 5. Fewer columns means bigger previews, which is easier to judge before you send one.
The settings page's own caption: "Web and image search share everything here. They search the whole web. The Brave plan includes a free monthly credit of about a thousand searches. Brave asks for attribution, so the panel shows 'via Brave'." That attribution is a small **"via Brave"** label inside the search bar, at its right end (just before the **✕** when there's a search to clear). It reads **"via Tavily"** while Tavily answers, and a SearXNG instance, being your own server, gets no label.
### SearXNG categories
Tools / Web search / SearXNG / Search categories
SearXNG sorts its engines into categories: general, images, videos, news, science, IT, music, files and more, depending on the instance. **Search categories**, on the SearXNG row's own screen, lists the ones your instance has and lets you pick any of them. The list comes from the instance's `/config` page, which answers even where the JSON search API is off; when the instance can't be reached, the screen offers the usual SearXNG categories and says so. General and images aren't in it, since they're the **All** and **Images** tabs already. None are picked by default.
Each category you pick becomes a chip in the panel's row of tabs, between **All** and **Images**, in the order the instance lists them; the row scrolls sideways when there are more than fit. Tap one to search that category only. Its results look like the **All** tab's, with a title, the site, the engines and a snippet, and a video result shows its thumbnail at the start of the row. **More results** pages through the category, and a tap on **All** goes back to the general search. The search engine's answer only shows on **All**.
The chips only show while a SearXNG instance is the one that searches: first in the **Providers** list, or picked in the panel's provider chooser. With Brave or Tavily first, the row is **All** and **Images** as before. A category search only asks the SearXNG instances in the list, never Brave or Tavily, whose general results would pass for the category's.
### Searching with Tavily
[Tavily](https://tavily.com) is a search API built for AI tools, and it works as a third backend for both tabs. Sign up at `app.tavily.com` for a key. The free plan gives 1,000 credits a month, and each search the keyboard makes uses one: it asks for Tavily's `basic` search unless you pick another **Search depth**, and only **Advanced** uses two. The answer Tavily writes for the query is free, and shows above the results while **Show the answer from the search engine** is on, with up to five of the pictures Tavily found under it, also free.
- **All** looks the same as with Brave: a title, the site, and a snippet. The snippet is the passages Tavily picked from the page, joined with "…".
- **Images** has no image index of its own behind it. Tavily sends back the pictures it found on the pages of its results, so a search usually finds fewer pictures than Brave, with no title under them. Tapping and long-pressing work as they do with Brave.
- With both Brave and Tavily in the list, the one higher up answers, and the other one only when it fails. Drag them to change which comes first.
- When your credits run out, or the key is wrong, the panel shows Tavily's own message for it.
- Tavily's own screen in the search list counts the credits this phone spent this month, and **Check what is left** asks Tavily for your plan's balance. After that the search bar reads, for example, "via Tavily · 812 left". With your own Brave key the same appears for Brave, which reports it on every search. See [Usage and credit](https://wmkeyboard.pages.dev/tools/providers/#usage-and-credit).
## Reading Wikipedia
Open **Wikipedia** from the toolbar, or the toolbox if you haven't pinned it. It isn't pinned by default either. Unlike the other tools on this page, Wikipedia has no [leader-key](https://wmkeyboard.pages.dev/reference/shortcuts/) letter of its own out of the box. From a hardware keyboard the toolbox shortcut is the way in: double-tap Ctrl, then press T. You can also rebind a letter to it yourself. The panel takes the whole keyboard, the toolbar row included, with a back button and the search bar in its header. While you type a search it shrinks to that header so the keys fit underneath. Results list article titles with short descriptions, and tapping one opens the article.
tools/wikipedia-article — An open Wikipedia article, with its Summary, Links and Full article tabs.
An open article has three tabs:
- **Summary**: the article's short description plus its extract, with **Insert summary**, **Insert link**, and **Insert title** chips.
- **Links**: the article's outgoing links, namespace 0 only, so no Talk: or File: noise. The list is capped by **Links per article** in the tool's options: 50 to 500, defaulting to 200. 500 is as many as Wikipedia returns at once. The tab loads the first time you open it, and each link gets its own **Insert** chip.
- **Full article**: the complete plain-text extract, split into paragraphs. Tap any paragraph to insert just that paragraph. Also loaded on first visit rather than up front. Formulas are typeset in place, the way the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) draws maths, rather than spread over the screen one symbol per line. What you insert or copy has them written out in plain characters (`x²`, `√(x+1)`), since a text box can't show a typeset formula. **Ask AI** gets the formula's LaTeX.
Two buttons beside the article's title are for reading it in more room. The expand button grows the panel up the screen, to as much as 80% of its height, and tapping it again brings it back to the keyboard's height; search results always stay at the normal height. The other opens the article's page on wikipedia.org in your browser.
Under the title, next to the tabs, **Copy** copies the article's text (the full article once you've opened that tab, otherwise the summary) with its address after it.
When the phone has an app that looks words up, such as Kiwix with a Wikipedia file in it or Aard 2, an **Open in** chip sits after **Copy** and hands it the article's title. A search that finds nothing offers the same chip with your query. With one such app the chip names it; with several, tapping it lists them. Apps that offer themselves in the text selection menu count too, so a translator may be on the list. The chip doesn't show on a phone with none.
Once you've opened an article from a results list, a back arrow appears and returns you to those results instead of a blank search. The idle screen puts it simply, with `en` swapped for whichever language you've set below: "Search for a subject on en.wikipedia.org. Read the summary, open the links, and insert text or the address of the article."
### Asking the AI about an article
With the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) on, an **Ask AI** chip sits next to the tabs, beside **Copy**. It opens the AI panel's [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) on a new conversation with the article attached: the full article when you've opened that tab, otherwise the summary, along with its title, description and address. The keys come up under the message box so you can type your question straight away, or pick **Explain simply**, **Summarise** or **Hard words** to start one for you. Nothing is sent until you send it.
To ask about one part, long-press a word in the summary or in a paragraph of the full article. The word is selected, with a handle at each end to drag it wider, and a bar over it offers **Ask AI**, **Copy**, **Insert**, **Quote** and **Select all**. **Ask AI** sends the passage along with the paragraph it came from, so the model reads it in context. **Quote** inserts the passage in quotation marks followed by the article it came from: `"…" (from [Wikipedia: Cat](https://en.wikipedia.org/wiki/Cat))` when **Markdown links** is on in the tool's options, `"…" (Wikipedia: Cat, https://en.wikipedia.org/wiki/Cat)` when it's off. Tap anywhere else to let the selection go. A selection stays inside one paragraph. **Copy** and **Quote** work without the AI tool.
### Options
Tools / Wikipedia
- **Turn on**: already on.
- **Icon colour**: only shown if colorful tool icons are on globally.
- **Wikipedia language**: a free-text subdomain code (`en`, `bn`, `de`, `fr`, `es`…), defaulting to **`en`**. It changes which language's Wikipedia every request in the tool reads from. Search, summaries, links, and the full article all follow it.
- **Markdown links**: off by default. Turn it on to insert links as `[Title](url)` instead of a bare URL, in both the Summary tab's "Insert link" chip and every Links-tab chip.
- **Links per article**: how many outgoing links the Links tab lists, from 50 to 500 and defaulting to **200**. A long article can have hundreds, and anything past your number is cut.
The settings page's caption: "The searches and the article text come from the free APIs of wikipedia.org, only while you use the tool." No API key is needed anywhere in this tool. It calls Wikipedia's free public REST and MediaWiki APIs directly.
## Looking up definitions
The keyboard has two things it calls a dictionary, and they don't share anything.
- **Word lists**, under **Languages & layouts**, are for typing: suggestions, glide and autocorrect, in any language. They hold spellings and how common each is, never a meaning.
- **The Dictionary tool**, under **Tools › Dictionary**, shows definitions. Downloading a word list does nothing for it. You don't need vocabulary packs or Anki either.
Out of the box the tool defines English words, online or from an [offline dictionary](#offline-dictionaries). For any other language, [import a dictionary](#imported-dictionaries) in Yomitan or StarDict form, or send the word to a [dictionary app on the phone](#dictionary-apps-on-the-phone). [Translations](#translations) go from English into other languages, not the reverse.
The dictionary looks up English words in a chain of sources you put in order (see [Dictionary sources](#dictionary-sources) below). None of them needs a key or an account. Open **Dictionary** with auto-lookup on, which is the default (see Options below), and it immediately looks up your current selection, or the word touching the cursor if nothing is selected. In the search bar the entry follows what you type, about 400 milliseconds after you stop, and a short result shows between the bar and the keys. Pressing return or the arrow puts the keys away so the whole entry shows. Each pause asks your sources in order, so with online sources on, a half-typed word can reach them too.
tools/dictionary-entry — A dictionary entry: serif headword, pronunciation audio, numbered definitions and synonym chips.
- Auto-lookup only picks up Latin-script words. A word holding any character outside that range, Bengali script for instance, is skipped quietly rather than looked up and shown "not found." The word also has to be 40 characters or fewer and hold at least one letter. Surrounding quotes and hyphens are trimmed off first.
- If there's nothing to look up and no earlier result is still on screen, the tool drops straight into its own search field instead.
- Headwords render in a serif display font (Lora), distinct from the rest of the keyboard's typography, falling back to the system serif if the font hasn't downloaded yet.
- Each entry shows the word and, when the source has them, its IPA phonetic transcription. Every entry has a speaker button, which plays a [recording of a real person](#recorded-pronunciations) saying the word when one exists. Otherwise it plays the source's own recording, and when there's none, or it can't be fetched, it says the word with a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) or the phone's text-to-speech voice. Set the voice's speed and pitch under the Vocabulary tool. The **Wiktionary definitions** source has no phonetics and no recording. An **Insert** chip types the headword itself at the cursor, not a definition.
- When an installed [vocabulary pack](https://wmkeyboard.pages.dev/tools/vocabulary/) has the word and a translation for it, a line under the headword gives its meaning in your language: the one you picked under **Translation languages**, or else your keyboard's. It needs the Vocabulary tool on and translations shown in the keyboard under its **Card sections**; a word outside the packs has no such line. When the entry has [translations](#translations), that line is left out, because the translations already say it.
- Each meaning lists its part of speech, numbered definitions with optional italic example sentences, and up to 12 tappable synonym/antonym chips per row. Tapping a chip looks that word up in turn.
- An entry from an [imported dictionary](#imported-dictionaries) can carry more: the reading in small kana over a Japanese headword, and a line under it with the pitch accent and the dictionary's frequency figure, as in "たべꜜる [2] · Frequency 512". The ꜜ marks the mora the pitch falls after; [0] is a flat word.
- A line under the last entry names the source that answered, for example "From Wiktionary". An imported dictionary is named by its own title, as in "From Jitendex".
- A **Copy** chip beside the search bar copies everything the look-up found as text. With the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) on, an **Ask AI** chip beside it opens a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) about the same text, and a long press on a definition or an example selects it, with **Ask AI**, **Copy** and **Insert** on a bar over it. See [Asking the AI about an article](#asking-the-ai-about-an-article), which works the same way.
- "The app cannot reach the dictionary" means no source could be reached: you're offline, or every online source you left on is down. A word that every reachable source answered for, without an entry, shows "No entry for" instead.
- When only the sources on the device answered and no online source could be reached, the panel says the word is not in the dictionaries on this device, rather than "No entry for". The vocabulary packs hold only the words of their own study lists, a few thousand at most. To define ordinary words with no connection, download one of the [offline dictionaries](#offline-dictionaries).
The same look-up works over any other app: select a word and pick **Look up**, or tap it after the floating button's Look up action. See [Look up](https://wmkeyboard.pages.dev/tools/look-up/).
### Recorded pronunciations
The first tap on an entry's speaker looks for recordings of people saying the word, in the word's language. Wiktionary is always asked. It needs no key, and its pages play audio files from Wikimedia Commons, many of them recorded through Lingua Libre, in a lot of languages the phone's voice reads badly or not at all. With your own [Forvo](https://forvo.com) API key added under the options below, Forvo's voices come first, best rated first.
The speaker plays the first voice. Every voice found also gets a chip under the headword, named by its accent (US, UK, AU) or by the person who recorded it, and a Forvo voice is marked "Forvo:". Tap a chip to hear that voice instead; the speaker then plays the one you picked. When nobody has recorded the word, nothing changes: the speaker reads it with the phone's voice as before.
With **Keep recordings for offline use** on (the default), a recording you've played stays on the phone, along with the list of voices the word had, so it plays again with no connection. They take at most 24 MB together, and the ones played longest ago go first. Turn the setting off and they're kept in the app's cache instead, which Android may clear.
The word goes to Wiktionary, and to Forvo when you've set a key, only when you tap the speaker. Turn **Recorded pronunciations** off to keep the speaker on the phone's own voice.
### Translations
Tools / Dictionary / Translations
Under the definitions, a **Translations** section gives the word in other languages: one row per language, with the language's name and a chip for each translation. Tap a chip to type that translation at the cursor. A word in a script other than Latin shows its romanisation in the chip after a dot, as in "বাড়ি · baṛi".
- The translations come from Wiktionary, the same kaikki.org page as the **Wiktionary** source. When that source answered, the page already had them, so nothing more is sent.
- When another source answered (a vocabulary pack, an offline dictionary, the Free Dictionary API or **Wiktionary definitions**), the entry shows at once and the tool asks Wiktionary for the translations after it, with "Looking for translations…" in the meantime. It does this only if **Wiktionary** is on in your [dictionary sources](#dictionary-sources). With it off, the word goes nowhere else, and an entry from another source has no translations.
- They're for the first entry, and they're English to other languages, like the rest of the tool.
- A language Wiktionary has no translation for gets no row. With no translation in any of your languages, the section isn't shown.
**Show translations** is on by default. **Translate into** picks the languages and their order. With nothing picked, it follows your keyboard languages other than English, so a keyboard with English and Bangla shows Bangla. The list offers the languages of the vocabulary packs' translations, your keyboard's languages, and the languages Wiktionary most often has. This list is separate from the Vocabulary tool's **Translation languages**.
### Dictionary sources
The tool asks the first source that is on. When that source doesn't know the word, or can't be reached, it asks the next one, so one service being down doesn't stop the tool. The default order, all on except the last:
1. **Vocabulary packs**: the [vocabulary](https://wmkeyboard.pages.dev/tools/vocabulary/) packs you downloaded, read on the device. Only knows the packs' words, but sends nothing and works offline. The **Vocabulary packs** row under the list opens the screen where you get them.
2. **Wiktionary**: Wiktionary's full entry through kaikki.org, with definitions, examples, IPA and recordings.
3. **Wiktionary definitions**: Wiktionary's own definition endpoint on wiktionary.org. Definitions and examples only, but small and fast.
4. **Free Dictionary API**: dictionaryapi.dev. Last of the online sources because it's often down for days at a time; while it's down, every look-up waits about 8 seconds on it before giving up.
5. **Wiktionary, offline**: the [offline dictionary](#offline-dictionaries) made from Wiktionary, once downloaded.
6. **Open English WordNet**: the [offline dictionary](#offline-dictionaries) made from WordNet, once downloaded.
7. **Bengali–English Wiktionary**: the [offline dictionary](#offline-dictionaries) of Bengali words with their English meanings, once downloaded. Only a Bengali word finds anything in it.
8. **Other languages, offline**: the [offline dictionaries](#other-languages-offline) of other languages' words with their English meanings, whichever you downloaded.
9. **Imported dictionaries**: the [Yomichan and Yomitan dictionaries](#imported-dictionaries) you imported, in any language. Every one that is on and knows the word answers, in their own order.
10. **Urban Dictionary**: slang from urbandictionary.com, up to five definitions a word. Off until you turn it on, because anyone can write a definition and many are crude. At the bottom of the list it only answers for words the other sources don't know, so drag it higher if you want its reading first.
The two offline dictionaries come after the online sources, so with a connection you get Wiktionary's fuller entries and recordings, and without one the tool still answers from the phone. Drag either one higher to have it answer first and send nothing. An update that adds a source puts it at the end of your list, switched on.
Drag a source to change the order, and switch one off to never ask it. With every source off, the panel says so rather than looking anything up. Before version 0.5.12 the Free Dictionary API was the only source, so an outage there broke the tool on every device.
### Offline dictionaries
Tools / Dictionary / Dictionary sources
Press **Wiktionary (offline)** or **Open English WordNet** on the source list to open its screen, where it is downloaded. Two whole English dictionaries you can keep on the phone, so the tool defines ordinary words like "house" with no connection at all, and in the [build with no internet permission](https://wmkeyboard.pages.dev/development/building/#a-build-with-no-internet-permission). Each row has a download button; once the dictionary is in, the row shows its size on the phone and a delete button.
| Dictionary | What it holds | Download | License |
|---|---|---|---|
| **Wiktionary (offline)** | about 65,000 common English words from Wiktionary, slang like "selfie" and "lol" included: three senses per part of speech and six in all, a short example, synonyms, antonyms and IPA; no proper nouns | 5.1 MB | CC BY-SA 4.0 and GFDL |
| **Open English WordNet** | every single English word in Open English WordNet 2024 (about 79,000), every sense, one example, synonyms, antonyms and, where it has one, IPA | 5.0 MB | CC BY 4.0 |
| **Bengali–English Wiktionary** | about 7,500 Bengali words from English Wiktionary with their English meanings, an example with its translation where Wiktionary has one, and the romanized spelling in place of IPA. Each inflected form Wiktionary lists (কলকাতার, বাড়িতে…) opens its word | 0.5 MB | CC BY-SA 4.0 and GFDL |
Both hold single words only. Phrases such as "ice cream" or "look after" are not in them, and neither are hyphenated compounds. A word form finds its headword: "mice" opens *mouse*, "houses" *house*, "running" *run*. Neither has recordings, so their entries have no play button.
Each takes about 14 MB on the phone once installed. A look-up reads a few kilobytes of that file; it does not load the dictionary into memory. Neither file is read before the phone is unlocked for the first time after a restart.
### Other languages, offline
Tools / Dictionary / Dictionary sources / Other languages, offline
One download per language, each made from English Wiktionary's entries for that language: Albanian, Arabic, Basque, Bulgarian, Catalan, Czech, Danish, Dutch, Estonian, Finnish, French, Galician, German, Greek, Hebrew, Hungarian, Icelandic, Italian, Latvian, Lithuanian, Persian, Polish, Portuguese, Romanian, Russian, Slovak, Slovene, Spanish, Swedish, Turkish and Ukrainian. They keep what the Bengali one keeps: English meanings, not definitions in the language itself, up to six senses a word, a short example with its translation, and IPA or a romanized spelling.
They hold single words, and a form Wiktionary gives an entry of its own opens its word: German *Häuser* opens *Haus*, *gehst* opens *gehen*. A form with no entry of its own finds nothing. The packs run from about 0.1 MB (Basque, Slovene) to 5.4 MB (Finnish) to download, and about three times that on the phone.
When a word is in two packs you have, as *hand* is in German and Dutch, the tool shows both and each part of speech says its language. With a connection, the online Wiktionary sources above them answer first; drag this one higher to have it answer first and send nothing.
### Dictionary apps on the phone
A dictionary app you already use can answer too. When the phone has one, an **Open in** chip after the "From" line at the bottom of an entry hands the word to it, and the "No entry" and "not in the dictionaries on this device" messages offer the same chip. It finds apps three ways: Aard 2's own lookup, the ColorDict intent that ColorDict, GoldenDict and most other offline dictionaries answer, and the text selection menu, which is how Kiwix and many others offer themselves. With one app the chip names it, and with several a tap lists them, each app once. Websites you turned on under [Look up in other apps](https://wmkeyboard.pages.dev/tools/look-up/#options) (Takoboto, Shinjikai, Jisho, Wiktionary, or one of your own) are listed after the apps and open on the word in the browser. Nothing is sent anywhere but to the app or site you pick.
The **Import a dictionary** row on each screen takes a `.wmdict.gz` file fetched elsewhere: **Get the files** lists both download links, to open in a browser on any device, and **Import** installs the file. See [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/).
### Imported dictionaries
Tools / Dictionary / Dictionary sources
Press **Imported dictionaries** on the source list to open its screen. The dictionaries made for Yomichan and its successor Yomitan, the pop-up dictionaries that language learners use in their browser. There are hundreds of them: Japanese ones such as JMdict and Jitendex with readings, pitch accent and frequency lists, and Chinese, Korean and Wiktionary-based dictionaries for many other languages. **Get the files** opens the Yomitan wiki's list of dictionaries. Download a dictionary's `.zip` there, then tap **Import** and pick it. Any of the app's other **Import** rows takes it too; see [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/).
**StarDict** dictionaries work here too, in any language pair: pick the `.tar.bz2`, `.tar.gz`, `.tar` or `.zip` a book came in, with its `.ifo`, `.idx` and `.dict` files together inside. If you have the loose files, zip them first. Definitions show as plain text, so a book's HTML styling is dropped along with any pictures and sound. Other spellings in a `.syn` file find the entry too.
- A large dictionary takes some minutes to import, and the import line shows how far it has got. On the phone it takes about as much space as the dictionary's text unpacked, so a big Japanese dictionary needs a few hundred megabytes free while it imports.
- Each imported dictionary gets a row with its number of entries, its size, its languages, its version and its attribution. The switch turns it off without deleting it. The arrows set the order its entries are shown in, and the bin deletes it.
- Importing a dictionary with the same title again replaces it, so a newer version simply goes over the old one.
- In the [source list](#dictionary-sources) they are one source, **Imported dictionaries**, which you can drag above the English sources so that it answers first.
- An inflected Japanese word finds its dictionary form: 食べました opens 食べる, 行かなかった opens 行く, 高くない opens 高い. A word typed in kana finds the entry written in kanji. Other languages are matched in any case.
- They work with no connection, and nothing about a look-up in them leaves the phone.
- Kanji dictionaries, which list one character's readings and strokes rather than words, are not supported. A dictionary with both keeps its words.
- Some dictionaries put pictures in their definitions; those are left out.
### Options
Tools / Dictionary
- **Turn on**: already on.
- **Icon colour**: only shown if colorful tool icons are on globally.
- **Look up the word at the cursor**: on by default. Its description on the settings screen reads "The tool searches the selected word or the current word when it opens". Turn it off and Dictionary always opens on its own empty search field.
- **Show translations**: on by default. Shows the word in other languages under its definitions. See [Translations](#translations) above.
- **Translate into**: the languages of those translations, in order. With none picked, your keyboard languages other than English.
- **Dictionary sources**: a [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag to reorder, switch a source off, remove one, or add it back with **Add a provider**. See [Dictionary sources](#dictionary-sources) above.
- Each source's own screen, opened by pressing its row: **Vocabulary packs** links to the packs, the two offline dictionaries download, delete or import there (see [Offline dictionaries](#offline-dictionaries) above), and **Imported dictionaries** imports, orders and deletes Yomichan and Yomitan dictionaries (see [Imported dictionaries](#imported-dictionaries) above).
- **Look up in other apps**: the [Look up](https://wmkeyboard.pages.dev/tools/look-up/#options) card's selection menu entry, its style for a tapped word, and how it sends words to AnkiDroid.
- **Recorded pronunciations**: on by default. The speaker plays people's recordings of the word before the phone's voice. See [Recorded pronunciations](#recorded-pronunciations) above. With it on, two more rows show at the Advanced level: **Keep recordings for offline use** (on by default) and **Forvo API key** (empty by default). The key is never put in a backup.
- **Servers** (F-Droid build only): on the screens of Wiktionary, Wiktionary definitions and the Free Dictionary API, the address each is called at, for a mirror or a copy you host yourself.
The settings page's caption: "Definitions come from the sources below, in their order. The keyboard sends the word that you look up to an online source only when you use the tool." [Power saving](https://wmkeyboard.pages.dev/typing/power-saving/)'s "Background network" toggle overrides this row. While that toggle is on, auto-lookup is forced off whatever you've set here. A look-up nobody explicitly asked for is exactly what it exists to stop.
Web search, image search and Wikipedia are the **Web search** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the three tools leave the toolbar, the toolbox and the Tools list until the bundle is switched back on; your search services stay set.
## Details & edge cases
- **Only web search needs to be unlocked.** It needs one usable provider: a Brave key, yours or the one baked into this build, a Tavily key of your own, or a SearXNG instance. Wikipedia and Dictionary work out of the box against free, keyless public APIs.
- **Default hardware letters:** W for web search, I for its Images tab, D for dictionary. Wikipedia has no letter of its own out of the box, so reach it through the toolbox (T), or [rebind a letter to it yourself](https://wmkeyboard.pages.dev/reference/shortcuts/).
- **Discoverable by typing.** Typing "search", "images", "wiki"/"wikipedia", or "define"/"dict" as the last word before your cursor offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) that opens the matching tool ("images" opens the Images tab). The keyword list is editable per tool, and only shows while that tool is enabled.
- **None of these are build-gated.** Every build behaves identically across all the tools here.
- **Attribution.** Brave Search and Tavily results, Wikipedia's article text, Wiktionary's entries, and the Free Dictionary API's definitions are all credited under "Online services" on the app's About → Open-source licences screen, alongside the terms each service is used under. Wikipedia's and Wiktionary's text is CC BY-SA, which keeps its own license independent of the app's. The offline dictionaries carry their sources' licenses with them: Wiktionary's CC BY-SA 4.0 and GFDL, and Open English WordNet's CC BY 4.0. An imported dictionary's own attribution line is shown on its row.
The [AI tool](https://wmkeyboard.pages.dev/tools/ai/#letting-the-model-look-things-up) can run searches of its own through this same backend, when you let it: it writes the query, reads the results and answers from them, and each one counts against your quota like a search you ran yourself.
For what leaves your device and when, across every network tool, see [network policy](https://wmkeyboard.pages.dev/privacy/network/). For the GIF and sticker tools' different, search-as-you-type behavior, see [GIFs](https://wmkeyboard.pages.dev/emoji/gifs/).
---
# Send files
URL: https://wmkeyboard.pages.dev/tools/send/
Section: Tools
Summary: Send a file to anyone as an encrypted link that expires, or straight to a phone next to you, and receive both kinds without leaving the app you are typing in.
The **Send files** tool moves a file out of the keyboard in two ways. **Link** encrypts the file on your phone, uploads it to a [Send](https://github.com/timvisee/send) server (the maintained fork of Firefox Send) and gives you a link with an expiry, a download limit and an optional password. **Nearby** hands the file to a phone or computer next to you over Wi-Fi, with no server at all. It receives both kinds too: a Send link in a chat gets a chip that downloads and decrypts the file, and a device nearby can send a file to you.
## The panel
The panel is a tray. Along the top, under **TO**, one row of cards names every place a file can go: **Link** (the Send server, with its host and the largest upload it takes), each phone or computer the Nearby engine knows, **Browser** (a page any browser on this Wi-Fi can open), **Direct** (a Wi-Fi this phone opens for another phone), **Scan code** where the build has a scanner, and a last **Nearby** card that opens the radar. The card you tap is filled and ringed; it is where the Send circle sends. Under the cards sit the four sources, **Files**, **Gallery**, **Clipboard** and **Field**, and the rest of the panel is the trough the files land in.
The header has **History**, which lists every link this phone made and every file it received, **Receive**, which takes a pasted Send link, and the tool's settings.
## Sending a link
1. Pick what to send: **Files** opens the keyboard's [file manager](https://wmkeyboard.pages.dev/tools/files/) (Android's document picker in the Play Store build), **Gallery** opens the [Gallery](https://wmkeyboard.pages.dev/tools/gallery/) tool to pick photos and videos, **Clipboard** takes what you copied (a file, a picture, or text saved as a file), and **Field** saves the text in the field you are typing in. Each file drops into the trough as a tile; the tiles fan into a pile, and a tap on the pile spreads them out so one can be removed.
2. With **Link** chosen, the trough shows how much of the server's cap the pile takes and three chips for the link's limits: how long it lives, how many downloads it allows and a password. Each chip picks in place; the choices are what the chosen server allows. **Generate**, in the password box, makes a twelve-character one you can read out.
3. Press the **Send** circle. The panel turns into the sending face: the Link card sits in the middle of a ring that fills as the file is encrypted and uploaded in one pass, the pile flies into it, and the percent, the bytes sent, the speed and the time left are written under it. **Cancel** is in the header. When it is done the link is typed into the field, or copied, or shown with its QR code, whichever you chose under **After the upload**. Hold the circle instead to pick for this one upload.
The file is encrypted before it leaves the phone, with a key that travels only in the link, after the `#`. The server stores the ciphertext, its size and when it arrived. It never sees the key, the file or even its name, which is encrypted too. Anyone with the link can download the file until it expires or the downloads run out; a password is a second lock the server checks on top.
A finished link is a receipt: the QR code at the left (tap it for a big one), the host over the link's hash, and chips for when it expires, how many downloads are used and the password, which shows its letters when you tap the lock. Under it one bar holds **Insert link** (or **Copy** when no field is open) with **Copy**, **Share** and the QR code as round buttons, and **Manage** opens the download count and **Delete from the server**. **RECENT** shows the link before this one, as a door into History, and **New** in the header starts over.
## Receiving a link
Paste or type a Send link in any field and a chip appears on the suggestion strip: **Receive the file**. Copy a link in another app and the same chip is waiting when you come back to the keyboard. Tap it and the panel asks the server about the file, shows its name, size and expiry, warns you when yours is the last download allowed, asks for the password if there is one, and downloads and decrypts it into **Downloads › WM Keyboard**. A picture can go straight into the chat field from there; anything else opens or shares from the panel.
You can also press **Receive** in the panel's header and paste the link into its box.
## Nearby
Two other apps' devices show up here as well, each as a card in the TO row. **LocalSend** apps on the same Wi-Fi appear by themselves, with no server and no account, while the panel is on Nearby. **PairDrop** pages on the same network appear once you turn on **PairDrop** in the tool's settings: it is off by default because it meets other devices through a PairDrop server (`pairdrop.net` unless you enter your own), which sees this phone's address while the panel is on Nearby. Sending is the same as to any other card. A file coming to you asks first, with **Accept** and **Reject** in the tray, and is refused after a minute with no answer. Neither runs with the panel closed, except to finish a transfer already under way.
PairDrop's files go directly between the devices through WebRTC, a library of about 12 MB that the tray offers to download the first time. The F-Droid build has no such library, so there PairDrop works only with a server that relays files itself. PairDrop's paired devices and public rooms aren't supported, only devices on one network.
**Nearby** uses the same engine as the [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/) tool, so it reaches phones and computers running WM Keyboard, KDE Connect, GSConnect or Valent on the same Wi-Fi. Every device it knows is a card in the TO row, with **Connected** in green, **Not in reach**, or **Pair** for one it has not met; tap the card and the Send circle sends the pile to that device. Pairing happens once, by comparing the eight-character code the trough shows with the one on the other screen, with **Accept** and **Reject** for a device that asks first.
The **Nearby** card at the end of the row opens the radar: a sweep turning over three rings, the pile in the middle, and the devices placed around it, the ones in reach on the near ring and the rest dim on the far one. The browser page and the direct Wi-Fi join the radar as nodes while they are up. Tap a device to send the pile to it (or to pair, when it is new), tap the middle to go back to the tray, and use **Refresh** in the footer to look again. A send wears the same sending face as a link, with the device's card in the ring; **Hide** in its header puts it away so the tray can be used meanwhile. Files sent to you land in **Downloads › WM Keyboard**; the transfers either way, under way and finished, are a chip in the trough and in the radar's footer that opens the list, with **Clear finished**.
### In a browser
The other device does not need WM Keyboard, or any app. Pick the **Browser** card and press **Start the page** (or the Send circle): the phone starts a small web page on its Wi-Fi address, then shows a QR code. Whoever scans it on the same Wi-Fi gets a page in their browser with each file you picked and a **Download** button beside it. With **Accept files** on, the page also takes files back: choose or drop them there and they land in **Downloads › WM Keyboard** on your phone. Files you add or remove in the panel show up on the page within a few seconds.
While the page is up, the trough holds its controls: **Show the code**, **Accept files**, **Copy** for the address, and **Stop**. When the other device is on a different network, or the Wi-Fi keeps its devices apart (common on guest and office networks), press **Use a hotspot**. The phone opens a Wi-Fi of its own, the same way the direct link below does, and the code sheet shows two codes: the first joins that Wi-Fi from a phone camera, the second opens the page. A laptop joins by typing the Wi-Fi name and password shown beside the code.
Nothing goes through the internet and no outside server is involved; the page comes straight from your phone. The address carries a random 128-bit token, so a device on the same Wi-Fi that did not get the code finds nothing. The page is plain HTTP, since browsers refuse a certificate a phone made for itself, so use it on networks you trust. It stops when you press **Stop** or after 10 minutes with nobody on it.
### With no Wi-Fi between you
Two phones with WM Keyboard can link directly. On the receiving phone pick the **Direct** card and press **Receive here**: the phone opens a Wi-Fi of its own (a Wi-Fi Direct group, or a local hotspot where the phone cannot do that) and shows a QR code. On the sending phone tap the **Scan code** card google and point the camera at it. The code carries the Wi-Fi's name and password, the receiver's address and a one-time pairing token, so the sender joins the Wi-Fi, finds the receiver and pairs with it in one go; then the files go. **Disconnect** takes the Wi-Fi down again, unless **Keep a direct link up** is on.
Android asks for the **Nearby devices** permission before an app may open or join a Wi-Fi this way (on Android 12 and older, the same thing sits behind the location permission). The tool asks only when you press one of those two buttons, reads no position, and needs nothing at all for devices that are already on the same Wi-Fi.
## From other apps
With **Show in other apps' share sheets** on, **Send with WM Keyboard** appears beside the apps you share to. Share a file from anywhere, pick the limits and a password, and the link is copied for you, with its QR code on the card. Nearby works from there too, to a paired device in reach. A share that names a file inside WM Keyboard's own storage is ignored, so another app cannot use it to upload the keyboard's private files.
## Choosing a server
The **Link** card names the instance uploads go to and the largest upload it takes. Tap it again while it is chosen, or hold it, for the list: the servers from the [community list](https://github.com/timvisee/send-instances) with their size, time and download limits, each with a dot that says whether it is answering, and **Add your own** for a server you run or trust. The default is the one the maintainer of Send runs. Every public instance is run by volunteers with no promises; what they see of your file is its size and its arrival time, nothing else.
If the pile is too big for the chosen server, the trough says so in red and offers the instances with room for it.
## Settings
Tools / Send files
Send files is the **Send files** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Switch the bundle off and the tool, the link detection and the history screen leave together; the history stays on the phone.
| Setting | What it does |
|---|---|
| Instance | The server uploads go to, and the list of your own. |
| Link expires after | The default life of a new link: 5 minutes to 7 days, capped by the server. |
| Downloads allowed | The default download count of a new link. |
| Password | Off, ask on every upload, or generate one every time. |
| After the upload | Insert the link, copy it, show it, or ask. |
| Offer to receive links | The strip chip for a Send link typed, pasted or copied. |
| Open after download | A single received file opens as soon as it is saved. |
| Send to devices nearby | The device cards, the radar, the direct link and the browser page; off, one card in the TO row leads here. |
| Keep a direct link up | Stay on the other phone's Wi-Fi after a transfer. |
| Show in other apps' share sheets | The **Send with WM Keyboard** entry. |
| Keep a history | The links you made and the files you received, for the History sheet and screen. |
Data saving has a row for this tool: a whole file over mobile data asks first, like a download.
## Privacy
The link is the key. The server you upload to receives only ciphertext; the server a link points at learns that you fetched it, which is unavoidable, and nothing about you beyond your address. Links you made are kept on the phone, with the owner token that can delete them, in a folder that is left out of every backup. Nothing about any of this goes to the developer. The network activity log lists every request the tool makes under **Send files**.
---
# Offline voice (Sherpa)
URL: https://wmkeyboard.pages.dev/tools/sherpa/
Section: Tools
Summary: Streaming dictation on your phone in 13 languages, Bangla among them, with words that appear while you speak.
Offline Sherpa runs [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx), k2-fsa's speech toolkit, on your phone. Its models are streaming Zipformer transducers: they decode while you speak, so the words go into the field as you say them, the way the phone's own recognizer does it, and nothing waits for the end of the phrase. Each language has its own model, and one covers eight at once. Once a model is downloaded, no audio leaves the phone. The same runtime also reads text aloud: see [Read back and offline voices](https://wmkeyboard.pages.dev/tools/read-back/). A language with no model on the phone uses the system recognizer instead.
## Setting it up
Voice typing
1. Add **Sherpa** to the **Recognition engine** list and move it to the top, or pick it for one language under **Engine for each language**.
2. Press **Sherpa** on the list to open its screen, which holds the **Sherpa models** group. The first time, a banner offers ONNX Runtime, the engine Sherpa runs on: a one-time download of about 33 MB (arm64), from Google Play on the Play build and from Maven Central on the GitHub and F-Droid builds; the offline build carries it already. The F-Droid build asks you to turn on **Download feature libraries** first.
3. Download the model for each language you dictate in. Each one comes from Hugging Face at a fixed revision, every file checked against its published SHA-256.
| Model | Languages | Download | Licence |
|---|---|---|---|
| Zipformer English Small | English | 46 MB | Apache-2.0 |
| Zipformer English | English | 75 MB | Apache-2.0 |
| Vosk Bengali | Bangla | 96 MB | Apache-2.0 |
| Vosk Russian Small | Russian | 30 MB | Apache-2.0 |
| Zipformer Korean | Korean | 134 MB | Apache-2.0 |
| Zipformer French | French | 130 MB | Apache-2.0 |
| Kroko German | German | 73 MB | CC-BY-SA-4.0 |
| Kroko Spanish | Spanish | 158 MB | CC-BY-SA-4.0 |
| Zipformer Chinese | Mandarin | 169 MB | Apache-2.0 |
| Zipformer Chinese Small | Mandarin | 27 MB | Apache-2.0 |
| Zipformer Chinese + English | Mandarin and English in one model | 200 MB | Apache-2.0 |
| Paraformer Chinese + English | Mandarin and English in one model; can't listen for your words | 239 MB | Apache-2.0 |
| Zipformer ar en id ja ru th vi zh | Arabic, English, Indonesian, Japanese, Russian, Thai, Vietnamese, Mandarin | 341 MB | Apache-2.0 |
| Punctuation (English) | punctuation and capitals for English; see below | 8 MB | |
The sizes include Silero VAD (2 MB), a small voice detector every model carries so that deleting one never breaks another.
## How it works
While you speak, the words go into the field as underlined composing text, rewritten as they grow, and the line under the microphone shows them too. When the phrase ends, the finished text replaces them and goes through the same spacing, spoken punctuation, casing and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)).
**Where a phrase ends.** With **Stop after a pause** set, Silero listens for you to stop talking and ends the phrase after that long a pause; with **Keep listening** on, the next phrase starts at once. Silero hears speech rather than loudness, so a fan or traffic does not keep the phrase open the way it can for a loudness threshold. With the setting off, the phrase runs until you press the microphone or two minutes pass. **Phrase end detector** on the Voice typing screen swaps Silero for FireRedVAD, plain loudness or a model of your own, and **Smart Turn** ends the phrase sooner when you sound finished; see [Hearing the end of a phrase](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase).
**Which model.** When two downloaded models know the language, the larger one dictates. The model list marks each one in use, and **Engine for each language** can pin a language to a particular model.
**Punctuation.** Most of these models write plain words with no punctuation, and the English ones trained on audiobooks spell in capitals. Sherpa turns capitals into ordinary lower case. With [Add punctuation and capitals](https://wmkeyboard.pages.dev/tools/voice/#adding-punctuation-and-capitals) on, the default, a finished phrase gets full stops, commas, question marks and sentence capitals from a punctuation model for 88 languages, downloaded from that setting's row on the Voice typing screen. **Punctuation (English)** here does the same for English on its own, and with both on the phone an English phrase takes the multilingual model's marks and this one's capitals for names. The German, Spanish, Korean and eight-language models punctuate by themselves and are left as they are. Spoken punctuation ("comma", "full stop") works either way.
## Words to listen for
Voice typing / Words to listen for
Sherpa listens for the same [words to listen for](https://wmkeyboard.pages.dev/tools/voice/#words-to-listen-for) the system recognizer gets: the names and terms you typed into **More words to listen for**, then your own words from the dictionary when **Listen for my words** is on. They go to the model as hotwords. While it decodes, a guess that spells one of your words gets a small bonus for each piece of the word it has spelled so far, and the bonus is taken back if the guess turns away before the word is complete. A name the model was close to hearing then comes out the way you spell it.
The model can only write a word in the pieces it was trained on, so each word is first spelled in that model's pieces. Words it has no pieces for are left out: German umlauts for the English models, Bangla letters for any model but the Bengali one, and characters the Chinese model has never seen, which is most rare characters in Chinese names. The eight-language model takes Chinese characters and plain Latin words, but not words in Arabic, Cyrillic, Thai or Vietnamese letters. Words under three letters are left out too, so a stray initial does not turn up wherever a word starts with it.
**What it costs.** Hotwords need a slower search: Sherpa keeps four guesses open instead of one. The search only runs when some of your words can be spelled by the model, so with an empty list, or **Listen for my words** off and nothing typed in, Sherpa decodes the quicker way. Switching between the two loads the model again, once.
**What to expect.** Hotwords help a name the model nearly heard; they cannot teach it a sound it has never heard. On synthetic test sentences with 15 rare names (Siobhan, Okonkwo, Ljubljana, Kubernetes and the like) read by five voices, the English model got the name right 1 time in 75 without the list and 9 to 11 times with it, whether the list held just those names or 195 words. Ordinary sentences came out as before: no word from the list was ever typed into a sentence that did not have it.
## Reduce background noise
Voice typing / Reduce background noise
Not Sherpa's own, but worth knowing about here: **Reduce background noise** cleans the microphone's audio before any offline engine hears it, Sherpa included. **Light** runs [RNNoise](https://github.com/xiph/rnnoise), a tiny network that costs about 10 ms; it comes with the offline voice engines' shared libraries, so on a phone that has only Sherpa so far it fetches those (about 12 MB) at the next phrase. **Strong** runs [DPDFNet](https://github.com/ceva-ip/DPDFNet), a DeepFilterNet model, on the ONNX Runtime Sherpa already has, after a 9 MB model download. Strong is the one that helps recognition: on noisy test recordings Sherpa's English model went from 33% of words wrong to 31% with it, and to 40% with Light. The RNNoise 0.2 model Light runs now does a little better than that but still costs words: on a second set made the same way it took Sherpa's English model from 29% to 35%, where 0.1.1 gave 37%. Off by default; see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/#continuous-mode) for the details.
## Your own models
Voice typing / Recognition engine / Sherpa / Your own models
The list above covers a few languages. To use another [streaming transducer](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-transducer/index.html) or [streaming Paraformer](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-paraformer/index.html) model, download its files and press **Import a model**. Pick the encoder, the decoder, `tokens.txt` and a transducer's joiner together, then give it a name and its language codes, such as `tr` or `en, de`. Where a repository has both a full and an `int8` copy of a part, the `int8` one is kept.
- It's used before the listed models for its languages, and a language it adds becomes one Sherpa can dictate. Delete it to go back.
- It also needs `silero_vad.onnx`, which hears where a phrase ends. Pick it with the rest, or have any listed model downloaded and its copy is used.
- Only streaming transducers and streaming Paraformers work. Files for CTC or a whole-file (offline) model are refused with a note, because the engine can't run them this way. A Paraformer can't follow the words to listen for.
## Sherpa, Whisper or the others
For Bangla, Sherpa is the only offline engine that shows the words while you speak; [Whisper](https://wmkeyboard.pages.dev/tools/whisper/) understands more accents and rarer words but types the phrase when you stop. For English, [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) streams too and punctuates on its own, [Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/) is the smallest, and Whisper (on whisper.cpp) takes your own words as a prompt, which it reads more freely than Sherpa's hotwords. Sherpa is the one to pick for live text in a language the others do not stream in. For a language none of them knows, [Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/) runs on the same runtime and covers about 380 keyboard languages, a phrase at a time.
## Details & edge cases
**Several languages at once.** A dictation that listens for more than one language at a time needs one Sherpa model that knows all of them, which only the eight-language model can be. Otherwise the panel asks for a model.
**The licences.** sherpa-onnx is Apache-2.0, ONNX Runtime is Microsoft's under MIT, Silero VAD is MIT, FireRedVAD is FireRedTeam's under Apache-2.0 and Smart Turn is pipecat's under BSD-2-Clause. The Kroko models are Banafo's community models under CC-BY-SA-4.0, the Vosk models Alpha Cephei's under Apache-2.0. All of them are credited in the app's About screen.
**Where the runtime comes from.** The app never carries a copy of ONNX Runtime rebuilt by anyone but Microsoft: the Play build gets it from Play, the offline build carries Microsoft's own file inside the APK, and the GitHub and F-Droid builds read Microsoft's file straight out of its package on Maven Central, checked against a SHA-256 built into the app. See [Network activity](https://wmkeyboard.pages.dev/privacy/network/).
---
# Snippets & text expansion
URL: https://wmkeyboard.pages.dev/tools/snippets/
Section: Tools
Summary: Reusable text blocks with variables, inserted from a panel or expanded by keyword.
Snippets are reusable pieces of text (a mailing address, a canned reply, a sign-off) that you save once and reuse everywhere you type. Insert one with a tap from the panel, or type its trigger word and let it expand on its own as you finish typing.
tools/snippets-panel — The snippets panel: saved snippets as two-column cards, ready to insert with a tap.
## Opening the panel
Open **Snippets** from the toolbar if you've pinned it there, or from the toolbox. Snippets isn't pinned by default on any device. A phone starts with Emoji, Clipboard and Settings, and a tablet starts with five or seven pins, none of them Snippets. So the first few times you'll likely reach it through the toolbox, or by typing its [keyword](#options). On a hardware keyboard, double-tap Ctrl to arm the leader key, then press S (see [hardware shortcuts](https://wmkeyboard.pages.dev/reference/shortcuts/) for the full letter-to-tool map).
Each snippet shows as a card in a two-column grid: its label in bold on top, the snippet text underneath. That text is the raw template rather than an expanded preview, so a card containing `{date}` shows the literal token instead of today's date. Tap a card to insert it. A card with more than one thing to insert shows a count in a filled circle beside its name, so it can't be mistaken for part of a name like `2`: press and hold it and the panel lists everything it offers, its own expansions first and then the snippets it links to, with a **›** on any of those that has more behind it. Anything you were mid-typing gets committed first, so a half-finished word can't get swallowed into the snippet. With nothing saved yet, the panel shows the same variable hint plus a shortcut straight to Snippets settings.
If you've made any [folders](#folders), the panel opens on them instead: folder tiles first, then whatever you've filed in no folder at all, in the same grid. Tap a folder to go inside it, and the header takes the folder's name while you're there. Back (the header arrow, the system Back gesture, or Esc on a hardware keyboard) climbs out of the folder before it closes the panel. Press and hold a folder tile to switch its automatic expansion on or off without leaving the app. With no folders anywhere the panel is exactly the flat grid it always was.
The panel re-reads the snippet file every time you open it. Settings and the keyboard share that file, so an edit you make in Settings shows up the next time you switch to this panel, not while it's already open.
The magnifier in the panel's header opens a search. The keys come back under a shorter panel that keeps a couple of rows of matches in view, with a bar on top that [drags it taller](https://wmkeyboard.pages.dev/tools/overview/#making-a-panel-over-the-keys-taller) so more matches show, and as you type the grid narrows to the snippets whose label, text or trigger contains what you typed, from every folder at once. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, the snippets that mean what you typed follow them. The × in the search, or Back, closes it and brings the full list and the folders back. **Panel columns** under [Options](#options) sets how many tiles sit side by side, two unless you change it.
## Auto-expanding as you type
Give a snippet a trigger and it expands on its own. Type the trigger by itself, then finish it with a space, punctuation or Enter. What you typed is replaced with the expanded snippet instead of being committed literally. Matching is case-insensitive and exact: `omw` matches `OMW` but not `omww`, with no partial or prefix matching. You can also glide a trigger: swipe `omw` and the snippet is typed as soon as you lift your finger, and one backspace puts the trigger back. While you swipe, the floating word, the suggestion strip and the words offered when you hold still all show the trigger with its expansion beside it, like `omw = on my way`. That works for a one-word trigger that doesn't [ask first](#ask-before-it-expands); a trigger with punctuation or a space in it still has to be typed. To match something that varies, like a name or a time or a phrase, use a [pattern trigger](#pattern-triggers) instead. To skip the space altogether, set the snippet to [expand immediately](#expanding-immediately).
### Triggers with punctuation or spaces in them
A trigger doesn't have to be a single bare word. It can lead with punctuation, as in `:shrug`, `;ty` or `//date`. It can hold spaces, as in `gr db` or `on my way`. It can do both, as in `:gr db`. Type the whole thing, finish it the usual way, and it expands like any other trigger.
The punctuation form is the convention almost every [Espanso](https://wmkeyboard.pages.dev/reference/importing-from-espanso/) package uses. The spaces form is what makes a trigger a phrase rather than a code you have to memorise: `gr db` for `./gradlew assembledebug` reads like the command it stands for.
Both work the same way under the hood, and that explains the one rule below. The keyboard's word buffer only ever holds letters, digits, apostrophes and a hyphen inside a word (as in `e-mail`), so by the time you finish typing `:shrug` the colon has already gone into the field, and by the time you finish `gr db` so has "gr ". Only the last word is in the buffer. So the keyboard looks that word up, and only when it could finish some trigger does it read the short run of characters in front of it to check the rest. A snippet list with no trigger of this kind in it costs nothing at all.
A trigger can also **end in a symbol**: `js:`, `->`, `gr db:`. Those fire the moment the symbol is typed, with no space or Enter after it, because the symbol itself is the cue: the keyboard checks whether a trigger ends in that symbol at all, and only then reads the short run in front of it off the field to confirm the rest. `js:` typed as `JS:` still matches, and a trigger that starts with a letter needs a word boundary in front of it, as every trigger does, so `xjs:` leaves `js:` alone. Punctuation in the middle is fine too, so `a-b`, `m:f-u` and `:a.b` all work, whether or not the hyphen joined the word you were typing. The one shape that can't be watched for is a trigger ending in a space.
Two smaller ones:
- The longest match wins. `::x` beats `:x` if you have both, and `gr db` beats a plain `db`.
- A trigger that starts with a letter or a digit needs a word boundary in front of it, the same as a plain trigger does. `gr db` fires after a space or a punctuation mark, but not in the middle of "xgr db". A trigger that starts with punctuation doesn't need one, because the punctuation is the boundary.
Everything in front of the last word can run to 32 characters. That's room for a couple of words, not a sentence.
### Expanding immediately
A word trigger can also fire the moment you type its last character, with no space, punctuation or Enter after it. Turn on **Expand immediately** on the snippet's editor screen and `btw` becomes "by the way" as the `w` lands. It works for every shape of word trigger: `:sig`, `;;addr` and `gr db` fire on their last letter too. This is how [Espanso](https://wmkeyboard.pages.dev/reference/importing-from-espanso/#when-a-trigger-fires) fires a match unless told otherwise, and an imported package keeps that.
By default the trigger still needs a word boundary in front of it, so `btw` leaves "subtweet" alone. Turn on **Also inside words**, under it, and it fires wherever the text ends with the trigger, even partway through a word. A trigger that starts with punctuation never needed the boundary.
Two ways to keep the trigger as you typed it:
- **Backspace once** straight after it expands. The trigger comes back, and it stays: a trigger set to expand immediately isn't looked at again when the word ends, so the space after it leaves it alone.
- **Type the escape character first.** A backslash in front of the trigger, as in `\btw`, stops it firing, and the backslash itself is taken out, leaving `btw`. Choose a different character, or turn the escape off, with **Escape character** under [Options](#options).
The rest works as it does for any trigger: capitals carry over with **Match the trigger's capitals**, a [glide](#auto-expanding-as-you-type) of a one-word trigger expands when you lift, and [several expansions](#several-expansions) put their chips on the bar. Nothing fires while a transliterating or converting layout is spelling the word (Pinyin, Avro, Vietnamese and the like), on a keypad layout that guesses words from key presses, or in a password field unless you've turned on **Expand in password fields**. A snippet set to [ask first](#ask-before-it-expands) has nothing to fire, so the switch is hidden for it, and [patterns](#pattern-triggers) always wait for the space.
A trigger that [ends in a symbol](#triggers-with-punctuation-or-spaces-in-them), like `js:`, already fires the moment that symbol is typed, with or without this switch.
### Several triggers
One snippet can answer to as many triggers as you like. The **Triggers** field on the editor screen takes one at a time: type a trigger, press the **+** button at the end of the box, and it becomes a small chip you can tap to edit or press the ✕ to remove. Enter does the same thing, and so does a comma, so pasting "brb, omw, ttyl" gives you three chips. You never have to save and reopen the snippet between triggers: add all four, then press Save once. Spaces don't split any more, since a trigger is allowed to contain them, and any run of whitespace inside one is saved as a single space. Each trigger fires the same snippet in exactly the same way. A spelling that repeats another is quietly dropped, and the app keeps the first 256.
### Matching capitals
**Match the trigger's capitals** carries how you typed the trigger into what comes out. Off (the default), `omw` and `OMW` both expand to whatever the snippet says. On:
- typing it in **capitals** gives you the expansion in capitals: `ALH` gives "ALTHOUGH"
- typing it with **one leading capital** gives you the style you picked: **First letter** (`Alh` gives "Although"), **Every word**, or **All capitals**
- typing it in **lower case** changes nothing
It matters for snippets that fix ordinary words rather than expand codes. A pack that turns "a bas" into "à bas" would otherwise mangle "A Bas" at the start of a sentence. Espanso calls this `propagate_case`, and packages that correct spellings lean on it heavily.
Trigger expansion is skipped while you're mid-transliteration or mid-conversion. For phonetic composers like Bangla-style transliteration, or Pinyin/Japanese-style conversion, the composing buffer holds an input spelling rather than the literal word you meant as a trigger, so expansion would misfire. On Pinyin and the other conversion layouts a reading that spells a trigger offers the snippet as a chip at the end of the strip instead, and a tap on it drops the reading. **Shortcuts while converting** turns that off.
This is a different mechanism from the **Keyword shortcut** field on the Snippets settings screen (see [Options](#options)), which is a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) that offers to *open the Snippets panel* when you type "snippet" or "snippets." It has nothing to do with any individual snippet's own trigger word.
### Pattern triggers
A word trigger matches one fixed word. A **pattern** trigger matches the last few words before the cursor with a regular expression, and hands whatever it captured to the snippet's text. One snippet then covers every name, every time, every phrase:
| Pattern | Snippet text | You type | You get |
|---|---|---|---|
| `^hello (.+)$` | `Hello, $1! Nice to meet you.` | `hello John` + space | Hello, John! Nice to meet you. |
| `^thanks (.+)$` | `Dear $1,`
`Thank you for your message…` | `thanks Sarah` + space | The whole letter, addressed to Sarah |
| `^/gh (\S+)$` | `https://github.com/$1` | `/gh wasi-master` + space | https://github.com/wasi-master |
| `\s+([.,?!:;])` | `$1` | `hi` + space + `.` | hi. |
Each pair of brackets in the pattern is a capture, and `$1` to `$9` put those captures into the text. `$0` is everything the pattern matched. Four transforms are available when you want the capture reshaped: `${1:upper}`, `${1:lower}`, `${1:title}` and `${1:trim}`. A literal dollar sign is `$$`.
Some details:
- **Matching is case-insensitive** by default, so `HELLO John` works too. Start the pattern with `(?-i)` if you want it to care.
- **`^` and `$` are optional.** The pattern always has to fill the whole span it's matched against, so `hello (.+)` behaves exactly like `^hello (.+)$`. Most people write the anchors anyway, and the examples here follow suit.
- **Words to match** caps how far back a pattern can reach: three words by default, up to eight. It's there because names are short. With a cap of three, `^hello (.+)$` can turn "hello John Smith" into a greeting but will leave a whole sentence alone.
- **A pattern never reaches across a line break**, so a snippet can't swallow the paragraph above the one you're writing.
- **The shortest match wins.** If two patterns both fit, the one that eats fewer of your words is the one that runs. A plain word trigger always beats a pattern.
- **A pattern can start on the space in front of a word.** `\s+([.,?!:;])` with the text `$1` pulls a stray space back out from in front of a full stop or a comma. The space and the word after it count as one word for the words-to-match cap.
- **Backspace takes it back.** One press right after an expansion restores exactly what you typed, spacing and capitals included, and the pattern won't fire again on that same text.
Patterns run on every space you type, so the keyboard is careful about what they cost. Start a pattern with a plain word (`^hello `, `^/gh `) and it can be skipped entirely unless that word is actually behind your cursor, which is nearly always. A pattern that starts with something variable instead, like `^(.+)$` or `^\d+`, has to be tried after every word. The editor says so when you write one. A pattern that turns out to be pathologically slow is stopped rather than allowed to stall your typing.
Everything else about word triggers applies here too: patterns are skipped mid-transliteration and mid-conversion, and they never run in password fields or in fields that ask for no suggestions.
A pattern either expands on its own or asks first, never both. Which one it is decides when it's looked for at all: the expanding kind is checked on the space, Enter or punctuation that ends a word, the asking kind on every keystroke (see below). Punctuation gets a second look once the mark itself is in the field, so a pattern about the mark (the space fixer above) sees it. That second look is skipped when the first one already expanded something.
### Ask before it expands
Every snippet has an **Ask before it expands** switch on its editor screen. Turn it on and the trigger stops rewriting your text. Instead, the moment it matches, the snippet appears as a chip on the suggestion bar showing the text it would insert, and nothing goes into the field until you tap it. Keep typing and the chip goes away, leaving what you typed exactly as you typed it.
The chip arrives as soon as the trigger is typed. There's no need to press space first, and that goes for both kinds of trigger:
- A **word** trigger offers itself while you're still typing the word. Type `brb` and the chip is there before you reach the space bar. Press space instead of tapping and you get a literal "brb."
- A **pattern** offers itself mid-phrase, and keeps up as you type. With `^hello (.+)$`, typing "hello Jo" already shows a chip reading "Hello, Jo!". The next letter re-reads it as "Hello, Jon!", and so on. The offer survives the space that ends the word, so you can finish the phrase and then decide.
Tapping the chip swaps the words it matched for the expansion and keeps any trailing space. What the chip shows is exactly what goes in.
The chip shares the suggestion bar with your word candidates, so turning an offer down never costs you the predictions you were about to use. Backspace takes an accepted expansion back the same way it does an automatic one. The offer isn't remembered anywhere. It's worked out fresh from the text in front of the cursor, so moving the cursor, editing from elsewhere, or carrying on typing all stop it matching, and the chip goes.
One cost: an asking *pattern* is checked on every keystroke rather than once a word, so it reads a little of the text in front of your cursor each time you type. The check is debounced and runs off the typing path, and it doesn't happen at all unless you have an asking pattern installed. Plain word triggers are free either way, because matching one is a dictionary lookup on the word you're typing, with nothing to read.
This is what the **Pattern Replies** pack in the [addon repository](https://wmkeyboard.pages.dev/addons/overview/) uses. Canned replies written by somebody else are exactly the case where silent rewriting is unwelcome: "hello John" turning itself into a paragraph you didn't ask for is a worse outcome than one extra tap. Every pack's preview says which way each of its snippets goes, so you can see it before installing.
## Several expansions
A snippet doesn't have to say one thing. Add a second expansion and typing its trigger offers you both, as chips on the suggestion bar, and you pick the one you meant.
The **Expansions** section on the editor screen is a list. The first row is the default, marked with a star: it's what a tap in the snippets panel inserts, and what any export that has no way to say "several" carries. The arrows move a row up or down, so promoting an expansion to the top is how you change the default. There's no separate button for it.
What happens when the trigger fires is up to you, in **Settings → Text expansion → When a snippet has several expansions**:
- **Chips only** (the default) inserts nothing. Every expansion sits on the suggestion bar and the text stays exactly as you typed it until you press one. Your word candidates keep working beside them, so ignoring the offer costs nothing.
- **Insert first** puts the default in straight away, the way a one-expansion snippet always has, and offers the rest as chips. Pressing one swaps what was just inserted for it, and you can keep swapping. Backspace still takes the whole thing back to the trigger you typed.
Any one snippet can overrule that. Once it has more than one expansion, its editor grows a row of its own with the same two choices plus **App setting**, which is what it starts on.
Each expansion is a row of its own in the editor, under **Expansions**. Press the row, or its pencil, to open the text for editing; the arrows move it up and down the list, where the top one is the default, and the bin removes it (a snippet keeps at least one). **Add expansion** opens a fresh row.
The bar scrolls sideways when there are more chips than fit. Nothing is hidden behind a "more" button, because reaching for one would mean leaving the bar and losing the text you were in the middle of.
## Linked snippets
A snippet can also point at other snippets, and typing its trigger offers those too.
This is the part that turns a snippet list into something closer to a map of the words you use. Give "continent" the links Asia, Africa and Europe, and typing "continent" offers all three. Give "address" the links Home, Work and Email, and one trigger covers all the addresses you own.
Add them under **Linked snippets** in the editor. The picker searches by name, trigger or tag, and you can pick several at once. The editor also tells you which snippets point *at* the one you're editing, under "Linked from", because a snippet can sit under more than one parent and there's otherwise no way to see it: Tehran belongs under Iran, under Capitals and under Cities all at once.
On the suggestion bar, a linked snippet shows as one chip carrying its default expansion. If that snippet has more of its own, whether other expansions or links, the chip grows a **›** on the end. Press the chip to insert what it says; press the **›** (or hold the chip) to go one level in, and the bar fills with what *that* snippet offers, with a back arrow to climb out. So "continent" → Asia → Iran → Tehran walks down as far as you've built it.
Links are a graph rather than a tree, so two snippets can share a child and a pair can point at each other without any harm: a walk never re-enters somewhere it has already been, it just stops offering the way back.
## Tags
Tags are free words you file a snippet under so you can find it again. Add them in the editor the same way you add triggers: type one, press enter, it becomes a chip. They do nothing while you type; they're for searching in the snippet picker when a list has grown past the point of scrolling it.
## Template variables
A snippet's text can include any of these tokens. They're substituted the moment the snippet is inserted, not when you save it, so `{date}` always reflects the day you actually type it:
| Token | Expands to |
|---|---|
| `{date}` | Today's date, e.g. 19 Jul 2026 |
| `{time}` | Current time, 24-hour, e.g. 16:45 |
| `{time12}` | Current time, 12-hour, e.g. 4:45 PM |
| `{datetime}` | Date and time together |
| `{isodate}` | ISO date, e.g. 2026-07-19 |
| `{isotime}` | ISO timestamp with a UTC offset |
| `{weekday}` | Day name, e.g. Sunday |
| `{day}` | Day of month |
| `{month}` | Month name |
| `{year}` | Four-digit year |
| `{timezone}` | Your time zone's full name, e.g. Bangladesh Standard Time |
| `{timestamp}` | Unix time in seconds |
| `{clip}` | Your latest clipboard entry |
| `{selection}` | Text currently selected in the field, which the snippet then replaces |
| `{app}` | Name of the app you're typing into |
| `{package}` | That app's package name |
| `{uuid}` | A fresh random UUID, different every time it's used |
| `{cursor}` | Not text; marks where the cursor lands after insertion |
| `{random:a\|b\|c}` | One of the alternatives, picked fresh each insertion |
| `{location}` | Where you are, from the [Location tool](https://wmkeyboard.pages.dev/tools/location/#in-snippets), in its default format. `{location:link}`, `{location:address}`, `{location:coordinates}`, `{location:dms}`, `{location:plus_code}`, `{location:message}` or `{location:custom}` pick one |
| `{place:Home}` | A place you saved in the Location tool, by its name in any case. A format can follow: `{place:Home:address}` |
| `{ha:sensor.outside_temperature}` | A [smart home](https://wmkeyboard.pages.dev/tools/home-assistant/) entity's state with its unit, as the keyboard last saw it, looked for in every home on the list; `{ha:home_key\|sensor.x}` names one. An attribute can follow: `{ha:light.kitchen:brightness}`. Empty when the tool is off |
`{timezone}` is the one worth a second look. It writes the zone's long name, not an offset, so you get "Bangladesh Standard Time" or "Central European Summer Time" rather than GMT+06:00. An offset only shows up for a zone with no name of its own. The settings screen's reference card gives GMT+06:00 as its example, which is the fallback rather than the usual case, but the live preview next to it always shows what you'd actually get. If you want the offset itself, `{date:XXX}` or `{date:ZZZZ}` will give you one.
`{date:pattern}` takes any date pattern you like, for example `` {date:EEEE 'week' w} `` or `` {date:dd/MM/yy} ``. A `+` or `-` and a number of seconds before the colon shifts it: `{date+86400:dd/MM/yy}` is tomorrow, `{date-86400}` was yesterday, and a bare `{date+3600}` is an hour from now in the usual format. It's checked before the plain `{date}` token, so a literal pattern can't be partly swallowed by it. A malformed pattern quietly expands to nothing instead of breaking the rest of the snippet. Tokens the expander doesn't recognize, like `{nope}`, are left untouched. A variable with nothing to draw on expands to an empty string rather than leaving the raw token behind, which is what you get from `{app}` outside the keyboard or `{clip}` with an empty clipboard.
## Managing snippets
Find the list under Settings → Text expansion (see [Options](#options) below for the exact path). The Snippets tool page has one row that opens the same screen.
tools/snippets-settings — Snippets settings: the variable reference card, saved snippets with live insert previews, and Add / Import / Export.
The settings screen opens with a reference card listing all 18 variables plus `{date:pattern}`, each with a live example. Below it:
tools/snippets-add-dialog — Adding a snippet: label, text with variables, and a Word or Pattern trigger.
- **Add snippet** opens the editor screen: a label, an optional folder, the [expansions](#several-expansions) it can insert, and a Word / Pattern switch for how it expands as you type.
- The section that holds both is called **Trigger**. Choosing **Word** gives you the [Triggers](#several-triggers) chip field and a **Match the trigger's capitals** switch that reveals its style choice when you turn it on. Choosing **Pattern** gives you the pattern itself, chips that write `(.+)`, `$1`, `^` and `$` at the cursor, a words-to-match picker, and a **Try it** box. Type into **Try it** what you'd type on the keyboard, and the result appears underneath. A pattern that won't compile is flagged as you type, with the regular-expression engine's own complaint under it. Save stays disabled until you fix it.
- Under both is **Ask before it expands**, which turns the trigger into a suggestion-bar offer instead of an automatic rewrite (see [Ask before it expands](#ask-before-it-expands)). With it off, a word trigger also gets **Expand immediately**, and once that's on, **Also inside words** (see [Expanding immediately](#expanding-immediately)).
- Below that come [Linked snippets](#linked-snippets) and [Tags](#tags).
- Pressing a saved snippet reopens the same screen, filled in, to edit it. Nothing is saved until you press **Save**, the button that floats at the bottom corner of the screen so it's always in reach. It's greyed out until the snippet has a label and some text. Leaving with changes you haven't saved, by the back arrow or the back gesture, asks first: save them, discard them, or go back to editing.
- The trash icon at the end of the row deletes it immediately. Swiping the row to the left shows the same delete button behind it, and a long swipe past about half the row deletes it outright. There is no confirmation prompt. Instead a bar appears at the foot of the screen naming what went, with **Undo** on it for a few seconds; undo puts the snippet back with its own id, its place in the list, and the links any other snippet had to it. Once that bar goes, the delete stands, so keep an [export](#import-and-export) around for anything that took a while to write. Deleting a second snippet replaces the bar rather than queueing behind it, so only the last delete can be undone.
- Every row also shows a live "Inserts as: …" preview whenever the expanded text would differ from what you typed, and "Expands by itself from: …" if you've set a trigger (or the pattern itself, if you set one of those). A snippet set to ask first says "Offers itself after: …" instead. A snippet with more than one expansion, or with links, counts them on a line of its own.
Snippets have a stored order. New ones append to the end, and the pencil button on the heading above a list of them turns that list into one you drag into order, so the ones you use daily can go back to the top. Press the tick to leave. That matters because the snippets panel draws in the same order, so the top of the list is what you see without searching. Each list is ordered on its own: the snippets inside a folder, and the ones in no folder at all.
### Folders
Folders group snippets that belong together: a set of work replies, a pack you installed, the handful of things you only use for one project. They're one level deep, and a snippet is in at most one of them.
The **Folders** section on the Text expansion screen is where you make them, and **New folder** asks for a name. Pressing a folder opens it, the same way the keyboard's snippet panel does, and inside you get the folder's own page: its name, its icon, its switch, and the snippets filed in it. So the main screen lists folders and then whatever is in no folder at all under "Not in a folder", instead of repeating every folder's contents down the page.
A folder can wear an icon of its own, picked from the same set a [keyboard mode](https://wmkeyboard.pages.dev/typing/modes/) picks from. It's on the folder's page under **Icon**, and it shows up on the folder row in Settings and on the folder tile in the snippets panel. **Plain folder** in that picker puts the ordinary folder drawing back.
The pencil button on the **Folders** heading is where the rest lives. Press it and each folder's switch is replaced by a drag handle and a trash icon: drag to set the order they're drawn in everywhere, or delete one. Deleting asks first. By default the snippets inside survive and become ungrouped, and a switch in that same question deletes them along with the folder if that's what you meant. An empty folder goes without being asked about. Press the tick on the heading to go back to the normal view. The editor grows a **Folder** picker once at least one folder exists, and the **+** button on a folder's own page writes a new snippet straight into that folder.
Each folder row carries a switch, and that switch is the point of the whole feature. (It's also on the folder's own page, as **Expand while you type**.)
- **On** is the normal state. Everything about the folder's snippets works the way it always has.
- **Off** stops the folder's snippets expanding while you type. Their word triggers and pattern triggers don't fire, and their ask-first chips don't appear. Nothing else changes: the snippets are still there, still listed in the panel, and still insert when you tap them. It's the difference between a set of canned work replies that must not fire in the middle of a personal message, and having to delete them.
Because switching a folder off is exactly the kind of thing you realise mid-message, you can also do it from the keyboard: press and hold a folder tile in the snippets panel. The tile dims and says "Off" while it's disarmed. The change is written straight to the shared file, so Settings shows it too.
A folder switched off costs nothing at all while you type. Its snippets are left out of the trigger index entirely, so the keyboard's "could anything match here?" checks answer no without doing any work.
**Change all snippets here**, on a folder's page, sets options on every snippet in the folder at once, so a folder of forty corrections doesn't need forty trips to the editor. For each of **Match the trigger's capitals**, **Which letters to make capitals**, **Ask before it expands** and **Expand immediately**, pick **On**, **Off** or a style, or keep **Leave as is**, which is where each one starts. **Apply** changes only what you picked. The editor's own rules still hold: a snippet with a pattern trigger keeps its own capitals and expansion, and a snippet that asks before it expands never expands immediately. A message at the bottom of the screen offers **Undo** for a few seconds.
### Import and export
**Import** and **Export** each start by asking which format you mean. This app's own is one of three; the other two are [Espanso](https://wmkeyboard.pages.dev/reference/importing-from-espanso/)'s, which is what lets a package from the [Espanso Hub](https://hub.espanso.org/search) work here and your own snippets work on a desktop. Importing from Espanso can also fetch straight from a pasted web address, and always shows what conversion costs before it writes anything.
Import takes two more formats that nothing exports to:
- **A table (CSV).** A `.csv` or `.tsv` file, with commas, tabs or semicolons between the columns. This is what most other text expanders and every spreadsheet can write. With a header row, the columns are found by name in any order: `trigger`, `shortcut` or `abbreviation` for the trigger; `text`, `expansion`, `phrase` or `content` for what it types; and, if you want them, `label` or `name`, `folder` or `group`, and `tags`. Without a header the columns are read in order: trigger, text, then an optional label, which is how TextExpander, aText and Texpand export. A cell in double quotes can hold commas and line breaks. A row with no text is dropped, and a trigger the keyboard cannot watch for is taken off while the snippet stays.
- **Gboard's dictionary.** The `PersonalDictionary.zip` that Gboard exports. Its shortcuts become snippets that ask first, the way Gboard shows them as suggestions. Its plain words are left out here; import the same file from the [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/#androids-dictionary) to add both at once. See [Gboard's personal dictionary](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#gboards-personal-dictionary).
Both show the same confirm dialog as an Espanso file, with a folder name to put the snippets in.
**Export** writes every current snippet to a `.wmsnippets.json` file (see [file formats](https://wmkeyboard.pages.dev/reference/file-formats/)), along with the folders they're filed in. A folder with nothing in it isn't written, so exporting doesn't carry empty shelves along. **Import** always adds alongside what's already there rather than replacing it, so importing the same file twice leaves you with two copies. The file's folders come back as new folders of the same name, and a snippet that was in none stays in none.
The fourth choice under **Export**, **Share with everyone**, sends one folder of snippets, or all of them, to the [community repository](https://wmkeyboard.pages.dev/addons/community/) for anyone to install. It asks which folder, and reminds you to check there's nothing personal in them first.
Rows are repaired rather than rejected when something's off. A snippet with no text is dropped. Text over 20,000 characters is trimmed. A blank label is filled in from the snippet's own first line. A trigger pattern that won't compile, or that runs past 200 characters, is removed while the snippet itself is kept. Only the first 500 snippets of an oversized file are kept, and the first 100 folders. A folder with no name, or one repeating another's ID, is dropped and its snippets arrive ungrouped. Once the import finishes you get a summary of everything that changed.
## Options
Text expansion
Everything about the snippets themselves lives here, under Features on the settings home screen, rather than on the tool page: the list, the two reference cards, folders, Add, Import and Export. Snippets expand while you type whether or not the tool sits on the toolbar, so they aren't really the tool's settings.
Tools / Snippets
The tool page carries only what belongs to the button:
- **All Text Expander settings**: opens the screen above.
- **Panel columns** (1 to 6, default 2): how many snippet tiles the keyboard's panel draws side by side. Folders use the same grid. The gear in the panel's header opens this page, so the setting is one tap away from the panel.
- **Names only** (off): each tile shows the snippet's name alone, without its text, so a tile is one line tall and a long list fits more to a screen.
- **Stay open after inserting** (off): tapping a snippet inserts it and leaves the panel open, for several in a row. Off, the panel closes after each one.
- **Enabled**: which way this starts depends on how you answered the setup wizard. Snippets is in the "recommended" and "show me everything" starter sets, so only the simplest answer leaves it switched off. Turning it off removes Snippets from both the toolbar and the toolbox.
- **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) is turned on globally. Overrides just this tool's icon color.
- **Keyword shortcut**: the words that make the [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) suggestion offer to open this panel. "Snippet" and "snippets" out of the box, editable or clearable here. This only controls opening the panel, and it's unrelated to a snippet's own trigger word (above).
The same **Panel columns**, **Names only** and **Stay open after inserting** settings also sit on the Text expansion screen, under **Behaviour**. It only changes the panel's grid, not the chips a trigger offers in the suggestion bar.
**Expand in password fields** (default **off**), in that **Behaviour** group, lets a word trigger or a trigger ending in a symbol expand inside a password box, for a Wi-Fi key or a long passphrase you keep as a snippet. With it on, pressing space or Enter there, or typing the symbol a trigger ends in, makes the keyboard read back the few characters before the cursor and check them against your triggers. Nothing it reads is learned, kept or sent. The space that ends a trigger is spent on the expansion rather than typed into the password, and Enter still does what the field asks, so `wifi` then Enter fills the box and submits it. Patterns, chips that ask first, and apps typing snippets through [automation](https://wmkeyboard.pages.dev/reference/automation/) stay off in password fields either way. Number-only PIN fields aren't covered; tap the snippet in the panel there instead.
**Escape character** (default **backslash**), in the same group, is what you type just before a trigger set to [expand immediately](#expanding-immediately) to keep the trigger as text. The character is removed when it does its job, so `\btw` leaves `btw`. The choices are a backslash, a backtick, or **Off**, for when you write that character in front of words for its own sake.
Beyond that, there's nothing global to configure about expansion. There's no on/off switch separate from the tool. How matching works is set per snippet instead, on the editor screen: word or pattern, how far back a pattern may reach, whether it expands on its own or asks first, and whether it waits for the space or expands immediately. The nearest thing to a group switch is a [folder](#folders), which arms and disarms everything filed in it together. Having no pattern snippets at all is what keeps the matching machinery out of the typing path entirely.
## Asking the AI about them
The [AI chat](https://wmkeyboard.pages.dev/tools/ai/) can look through your snippets with **Read your snippets**, one of the [keyboard's own tools](https://wmkeyboard.pages.dev/tools/ai/#the-keyboards-own-tools): ask it for "my bank details snippet" or "what do I type for my address?" and it reads back the matching snippets' names, triggers and text. A template is read as you wrote it, so the model sees `{date}` rather than today's date, and `{clip}` never hands it your clipboard. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, it also finds snippets that say the same thing in other words. Like the other tools that read your things, it asks you first each time unless you set it to **Run**.
## Snippet packs and backups
Someone can package a set of snippets as an installable addon: a `.wmsnippets.json` file up to 4 MB. You can preview it before installing, so you see the actual snippets it contains rather than installing blind. Installing a pack adds its snippets alongside anything you already have, in a [folder](#folders) of its own named after the pack. That's what lets you silence a whole pack with one switch, find it again in six months, and see at a glance which snippets are yours. If the pack file declares folders of its own they're flattened into the pack's folder, since folders are one level deep.
Uninstalling a pack removes exactly the snippets that pack added and leaves your own snippets and any other installed pack untouched. Its folder goes with it, unless you've moved something else into that folder, in which case the folder is yours now and stays. Packs you installed before folders existed are filed into one the next time you open the Addons screen, using the name the pack was installed under. If you've already sorted a pack's snippets by hand, that arrangement is left exactly as you made it. See [installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the general install/preview/uninstall flow.
That's a different mechanism from the whole-app [config backup](https://wmkeyboard.pages.dev/privacy/backup/) (`.wmconfig.json`). That backup has its own optional Snippets section you can include or leave out when backing up everything at once, alongside themes, dictionary, clipboard, stickers and your settings. The plain settings backup (`.wmsettings.json`) is different again: it deliberately leaves snippets out, since these two flows already cover them.
## Details & edge cases
- Snippets aren't available before you've unlocked your device once after a restart. The snippet file lives in credential-encrypted storage, so this tool sits out any lock-screen or pre-unlock typing until then.
- Ships the same way in every build: there's no build gating on this tool.
- The 500-snippet and 20,000-character limits above are enforced only on import. Adding snippets one at a time from the editor screen has no equivalent cap.
- Because the panel re-reads the snippet file only when you open it, a snippet you just added in Settings won't appear if you already had the panel open. Close and reopen the panel (or switch to another panel and back) to pick it up. Triggers themselves are refreshed whenever you tap into a text field, so a snippet you just saved starts expanding straight away even if you never open the panel.
- `{uuid}` generates a new value on every insertion, including inserting the same snippet twice in a row. It isn't fixed per snippet.
- A tap in the snippets panel always inserts the default expansion, never a picker: one tile, one insertion. Hold it if you want the choice.
- Chips on the suggestion bar go the moment the text they were talking about changes: a keystroke, a backspace, moving the cursor, opening a panel. A swap chip verifies the text is still there before it replaces anything, so a press that lands late does nothing rather than eating the wrong words.
- Every expansion of a pattern snippet sees the same captures, so `$1` means the same thing in the second one as in the first.
- Tapping a **pattern** snippet in the panel inserts its text with the captures left blank and puts the cursor in the first gap, since a tap has no words to capture. `Hello, $1! Nice to meet you.` goes in as "Hello, ! Nice to meet you." with the cursor after "Hello, ". A plain snippet is unaffected: a `$1` in its text is a dollar sign and a one.
- Capture text is spliced in exactly as you typed it. A template variable that lands inside a capture stays literal rather than expanding. Type "note remember `{clip}`" into a `^note (.+)$` pattern and the token goes in as written, so text in the field you're typing into can never pull your clipboard in behind you.
- Put `{cursor}` in a snippet's text where you want to keep typing after it goes in. `Dear {cursor},` with the trigger `dear` inserts "Dear ," and leaves the cursor before the comma, ready for the name. The space you pressed to expand the trigger is used up by the expansion rather than typed, so it doesn't land in front of the name. With `{cursor}` at the very end, or with no `{cursor}` at all, the cursor ends up after the snippet and the space goes in as usual. Some editors, mostly code editors and a few note apps, ignore where the keyboard asks the cursor to go and leave it at the end of the inserted text; the keyboard checks for that and moves the cursor back to the marker itself.
- Word triggers work in every field except password fields (unless you turn on **Expand in password fields** under [Options](#options)), including a browser's address bar, which asks the keyboard for no suggestions. There the trigger is read back from the box when you press space or Enter, and it replaces the browser's own inline completion. Chrome, Brave and the other Chromium browsers treat a block of text landing at the start of an empty address bar as a paste and strip `javascript:` out of it, so the keyboard puts an expansion there the way typing would, and a snippet that expands to a `javascript:` bookmarklet arrives whole. A trigger set to [ask first](#ask-before-it-expands) offers its chip in the address bar too, as long as the suggestion bar is showing there (**Suggestions in every field**, on by default). A trigger that starts with a digit, like `123`, is read back the same way in every field, since a number at the start of a word goes straight into the field instead of the word buffer. Patterns are stricter: they stay out of fields that ask for no suggestions, and out of every field when you've turned suggestions off entirely.
- Swiping a word with gesture typing, or tapping one from the suggestion strip, doesn't fire a pattern. The next word you type by hand does.
---
# Internet speed test
URL: https://wmkeyboard.pages.dev/tools/speed-test/
Section: Tools
Summary: Measure download, upload, ping and jitter from the keyboard, then send the result as a picture: a card in your choice of style, size and layout.
Someone asks how the Wi‑Fi is, and the honest answer is a number. The **Internet speed test** tool measures the connection you are on and drops the result into the chat as a picture, without leaving the keyboard. A run takes about twenty seconds. It pings the server, pulls random bytes for ten seconds, pushes them back for ten more, and then shows the card it would send, with chips to change how that card looks before it goes.
## Running a test
Open the tool from the toolbox. The panel is a dial, with the server a run will use in its header and roughly how much data the run will move written inside it. The dial is the button: tap it and it fills with the live speed while the run goes, the phases (Ping, Download, Upload) as a rail across the header with the ping's answer written on its step, and the last seconds as a skyline along the bottom. **Cancel** sits in the header and stops it at once, and so does closing the panel or hiding the keyboard: a run nobody is watching is only costing data.
A finished run shows four numbers.
- **Download** and **Upload** are the mean speed over each transfer with its first fifth left out. That is where the connections are still opening and TCP is still finding its pace, and every browser test discards it too. The unit is megabits per second unless you switch it to megabytes.
- **Ping** is the median of ten round trips to an empty page, the first one dropped because it also opens the connection.
- **Jitter** is how much those ten varied, as the mean difference between one and the next.
The panel remembers the last run. Open the tool again later and the result is already there with its card, and the next run's card notes how it compares.
## The card
The result is sent as a PNG, and the panel shows it as it will go: the card itself, as large as the panel allows, with the next and previous looks peeking at its sides. A swipe across the card changes its look, the dots under it say which of the five is on, and one row of chips under those sets its size, layout and unit; every change redraws the card on the spot.
- **Style** (the swipe): **Theme** paints the card in the colours of the keyboard theme you have on, so it matches your screenshots. **Dark**, **Light**, **Gradient** and **Mono** are fixed designs that look the same whatever theme is on. Mono is black on white, for printing or a plain thread.
- **Size**: **Square** (1080 × 1080) and **Portrait** (1080 × 1350, 4:5) suit most chats; **Story** is 9:16 for a status or story; **Wide** is 16:9 for a post or a forum; **Sticker** is 512 × 512, small enough to go as a sticker where the app takes one.
- **Layout**: **Full** has every number, the download line as a graph, and a footer naming your network, the server and the change since the last run. **Gauge** is one dial with the download speed in it and the other numbers underneath. **Compact** is the four numbers in a grid, which suits the Sticker size. **Minimal** is the two speeds and nothing else, in very large type.
Along the bottom sits one bar of the ways to send it: **Send** as the wide button, the other three as round buttons that name themselves on a hold.
- **Send** puts the card into the field, the same way a GIF or a sticker goes. An app that takes no pictures gets the card on its share screen instead, or on the clipboard.
- **Save** writes it to `Pictures/WM Keyboard`.
- **Share** opens the [share sheet](https://wmkeyboard.pages.dev/reference/settings/share-sheet/) with the card.
- **As text** types one line instead of a picture, `↓ 94 Mbps · ↑ 31 Mbps · 18 ms ping (Cloudflare Dhaka)`, with your own numbers.
- **Run again**, in the header beside where and when the result came from, starts over.
The footer never shows your IP address unless you turn **Show my IP address** on. It does name your network provider and the city of the server that answered, which is what a result is usually shared to say.
## Servers
A run talks to one server, and the chip under the Start button picks which. Tools / Internet speed test / Servers is the list, in the same shape as every other [provider list](https://wmkeyboard.pages.dev/tools/providers/).
- **Cloudflare** is on by default and needs nothing set up. The test reaches the nearest of Cloudflare's data centres, which is why the numbers are about your line rather than about some distant server, and Cloudflare tells the keyboard which network you are on and which city answered, which the card shows.
- **LibreSpeed** is a server running the free LibreSpeed software. Add the row, then either type the address of one you run yourself under **Setup**, or open the row and **Pick from the public list**: about a hundred volunteers' servers, listed by city with their sponsor, filtered as you type. Picking one fills in the row. You can add several LibreSpeed rows and switch between them on the panel.
In the F-Droid edition both addresses, Cloudflare's and the list at librespeed.org, are settings on the row's screen and under Advanced / Servers, as every service's address there is.
## Data use
This is the one tool in the keyboard that moves a lot of data on purpose: on a fast line a run can pass a few hundred megabytes. The panel says what a run is likely to cost before you start, worked out from the last run's speeds (a guess of 50 Mbps each way before there is one). On a connection Android counts as metered, [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) asks first by default and **Run anyway** is the yes. Every request of a run shows up in [Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/) under Speed test, with the bytes it moved.
### Options
Tools / Internet speed test
- **Seconds per direction**: 5 to 30, default **10**. How long the download and then the upload each run. Longer is steadier and costs more data.
- **Connections at once**: 1 to 8, default **4**. Requests in flight together. One connection rarely fills a fast line, and four is what the browser tests use.
- **Measure upload**: on by default. Off, a run is pings and download only, in half the time and data.
- **Unit**: **Mbps** (default) or **MB/s**, on the panel and the card.
- **Result card**: **Style**, **Size** and **Layout**, the same choices as the panel's chips, so either place changes both. **Send a sticker-sized card as** picks Picture (default) or Sticker for the Sticker size, which is the only size that can go as a sticker, and only into apps that take one. **Save sent cards to the gallery** (off) keeps a copy of every card you send; the panel's Save button saves one regardless. **Show my IP address** (off) puts the connection's public address in the card's footer.
- **Servers**: the provider list described above.
The speed test is the **Speed test** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/). Off there, the tool leaves the toolbar, the toolbox and the Tools list until the bundle is switched back on.
## Details & edge cases
- **Discoverable by typing.** Typing `speedtest` or `bandwidth` as a word offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) that opens the tool. The list is editable under the tool's own **Keyword shortcut** field.
- **Not before you unlock.** The tool needs the network and the settings, so it waits for the first unlock like the other online tools; see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone).
- **A VPN or a hotspot counts as metered** to Android, so Data saver may ask on Wi‑Fi too. The question names the kind of link it saw.
- **What the headline leaves out.** The first fifth of each transfer is dropped from the mean. The dial during a run shows the same running mean, so it climbs while the connections open and then settles on the number the card will carry; the thin line beside it is the raw 200 ms samples, lightly smoothed and drawn as a curve on a fixed time axis, so it grows to the right rather than squeezing up as the run goes on. A run shorter than a second has too few samples to trim and reports them all.
- **Why the upload line is spikier than the download line.** A write lands in the phone's send buffer at once and counts the moment it does; the next waits until that buffer drains. The raw samples therefore come in bursts, which the running mean on the dial evens out.
- **Cloudflare limits repeated runs.** After several large runs from one address in a short time, Cloudflare refuses big downloads with HTTP 429 while still serving small ones. The test then asks for smaller pieces, 25 MB down to 1 MB, and the run goes on; only when even those are refused does it stop, saying that Cloudflare is limiting speed tests from this connection for now. Other refusals name the status and the phase. A LibreSpeed server has no such limit.
- **Uploads on a slow line.** The upload body grows while requests finish quickly and shrinks when they take seconds, so a slow line is not stuck finishing one large post after the clock has run out; at the deadline every open connection is cut.
- **Hardware shortcut.** There is no default letter. Reach it through the toolbox (T) or [assign one yourself](https://wmkeyboard.pages.dev/reference/shortcuts/).
---
# Text editing & cursor tools
URL: https://wmkeyboard.pages.dev/tools/text-editing/
Section: Tools
Summary: A cursor pad, selection helpers, and undo/redo: precision editing from the keyboard.
Fixing a typo three lines up, selecting a whole paragraph, or nudging the cursor one character at a time is fiddly on a touchscreen with no physical arrow keys. WM Keyboard adds a dedicated text-editing panel, a set of one-tap cursor tools for the toolbar, and one-tap undo/redo. Together they stand in for the arrow keys and Ctrl-shortcuts a hardware keyboard would give you.
tools/text-edit-panel — The text-editing panel: a d-pad for moving the cursor, Select/Select all/Copy/Paste on the right, and Home/End/Backspace along the bottom.
## Moving and selecting text
Open **Text editing** from the toolbar, or from the toolbox if you haven't pinned it there (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/) for pinning and the toolbox in general). On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press X.
The panel is a d-pad cluster: ◀ and ▶ span the full height on either side, with ▲, a Select toggle, and ▼ stacked in the middle. A bottom row adds Home, End, and Backspace. Each of those keys sends the field a real key event, the same one a hardware arrow key would send, rather than a movement the keyboard computed itself. So the cursor behaves exactly as it would with a physical keyboard plugged in. Hold Home or End for the start or the end of the whole text. Those two are the exception: Ctrl+Home and Ctrl+End mean nothing to many apps, so the keyboard puts the cursor there itself, and falls back to the key press only in a field that hasn't said where its cursor is. With Select on, they select from the cursor to that end.
Tap Select to turn selection mode on. The key lights up while it's active. With Select on, every arrow, Home, or End move extends the selection instead of just moving the caret. The panel wraps each move in a real Shift press rather than a background modifier flag, since some editors only pick up Shift state from the key events themselves. Tap Select again to turn it off, or see below for the two actions that flip it automatically. The toolbar's [Selection mode](#selection-mode) tool is the same mode from outside the panel: either one turns it on, either one turns it off, and both light up while it's on. The difference is how long it lasts. The panel's toggle ends when the panel does, and the tool's mode stays on until you press it again.
Select answers the same three gestures the tool does. One tap toggles, two quick taps select the word at the cursor, three select the line, and a press and hold turns selection mode on only for as long as your finger stays down. Both extra gestures read the same two switches under Tools / Selection mode / Options the tool does, so turning either off turns it off on both surfaces. The one thing that overrides the hold is a long press you assigned yourself: give a Select key alternates in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/) and the hold opens those instead, which the editor warns you about as you bind them. Tapping is never affected.
Once something is selected, the keyboard can offer what to do with it: [Selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/) puts the edits, a find and replace, the case options, Call, Open, QR and a good deal more above the keys, depending on what you selected. That bar is off by default and lives under Advanced.
Holding ◀, ▶, ▲, ▼, or Backspace auto-repeats after the keyboard's own key repeat delay, then keeps repeating at an interval you can tune (see [Options](#options) below). That start delay defaults to 300 ms and is adjustable from 150 to 3000 ms under Key press.
## Copying, pasting, and cutting
The panel's second column holds Select all, Copy, and Paste. There's no Cut button in the panel: cutting lives elsewhere, as a press-and-hold letter shortcut (see [below](#press-and-hold-shortcuts)).
- **Select all** selects the whole field and doesn't touch selection mode either way, so a follow-up arrow key moves the caret unless selection mode was already on for some other reason. It's also a toolbar tool of its own (see [below](#one-tap-cursor-tools-for-the-toolbar)), so it needn't cost a panel.
- **Copy** copies the current selection and turns selection mode back off. With nothing selected it copies the whole text, the same as holding C.
- **Paste** inserts the clipboard content at the cursor (replacing any selection) and doesn't touch selection mode either way.
All three also exist as their own toolbar tools, **Copy**, **Cut** and **Paste**, under the **Quick actions** group in Tools. Pin any of them and the operation costs one tap with no panel to open first. They do exactly what the panel's keys do, including copy and cut ending selection mode. Like Copy, Cut takes the whole text when nothing is selected, the same as holding X. That goes for a layout's Cut key too, and for the keyboard's own boxes, such as a note or a search field. Copy and cut work [before you unlock the phone](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone); paste doesn't, because the clipboard isn't readable yet.
Copy and paste run through the target field's own handling, the same as if you'd triggered them from that app's text-selection menu. What exactly gets copied, and whether a "Copied" system toast appears, therefore follows the app you're typing into. WM Keyboard can add its own **"Copied"** confirmation on top, off by default, under Clipboard / Panel (see [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/)).
Pasting into a password field clears the system clipboard afterward by default, so a copied password doesn't linger (also configured on the [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/) page, not here).
## One-tap cursor tools for the toolbar
The panel above costs one tap to open and another to act. If you reach for the same cursor move often, pin its toolbar tool instead and skip the panel entirely. They call the same underlying handler, so the move itself is identical either way. The toolbar adds two things the panel has no room for: Shift extends the selection, and a hold repeats the move.
| Tool | What it does |
|---|---|
| Cursor left / Cursor right | Move the cursor one character |
| Word left / Word right | Jump a whole word (Ctrl+Arrow, or where you set a word to end) |
| Cursor up / Cursor down | Move the cursor a line |
| Line start / Line end | Jump to the start or end of the line |
| Page up / Page down | Scroll the cursor a page |
| Select word | Select the word at the cursor; press again to take the words beside it too |
| Select line | Select the entire line at the cursor |
| Select all | Select the whole field |
| Selection mode | Every cursor move selects text until you turn it back off |
**Select all** is the panel's Select all key on the toolbar: it selects the whole field and leaves selection mode where it found it. **Select word** and **Select line** don't send Ctrl+Shift+Arrow. The keyboard works out the word or line boundaries around the cursor itself and sets the selection directly. Both turn selection mode on afterward. Both do nothing if the cursor sits between spaces or on an empty line. A cursor against marks with no word in reach, as in `example@|`, gets **Select word**'s next best thing: the whole run between the spaces around it, `example@`.
Press **Select word** again while its selection is still there and the selection grows, the parts touching it first and anything past a space last. Each press takes the next ring out: across `-` and `_`, then across `.`, then across a single `/` (never the `//` of `https://`), then the whole run between two spaces, then a word past the space on each side, and on from there. With the cursor in `git|hub.com` inside `https://github.com/wasi-master/wmkeyboard`, the presses select `github`, `github.com`, `github.com/wasi-master/wmkeyboard` and then the whole link. With Shift up, a press goes back one step instead, down to the first word. While **Shift changes the case of the selection** is on, pressing Shift with text selected changes its case, so turn that off to use Shift this way.
### Where a word ends
Out of the box, **Word left** and **Word right** send Ctrl and an arrow key, and the app decides what a word is. Most apps treat `wmkeyboard.pages.dev` as one word and `GrapheneOS` as one word, so the cursor cannot stop at `wmkeyboard.|`. **Word steps**, on the settings page of each of the three word tools, lets the keyboard measure the steps itself:
- **As the app decides**: Ctrl and an arrow key, as before.
- **At spaces**: the cursor stops only at spaces.
- **At spaces and punctuation**: the cursor also stops between letters and marks, so `wmkeyboard.pages.dev` is five steps. An underscore is a mark too, so `wordtwo_wordthree` is three.
With either of the last two, **Stop at capitals** also splits a word at a capital inside it: `GrapheneOS` is `Graphene` and `OS`, and `HTMLParser` is `HTML` and `Parser`. **Stop at numbers** splits letters from digits, so `word1` is `word` and `1`. **Select word** takes its word by the same rules. A field that does not say where its cursor is gets the Ctrl and arrow key anyway.
The delete keys have their own switches for the same thing, on the [Key press](https://wmkeyboard.pages.dev/reference/settings/keypress/#timing) screen.
### Hold Shift to select
Tap Shift before a cursor tool, or before an arrow key on the keys themselves, and the move extends the selection instead of collapsing it, the way Shift+Arrow does on a physical keyboard. Shift lock works too, which is the easier one for dragging a selection out over several taps. Shift isn't spent by the move, so it stays on and every following tap keeps extending. To stop, tap Shift again: that puts it back down and leaves the selection where it is. A cut or a paste puts Shift down for you, lock included, the way you let go of Shift on a physical keyboard once the shortcut is done, and so does leaving the letters for the symbols or the number pad. The cursor keys after it move the cursor again instead of selecting. One more Shift tap after that, with the selection still standing, re-cases it (see [Details & edge cases](#details--edge-cases)).
Only a Shift you pressed yourself counts. The keyboard arms Shift by itself at the start of a sentence, and an ALL CAPS field arms the lock, so reading those would turn an ordinary cursor tap into a selection you never asked for and your next keystroke would type over it.
Auto-capitalize leaves your Shift alone while a selection is live, so the second tap extends the selection instead of dropping it.
A held or locked Shift reaches every cursor move you make from the keyboard itself, not only the toolbar's tools: the [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/) on both axes, the [volume keys](https://wmkeyboard.pages.dev/typing/gestures/) when they're set to move the cursor, and a layout's own arrow keys. Lock Shift and scrub the spacebar and you're dragging a selection out with one thumb each. In a one-line field such as a search box, Shift with up or down selects to the start or the end of the text, the way a desktop text box does, rather than stepping into the box's suggestions or out of the field. A box the app marks as holding several lines, or one that already has a line break in it, keeps its real Shift with up or down, so the selection moves a line at a time there, Firefox's text boxes included. A selection grown up or to the left keeps growing that way: the end you started from stays put. The text-editing panel as shipped has no Shift key, so its own Select toggle is the answer there, unless you put a Shift key on the panel in its [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts), where it works exactly like this.
### Selection mode
**Selection mode** is the toolbar's version of holding Shift down, without a hand tied up doing it. Press it once and it stays on: from then on every cursor move extends the selection instead of collapsing it. Press it again to turn it off. The tool lights up while it's on, and so does the panel's own Select key, because they're the same mode seen from two places.
*Every* cursor move obeys it, not just the toolbar's:
- the arrows, Home and End in the text-editing panel
- the one-tap cursor tools above
- the [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/), including its 2-D mode
- the [volume keys](https://wmkeyboard.pages.dev/typing/gestures/), when you've set them to move the cursor
- arrow keys on the keyboard itself, on a layout that has them
That's the point of a mode over a held Shift: whichever hand you'd normally move the cursor with is free to do it, and navigating never cancels the selection you're building.
Turning it off keeps the selection. Only the *extending* stops, so what you've selected is still there to copy, cut, or type over.
Two more gestures on the same button:
- **Press and hold** for a temporary mode. Selection mode is on for as long as your finger stays down and off the moment you let go, again leaving the selection intact. Good for one quick selection when you don't want to remember to switch the mode back off. It's on by default and can be turned off under [Options](#options).
- **Press twice quickly** to select the word at the cursor, or three times to select the line. Both leave the mode on, so you can widen the selection from there with any cursor move. The first press of a pair still toggles the mode, exactly the way the first tap of a Shift double-tap still arms Shift. If that trips you up, the shortcut is switchable off.
Selection mode ends by itself when you move to a different text field, and a press and hold ends when the keyboard closes. It survives everything else: panels opening and closing, layout switches, and any amount of cursor movement.
### Hold to repeat
Holding a cursor tool on the toolbar keeps the move going for as long as your finger is down, at the same repeat interval the panel uses. It saves a tap per character when you're crossing a paragraph.
Eight tools repeat: **Cursor left**, **Cursor right**, **Word left**, **Word right**, **Cursor up**, **Cursor down**, **Page up** and **Page down**. **Line start**, **Line end**, **Select word**, **Select line** and **Select all** don't, because a second press of any of those lands exactly where the first one did. **Selection mode** doesn't either: its hold is the temporary mode described above.
The toolbox grid can do it too, but that one is off until you ask for it, one tool at a time. See [Options](#options) for why.
Two things a hold does on every *other* tool are worth knowing about here:
- **Hold and drag still moves the tool.** Once your finger travels far enough to read as a drag, the repeat stops and the tool picks itself up as usual. The moves it already made stay, so a deliberate drag costs you a caret move or two. Move the caret back afterward.
- **Hold opens a tool's settings page.** Repeat spends that gesture on whichever surface it's turned on for. With the toolbar switch on and the toolbox one off, which is how it ships, a toolbox hold still opens any tool's page. Turn a tool's toolbox switch on as well and that tool's page is only reachable under Tools.
A **Hide keyboard** tool (one tap dismisses the keyboard) shares the **Cursor** section with these on the Tools screen, but it isn't a cursor move itself.
None of the cursor tools is pinned to the toolbar out of the box, and Text editing is pinned only on a large tablet. A phone's default row is Emoji, Clipboard and Settings, a small tablet adds GIF and Voice, and a large tablet adds Translate and Text edit on top of those. A pinned tool that setup left switched off is dropped from the row rather than drawn dead, so the "keep it simple" answer on a large tablet gets five of the seven. Everything else waits in the toolbox. The onboarding answer matters too. "Recommended" turns on Text editing, Undo and Redo, but leaves out the numpad, Hide keyboard and every cursor tool above. If you don't see one, open Tools and turn it on.
## Undo and redo
The toolbar's **Undo** and **Redo** tools fire immediately, with no panel: one tap sends the field's own undo or redo keyboard shortcut. Undo always sends Ctrl+Z. Redo sends Ctrl+Shift+Z by default, or Ctrl+Y if you flip the [redo shortcut setting](#options).
This isn't a typing history WM Keyboard keeps of its own. It has no way to reach into another app's private undo stack. It sends the same shortcut a hardware keyboard would, and relies entirely on the app you're typing in supporting it: an editor with no undo shortcut just ignores the tap.
## The numeric keypad
**Numpad** opens a dedicated 4×4 number pad over whatever field you're in. Don't confuse it with the keyboard switching to a numeric layout on its own, which happens in a field already typed as a phone or number field. Numpad is a panel you open by hand, and it works over any field, numeric or not.
There are several ways to open it: the Numpad tool, a press and hold on ?123, a spacebar swipe set to Numpad, a 1234 key on the symbol pages (**1234 key on the symbol pages** under Layout & size / Bottom row keys, off by default), or a Numpad key you add to your own layout in the editor.
tools/numpad-panel — The numpad panel: a 4×4 grid in phone or calculator digit order, with Backspace repeating on hold.
It comes in two layouts, switched under [Options](#options):
- **Phone-style** (default): `1 2 3 ⌫` / `4 5 6 +` / `7 8 9 −` / `. 0 , ⏎`
- **Calculator-style**: `7 8 9 ⌫` / `4 5 6 +` / `1 2 3 −` / `. 0 , ⏎`
Backspace repeats on hold like the rest of the keyboard, and Enter sends a normal Enter key.
## The quick alternative: spacebar cursor swipe
You don't have to open a panel at all for the most common move: sliding the cursor a short distance. By default, a long swipe on the spacebar moves the cursor (a short swipe switches language instead), and there's an optional 2-D touchpad mode that adds vertical line movement to the same gesture. Full mechanics, including how to reassign what each swipe length does, live on [Gestures](https://wmkeyboard.pages.dev/typing/gestures/).
## Options
### Text-edit panel repeat speed
Tools / Text editing / Options
**Key repeat interval**: default **60 ms**, adjustable from 30–200 ms. It sets the pause between repeats while you hold an arrow key in the panel, and while you hold one of the toolbar's cursor tools. The panel's two delete keys are the exception: they repeat at the keyboard's own **Key repeat interval for delete** under Key press, because they *are* the delete keys (see below). Lower is faster. The delay before repeating starts is the keyboard's own key repeat delay in the panel, and the system's long-press timeout on the toolbar.
### Panel layout
Tools / Text editing / Panel layout
The panel's grid is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts): the same editor, the same key sheet and the same undo as a key layout. The row reads **Standard** until you change something, then **Your own**.
Every key on the panel is a key whose action is a **text editing action**, one of twenty-five: the four arrows, word left and right, page up and down, start and end of the line, start and end of the text, Select, Select all, Select word, Select line, Cut, Copy, Paste, Delete, Forward delete, Delete word, Delete word forward, [Retype word](https://wmkeyboard.pages.dev/smart/suggestions/#retyping-the-last-word) and [No space](https://wmkeyboard.pages.dev/typing/glide-typing/#the-space-after-a-glided-word). Holding a No space key arms it for the next key, so a key needs no second action for that. Delete word and Delete word forward take the word before or after the cursor in one press, the way Ctrl+Backspace and Ctrl+Delete do on a computer, and find the end of a word the same way a [word swipe on the delete key](https://wmkeyboard.pages.dev/typing/gestures/#deleting-more-than-a-character-with-backspace) does. Pick a key in the preview and its settings open under it, where you can change its action, its width in grid units, how many rows it covers, and its alternates for press and hold. You can also put ordinary keys on the pad: Enter, a tool key, or anything else the Action picker offers.
**Press and hold only works on keys that don't repeat.** The four arrows, word left and right, page up and down, the two deletes and the two word deletes all repeat while you hold them, at the repeat interval above, and that's the point of holding them, so the sheet doesn't offer alternates on those. Everything else (Home, End, start and end of the text, Select, the selects, Cut, Copy, Paste) has a hold going spare, and that's where an alternate goes. Holding such a key opens the alternates popup, the way it does on any key of the keyboard, and sliding onto the entry and letting go runs it.
**The pad's Delete and Forward delete are the keyboard's own delete keys.** They carry every gesture the key on the letters row has: a swipe takes words or characters with the same select-first preview, **Holding delete clears words** applies, and each stops at the end of the text it eats into. They use the delete key's repeat interval rather than the panel's for the same reason. Delete takes what is behind the cursor; Forward delete takes what is ahead of it, and its swipe runs rightward. See [Gestures](https://wmkeyboard.pages.dev/typing/gestures/#deleting-more-than-a-character-with-backspace) for the settings that shape both.
Two of those pairings ship: **Home** and **End** hold to the start and end of the *text* rather than the line. Widths and spans mean exactly what they mean in a key layout, and the panel places them with the same geometry the keyboard draws its keys with, so the tall left and right arrows in the standard arrangement are simply a key three rows deep. The pad has no component cell, so its rows share the height of the key area by their row heights. **Use the standard layout** at the bottom of the editor puts the shipped layout back.
A layout you made on an earlier version, when the pad had an editor of its own, carries over the first time this version reads it, key for key.
### Hold a cursor tool to repeat
Tools / Cursor left / Options
**Hold to repeat on the toolbar**: on by default. Holding one of the eight repeating cursor tools on the toolbar keeps moving the cursor. Turn it off and a hold there does the tool's [press and hold action](https://wmkeyboard.pages.dev/tools/overview/#hold-a-tool-for-a-second-action) instead: out of the box a word left or right for the arrows, page up or down for up and down, and the start or end of the line for the word moves. Page up and down hold to their settings page. It's one switch shown on all eight of their settings screens, so flipping it on any one of them flips it everywhere.
**Hold to repeat in the toolbox**: off by default, and this one is per tool. Turn it on and holding that tool in the toolbox grid repeats the move as well.
It's per tool, and off to start with, because of what it costs. A hold in the toolbox is how you reach any tool's settings page from the keyboard. A tool that repeats there has spent that gesture, so its own page is then only reachable under Tools. That's a fair trade for the one or two moves you actually hold down and a bad one across the whole grid, which is why it's answered a tool at a time rather than all at once.
The **Key repeat interval** slider sits under both, the same slider the text-editing panel uses, and it shows as soon as either surface repeats. Dragging a tool to move it survives both switches on both surfaces.
### Selection mode gestures
Tools / Selection mode / Options
**Press and hold to select**: on by default. Holding the tool on the toolbar turns selection mode on until you let go. Turn it off and a hold there opens the tool's settings page instead, the way a hold on any other toolbar tool does.
**Two presses select a word**: on by default. Two quick presses select the word at the cursor and three select the line, on the same 350 ms window the Shift key uses for caps lock. Turn it off if you flip the mode on and off faster than that and don't want a word selected on the way past.
Both switches cover the panel's Select key as well as the toolbar tool. The two buttons count their taps separately, so alternating between them never reads as a double tap.
Dragging the tool to move it works with either switch on: once your finger travels far enough to read as a drag, the mode ends and the tool picks itself up as usual.
### Numpad layout
Tools / Numpad / Options
**Calculator-style layout**: off by default. It puts 7 8 9 on the top row like a desk keypad instead of 1 2 3 like a dialer. It only reaches the shipped pad: a pad you laid out yourself is drawn as you laid it out.
**Panel layout**: the pad is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) like the text editing pad, so it has a shared grid under Key layouts → Panel layouts, a tab in every key layout's editor for a grid of that layout's own, and with them a theme, a "keep this layer open" switch, press-and-hold alternates on its keys, and any key the Action picker offers. What the keyboard draws, in order: the typing layout's own Numpad tab, the shared Numpad layout if you changed it, the layout's own **Number** layer if it authored one (key for key, in the order it drew them), and last the shipped pad in the digit order the switch above asks for.
### Undo/redo shortcut
Tools / Undo / Options
**Redo sends Ctrl+Y**: off by default, so Redo sends Ctrl+Shift+Z. The same toggle appears on both the Undo and Redo tool's own settings screen. Both are standard redo shortcuts, and which one an app responds to varies. If redo does nothing, flip this.
### Press and hold shortcuts
Key press / Press and hold shortcuts
One **Hold shortcuts** row with six chips, all on by default. Each one puts its command at the end of that letter key's press-and-hold popup, after the accents:
- **Select all**
- **Copy**: copies the current selection. If nothing is selected, it selects everything first.
- **Cut**: the same rule as copy, but it cuts.
- **Paste**: pastes the clipboard at the cursor, replacing any selection.
- **Undo**: the same shortcut as the toolbar's Undo tool.
- **Redo**: the same shortcut as the toolbar's Redo tool.
A **Shortcut keys** row sits under them. A, C, V, X, Z and Y are the QWERTY answer, so on a layout with no Latin letters that row is where you move the six onto keys you actually have.
This is the only place cut lives: there's no Cut button in the text-editing panel itself, and no Cut toolbar tool.
## Details & edge cases
- **Shift on a live selection re-cases it, and the keys follow.** Lower case puts Shift down, Title case arms it, UPPER turns the lock on, so the case you just gave the selection is the case the next thing you type comes out in. There is no need to unselect, press Shift again and reselect. It re-cases rather than switching Shift off, with one exception: a Shift that's currently holding the selection open. There the first tap is read as putting the modifier down, so a selection you dragged out with Shift can always be ended with the same key; the tap after that re-cases. That's the "Tapping Shift with text selected cycles its case" toggle in the next bullet, on by default; turn it off and Shift goes back to being a plain Shift.
- **Shift can change the case of a word with nothing selected.** Two switches sit under the selection one on the Typing screen, both off by default. With **Shift changes the case of the word before the cursor** on, Shift with the cursor right after a word steps that word through lowercase, Title Case and UPPERCASE, the way SwiftKey does: `cursor| touching`. **Shift changes the case of the word around the cursor** does the same with the cursor inside a word: `cur|sor`. A word that only starts at the cursor is left alone. The cursor stays where it was, and the Shift key follows the word's case, so UPPERCASE leaves caps lock on for what you type next. While you type a word, Shift is still for the next letter.
- **A case change never changes the spelling.** UPPER CASE turns `ß` into the capital `ẞ`, not `SS`, so `Spaß` goes to `SPAẞ` and back down to `Spaß` again. The same holds for a word glided with caps lock on or shouted through the Shift key. A letter with no single capital form, such as the `ff` ligature, stays as it is.
- **A selection the keyboard sets is brought into view.** Find on the selection bar, and the Find & replace panel, select the match in the field. Many editors leave a selection set from outside where it is, under the keyboard or off the screen, so the keyboard nudges the selection a step and back, which makes the editor scroll to it. The same nudge puts the cursor back on screen when the keyboard grows under it, for example when the suggestion strip opens for a word you tapped. Only a plain cursor gets it, and only once for the same spot: a selection you made yourself, Select all included, is left exactly as it is when the selection bar opens under it, and in a web page the selection bar doesn't try to scroll it either.
- **Two Typing-screen toggles change what Select mode does elsewhere on the keyboard**, even though neither is on this page's own settings screens. Typing a bracket or quote with text selected wraps the selection in the pair instead of replacing it (on by default). Either half works: `(` and `)` both turn a selected "abc" into "(abc)". Tapping Shift with text selected cycles its case instead of arming Shift for the next letter (also on by default, and a no-op in caseless scripts like Bengali). Both live under Typing / Automatic corrections.
- **Every tool on this page works before you've unlocked your device for the first time after a restart** (direct boot). The panel, the cursor tools, undo/redo and the numpad only touch the input connection, never stored data, so none of them wait on your device being unlocked. Compare [Media controls](https://wmkeyboard.pages.dev/tools/media/), which can't.
- **Nothing here is build-gated.** The panel, every cursor tool, undo/redo, and the numpad all work identically in every build.
- The hardware [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/) reaches Text editing (X) and Numpad (N) as panels, and fires Undo (Z) directly. There's no leader-key letter for Redo or for any individual cursor tool.
- All copy and paste actions here go through the target app's own text-selection handling, not a WM-Keyboard-side clipboard bypass (see [Clipboard manager](https://wmkeyboard.pages.dev/tools/clipboard/) for the separate clipboard history panel these actions feed into).
---
# To-do list
URL: https://wmkeyboard.pages.dev/tools/todo/
Section: Tools
Summary: A to-do list inside the keyboard: add and tick tasks, open a task to its steps, edit with a long press, and sync with a CalDAV task list through DAVx5 if you want to.
The **To-do list** tool keeps your tasks in the keyboard, a tap away from whatever app you are writing in. Each task has a box to tick and can hold steps under it: "Plan the trip" with "Book the train" and "Pack" below, each ticked on its own. The same list is in the settings app, where there is room to pick any date. Both write one file, so a task added on the keyboard is in the app when you get there, and the other way round.
If you already keep your tasks on a CalDAV server, such as Nextcloud, the list can sync with one of its task lists through DAVx5. That is off until you set it up; see [Sync with a task app](#sync-with-a-task-app).
## Opening it
Tap **To-do list** on the toolbar, or in the toolbox if it isn't pinned (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). Tap it again to close it. Typing `todo` or `tasks` into a text field offers the tool as a chip on the suggestion strip, like every other tool keyword.
In the settings app the list is one tap from the tool's own page:
Tools / To-do list / Your tasks
**Add to home screen**, under it, puts an icon for the list on your home screen, which opens it straight away.
## The panel
Each task is a card: a round box to tick at the left, the task's words, its date under them when it has one, and at the right a small ring with "2 of 5" for a task with steps, then an arrow. Cards arrive one after another as the panel opens and squash a little under your finger. A ticked task fades to struck-through and slides down below the open ones, where it sits dimmed.
Anything overdue or due today leaves the column for a **Due** row at the top, where the tasks wait as wide cards you scroll sideways. A card due today is washed with the theme's accent, an overdue one with a warmer amber, each with a stripe of that colour along its edge and the date written in it. The row is there only while something is due.
An **Add a task…** pill floats along the bottom. Tap it, or its **+**, to start a new task; it steps aside while the Undo bar is up. The header says what is left, **3 to do** or **All done**, and holds **Hide done** and **Clear done** once anything is ticked, the sync button when a task app is set up, and the tool's settings.
## Adding, ticking and deleting
The **Add a task…** pill opens a box in the header, and the key rows come back underneath so you can type into it. Enter adds the task and leaves the box open and empty for the next one, so a whole shopping list goes in without a tap between items. **Done** adds what is in the box and gives the keys back to the app.
Tap a task's box to tick it. The box springs shut, the words fade to struck-through, and the card sinks below the open ones. Ticking a task ticks the steps still open under it, and clearing a step's tick clears its task's tick too, since the task isn't finished after all.
Press and hold a task to edit it. Its words go into the box in the header, and the card stays under it with a row of chips inside: the quick dates, then **Repeat**, **Delete**, **Type it** (which types the task into the field behind the keyboard), **Add a step** and **Cancel**. A tap on the card puts the edit away. The quick dates are **Later today** (at least three hours on, at the next full hour, while that is still today), **This evening** (18:00, until then), **Tomorrow** and **Next week** (09:00 tomorrow, and 09:00 next Monday), and **No date** on a task that has one. A tap keeps any change to the words too. A delete shows an Undo bar for a few seconds. **Clear done** in the header deletes every ticked task and step at once, with the same Undo.
**Hide done** in the header leaves the ticked tasks out for this visit; Tools / To-do list / Hide finished tasks in the keyboard makes the panel always open that way.
## Steps
Tap a task's card to open it. Its steps unfold inside the card, each with its own smaller box, and **Add a step** at the end opens the box for a new one, which Enter adds like a task. A task with steps shows a ring of how many are ticked, "2 of 5", and the arrow turns while it is open. Tap the card again to fold it shut. A card in the Due row opens the same way. Steps go one level deep: a step can't hold steps of its own.
## The list order
Tasks overdue or due today sit in the Due row. Below it, open tasks come first, the soonest due at the top and the ones with no date after them, newest first. Ticked tasks follow, the one ticked last at the top. Steps stay in the order they were added.
## In the settings app
The list in the app works the same way with a phone-sized touch. The field at the top adds a task. Tap a task to open its steps and the **Add a step** row; tap its box to tick it. Hold a task, or a step, to edit its words, give it a due date or take the date off, or delete it. Swipe a row to the left to delete it; a long swipe deletes it at once, with an Undo. **Clear finished tasks** under the list does what the panel's Clear done does.
A date picked in the app is a day; one from the keyboard's quick dates, or from a task app, has a time as well. The keyboard shows it under the task, in the accent colour on the day and in amber once it has passed.
## Reminders
An open task with a date reminds you when it falls due: a notification with the task's words and a **Done** button that ticks it. A task with a time reminds at that time; a day-only one at 09:00 that day. Tapping the notification opens the tool's page in the settings app. A date moved later reminds again, a ticked task never does, and a date already past when it was set (an old one from a task app) stays quiet.
Reminders need the keyboard's notifications allowed. To turn them off, or change how they sound, use the **To-do reminders** channel in the system's notification settings for WM Keyboard. Android 12 and later may deliver one a few minutes late unless the app is allowed to set exact alarms, the same grant the [clock's alarms](https://wmkeyboard.pages.dev/tools/clock/) ask for. After a restart they come back once the phone is unlocked, and one that fell due while it was off shows then.
**On the suggestion strip.** A task that has fallen due also waits on the suggestion strip, once the keyboard next comes up. Tap its words to open the list. The tick marks it done, the clock puts it off for an hour, and the cross closes the chip until the task has a new date. Tools / To-do list / Show due tasks on the suggestion strip turns it off.
## Repeating tasks
Hold a task and tap **Repeat** to step through every 30 minutes, every hour, day, week, month and year, then off. A task with no date yet is first due one step from now. Ticking a repeating task doesn't finish it: its date moves to the next time and its steps open again. Until you tick it, it reminds you each time it comes round.
Repeats stay on this phone's list. A task app you sync with sees only the task's current date.
## Sync with a task app
Tools / To-do list / Sync with a task app
Off by default. On, the list stays the same as one task list in **OpenTasks** or **Tasks.org** on the phone, and DAVx5 syncs that task list with your CalDAV server. WM Keyboard never connects to the server itself. It reads and writes the task app's list on the phone, and DAVx5 carries the changes on its own schedule.
To set it up:
1. Install DAVx5 and either OpenTasks or Tasks.org, and add your CalDAV account in DAVx5 with its task lists ticked.
2. Turn on **Sync with a task app**. A dialog lists the task apps on the phone.
3. Tap **Allow access** for the app you use. The keyboard says what the permission covers before Android asks: the permission reaches every task list in that app, and the keyboard opens only the one you pick.
4. Pick the task list.
The first sync runs straight away. After that the keyboard syncs when the panel opens and a moment after each change you make, and the sync button in the panel's header runs one on demand. **Task list** on the tool's page changes the list, and **Sync now** runs a sync and says how many tasks came in, went out and were removed.
What travels, both ways: each task's words, its tick, its due date, and which task a step belongs to. A delete on either side deletes on the other. When the same task changed on both sides since the last sync, the later change wins. A reminder set in the task app stays in the task app. Turning the sync off leaves both lists as they are. **Delete all tasks** on the tool's page empties only the keyboard's list: the task app keeps its tasks, and the next sync brings them back.
**Remove copied tasks**, under **Sync now**, deletes every task that repeats an older one word for word, with the same tick and due date, and keeps the oldest of each. The copies go from the task app too, and DAVx5 deletes them from your server on its next run. Up to 0.6.5, a task added in the keyboard could be sent to the task app again on every sync, so a list could fill up with copies of the same tasks; this cleans that up, and the sync no longer makes them.
jtx Board, the third task app DAVx5 works with, isn't supported. Its provider only answers apps that say they are a sync adapter, and a change written that way is marked as already synced, so DAVx5 would never send it to the server.
## Coming from the Todo List plugin
The tool replaces the Todo List plugin from the add-on repository. If the plugin is installed and holds tasks, the tool's page shows:
Tools / To-do list / Import from the Todo List plugin
It copies the plugin's tasks, their ticks and their due dates into the list, and skips any task already there with the same words, so running it twice doesn't double anything. The plugin keeps its own copy and its reminders, and keeps working until you remove it.
## Backups
The to-do list is a section of the [backup file](https://wmkeyboard.pages.dev/privacy/backup/), on by default for the automatic backup and for sync between your phones. Every task and step travels in it, ticked or not. A restore replaces the list, and a keyboard already running reloads it rather than saving its own copy back over it. The task-app sync keeps a small record of its own on the phone, which never goes into a backup: it names rows of this phone's task app, which mean nothing on another one.
## Privacy
The list stays on the phone unless you back it up or turn on the task-app sync, and then it goes only where those send it. The keyboard never learns from a task: what you type into the box does not reach your personal dictionary, your word pairs or your typing statistics. The tool is not available before the first unlock after a restart, when the keyboard has no access to its files.
---
# Trackpad
URL: https://wmkeyboard.pages.dev/tools/trackpad/
Section: Tools
Summary: Turn the key area into a trackpad: drag to move the cursor, hold and drag to select, two fingers for words.
Placing the cursor by tapping the text is the least precise thing a touchscreen asks of you. The **Trackpad** tool takes the key area over and turns it into a laptop-style pad: put a finger down anywhere and the cursor moves by how far you drag, a character at a time sideways and a line at a time up or down. Hold first and the same drag selects. It is the same idea as the [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/), with the whole keyboard to move across instead of one key.
tools/trackpad-panel — The trackpad panel: the pointing surface over an abc / space / backspace row. The trail and crosshair follow your finger.
## Opening it
Tap **Trackpad** on the toolbar, or in the toolbox if you haven't pinned it (see [the toolbar](https://wmkeyboard.pages.dev/tools/overview/)). The tool lights up while the panel is open, and tapping it again, or the abc key on the panel's bottom row, puts the keys back.
**Press and hold** the tool instead and the trackpad is open only for as long as your finger stays down. Drag on it with your other thumb, let go of the tool, and you are back on the keys. This is the one for a quick nudge in the middle of typing. A hold over a trackpad you already opened with a tap does nothing, so that panel stays put when you let go.
## Gestures
| Gesture | What it does |
|---|---|
| One finger drag | Move the cursor. Sideways is characters, up and down is lines |
| Hold, then drag | Select. Selection mode is on until you lift the finger |
| Two taps | Select the word at the cursor |
| Three taps | Select the whole line at the cursor |
| Two finger drag | Move the cursor a word at a time |
| Two finger tap | Type a space |
The pad is relative. Where you put your finger down doesn't matter, only how far it travels from there, so you can start a drag anywhere on the surface, lift, and start again. A slow drag that covers less than a step per frame still adds up: the surface keeps the fraction and fires the step once the travel reaches it, and dragging back the way you came unwinds that fraction rather than firing a move.
Every move is a real arrow key sent to the field, the same event a hardware keyboard would send, through the same handler the [text-editing panel](https://wmkeyboard.pages.dev/tools/text-editing/) uses. That has two consequences. The cursor behaves exactly as the app you're typing in wants it to, and the trackpad obeys whatever selection mode is armed, from wherever it was armed. Turn on the toolbar's [Selection mode](https://wmkeyboard.pages.dev/tools/text-editing/#selection-mode) tool, or the panel's Select key, and every trackpad drag extends the selection until you turn it off again.
**Hold, then drag** is the short form of that. The hold arms selection mode, the drag extends the selection, and lifting the finger disarms the mode. What you selected stays selected: the selection lives in the editor and nothing on the keyboard collapses it. Move the cursor again with a plain drag and it collapses, the way an arrow key collapses a selection anywhere else. A word or line picked up by two or three taps also stays until the next plain move, so tap twice and then hold-drag to grow the selection from the word outward.
**Two fingers** move by words: Ctrl+Arrow to the field, in the direction you drag. Both fingers down and up together with no travel is a space, so you can separate words without leaving the pad. A third finger counts as two.
## Visual feedback
The tool's button is lit on the toolbar while the panel is open, however it was opened. On the surface, a trail follows your finger and a crosshair marks where it is now; both go the moment you lift. The trail can be turned off under [Options](#options). A faint hint in the middle of the surface fades while you are touching it.
While you drag, a **magnifier** shows the line of text around the cursor, enlarged, in a bubble over the text, with the cursor in its middle. It comes up when a drag starts or a hold arms selection, and it follows the cursor from line to line. Any selection shows highlighted in it, and the cursor in it is the end of the selection that is moving. A plain tap never shows it. The bubble is the keyboard's own drawing of the text the app hands it, not a zoomed picture of the screen, so it uses the keyboard's colours and font. It sits above the cursor, or below it when the cursor is too close to the top of the screen, for apps that tell the keyboard where their cursor is, which most do. For an app that doesn't, or when the cursor is scrolled out of sight or hidden behind the keyboard, it sits just above the keyboard instead. In a password field it shows dots, as the field does, never the password itself. With a long selection (more than about a thousand characters, such as after Select all) it shows the text past the moving end, with the selected side tinted, rather than reading the whole selection back on every step. It never takes a touch and it goes the moment you lift your finger.
## The panel is a layout
The trackpad is a [panel layout](https://wmkeyboard.pages.dev/languages/custom-layouts/#panel-layouts) like the emoji, clipboard and text-editing panels. Out of the box it is the pointing surface over an abc, space and ⌫ row. Open the layout editor from the tool's page (or from **Panel layouts** in the layouts gallery) to change the row, add keys beside the surface, give the surface more or less height, or put a text-editing key on it. The surface itself is the one component the layout cannot lose: a layout without it gets one added back.
## Options
Tools / Trackpad / Options
**Horizontal sensitivity**: default **12 dp**, adjustable from 4 to 48. How far your finger travels for each character. Lower is faster.
**Vertical sensitivity**: default **28 dp**, adjustable from 8 to 96. How far your finger travels for each line. It starts coarser than the horizontal step on purpose: a line is a bigger jump than a character, and a finger dragging sideways drifts up and down more than it means to. Two fingers move a word for every two horizontal steps of travel.
**Press and hold to open**: on by default. Holding the tool on the toolbar opens the trackpad until you let go. Turn it off and a hold there opens the tool's settings page instead, the way a hold on any other toolbar tool does, or runs whatever second action you bound to it. Dragging the tool to move it works either way: once your finger travels far enough to read as a drag, the panel closes and the tool picks itself up as usual. While the toolbox is open the hold does not open the trackpad at all, so the toolbox stays on screen and you can drag the tool off the toolbar and back into the grid. A still hold there opens the tool's settings page.
**Two taps select a word**: on by default. Two quick taps on the surface select the word at the cursor and three select the line, on the system's double-tap window and within a thumb's width of each other. Turn it off if your drags tend to start with a tap you didn't mean.
**Vibrate on each move**: on by default. A short tick for every character or line the cursor moves.
**Show the trail**: on by default. Draws your finger's path and a crosshair while you drag.
**Magnifier**: on by default. Shows the text at the cursor, enlarged, while you drag (see [Visual feedback](#visual-feedback)). Turn off **Follow the text** under the spacebar's **Cursor magnifier** and it stays still above the keyboard instead of moving with the cursor. The [spacebar cursor swipe](https://wmkeyboard.pages.dev/typing/gestures/#moving-the-cursor-with-a-spacebar-swipe) has a separate switch for the same bubble.
**Panel layout** opens the layout editor described above.
## Details and edge cases
- The trackpad is a panel, so opening it closes whatever panel was open and puts down any pending shift, Ctrl or Alt.
- The bottom row's space is an ordinary spacebar, swipes included, so the language switch and the spacebar cursor swipe still work from inside the panel.
- The panel never goes full-bleed, because the toolbar has to stay for the hold gesture that may have opened it.
- With a hardware keyboard, the panel has no focus ring: it is a pointing surface with nothing to arrow between, like the handwriting canvas. Bind a leader letter to open it under Typing / Physical keyboard; the tool ships without one because every letter is taken.
- The trackpad works before the first unlock after a restart, since it only touches the field it is typing into.
- If the keyboard closes while you are holding the tool, the hold ends with it; there is no release to wait for.
---
# Transcription server
URL: https://wmkeyboard.pages.dev/tools/transcription-server/
Section: Tools
Summary: Dictate through a speech-to-text server you run: speaches, whisper.cpp, or any OpenAI-compatible service.
The transcription server is another dictation engine, next to the system recognizer, [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/), [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/), [offline Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), [offline Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) and [offline Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/). The keyboard records what you say and sends the recording to a server you choose. The server sends back the text. The heavy work runs on a computer on your desk or in your rack, not on your phone's battery, and you choose the model.
It works in every build, F-Droid's included. The phone needs only a small library (about 300 KB) that compresses each recording to about a twelfth of its size before it is sent, fetched the first time you dictate with this engine. Until it is there, recordings go uncompressed, and dictation works the same. See [How much it sends](#how-much-it-sends).
## Setting it up
Voice typing
1. Under **Recognition engine**, press **Add a provider** and pick **Transcription server**.
2. In the sheet that opens, fill in **Server address**. For a server on your own network it looks like `http://192.168.1.10:8000/v1`. An address has no spaces, so if the field turns red and says so, something was pasted along with the address, such as the line of a provider's docs that follows it. Remove everything after the address.
3. Fill in **Model** if your server needs one (see the recipes below). Fill in **API key** if it asks for one, and **Endpoint path** for a server on a route of its own.
4. Press **Test**. The keyboard sends one second of silence and says whether the server answered. If it fails, it shows the same message a failed dictation would show. Then press **Add**.
5. Drag the server to the top of the list to make it the engine for every language, or give it only some languages under **Engine for each language**.
You can add more than one server, each with its own address, key and model, and switch one off without losing it. Dictation sends to the first server on the list that is on.
Dictation then works the way it does with offline Whisper. Speak, then press the microphone when you are done (or let the 30-second clip fill, or set **Stop after a pause** under Voice typing and let a pause end it). The phrase lands a moment later, with [spoken punctuation](https://wmkeyboard.pages.dev/tools/voice/), spacing and undo applied as usual. **Keep listening** chains the next phrase the same way.
Press a server's row to open its screen: **Setup** reopens the sheet, and **Send the keyboard language**, the prompt and the server languages are there too. **Send the keyboard language** is on by default. It tells the server the language of your active layout, which is faster and more accurate than letting it guess from a short clip. Turn it off if you dictate in two languages on one layout and want the server to detect which one you are speaking. With a [Voice typing language](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine) picked, the server is told that language instead, and with several picked it's told none and detects it.
## The address
Type the server's API root, up to and including the version segment, the same way the AI tool's [Other service](https://wmkeyboard.pages.dev/tools/ai/#any-other-openai-compatible-service) asks for it. The keyboard adds `/audio/transcriptions` itself.
| You type | The keyboard sends to |
|---|---|
| `http://192.168.1.10:8000/v1` | `http://192.168.1.10:8000/v1/audio/transcriptions` |
| `http://192.168.1.10:8000` | `http://192.168.1.10:8000/v1/audio/transcriptions` (a bare address gets `/v1`) |
| `https://api.groq.com/openai/v1` | `https://api.groq.com/openai/v1/audio/transcriptions` |
| `http://192.168.1.10:8080/inference` | used as it is (whisper.cpp's own endpoint) |
An address that already ends in `/transcriptions` or `/inference` is used as typed.
### A server with its own route
Some servers take the same request on a route of their own, such as `POST /v1/transcribe`. Put that route in **Endpoint path**, under **Server address**. When it is filled in, the keyboard adds it to the address exactly as typed, with no guessing and no `/v1`:
| Server address | Endpoint path | The keyboard sends to |
|---|---|---|
| `http://192.168.1.10:8000` | `/v1/transcribe` | `http://192.168.1.10:8000/v1/transcribe` |
| `http://192.168.1.10:8000/v1` | `transcribe` | `http://192.168.1.10:8000/v1/transcribe` |
| `http://192.168.1.10:8000` | `https://asr.example.com/api/stt` | `https://asr.example.com/api/stt` (a whole address replaces the server address) |
Leave **Endpoint path** empty for the usual `/audio/transcriptions`. The request itself is the same either way; see [The protocol](#the-protocol).
## A model or server per language
Some models are much better at one language than another. With two or more languages enabled, a **Per language** group appears in the **Transcription server** group under the engine list, with one row per language. Each row says what that language uses. Tap it to change it:
- **Same server, another model.** Leave **Server address** empty and fill in **Model**. The language uses the main server (the first one on the list), its route and its key, with this model instead of the main one. Leave **Model** empty too and the language goes back to the main server.
- **A server of its own.** Fill in **Server address**. **Endpoint path** and **API key** appear for that server. Nothing is borrowed from the main server: the main key is never sent to a different address, and an empty **Model** lets this server pick its own.
**Test** in the dialog sends one second of silence to what the dialog holds, before you save. **Use main server** clears the language's entry.
Dictation picks the server from the language of the layout you are typing on, the same way [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) picks a model. **Send the keyboard language**, **Prompt** and the words to listen for apply to every server. A language's own API key is left out of backups unless you choose to include API keys, like the main one.
## Running a server
The keyboard does not use a protocol of its own. It speaks OpenAI's audio transcription API, which most speech-to-text servers already support. Any of these works.
### speaches (faster-whisper)
[speaches](https://github.com/speaches-ai/speaches) runs faster-whisper behind an OpenAI-compatible API. On a machine with Docker:
```bash
docker run --detach --publish 8000:8000 --name speaches \
--volume hf-hub-cache:/home/ubuntu/.cache/huggingface/hub \
ghcr.io/speaches-ai/speaches:latest-cpu
```
Use the `latest-cuda` image with `--gpus=all` on a machine with an NVIDIA GPU. speaches only runs models you have downloaded, so fetch one once:
```bash
curl -X POST http://localhost:8000/v1/models/Systran/faster-whisper-small
```
Then set:
- **Server address**: `http://:8000/v1`
- **Model**: the id you downloaded, here `Systran/faster-whisper-small`. For English only, `Systran/faster-distil-whisper-small.en` is faster.
### whisper.cpp
[whisper.cpp](https://github.com/ggml-org/whisper.cpp) ships `whisper-server`, a single binary with no Python. Download a model and start it on all interfaces:
```bash
whisper-server -m models/ggml-base.bin -l auto --host 0.0.0.0 --port 8080 \
--inference-path /v1/audio/transcriptions
```
With `--inference-path` as above, set **Server address** to `http://:8080/v1`. Without it, point the keyboard at `http://:8080/inference`. Leave **Model** empty: whisper-server uses the model it was started with. `-l auto` matters when **Send the keyboard language** is off. Without it whisper-server assumes English.
whisper-server reads WAV, MP3 and FLAC on its own. Add `--convert` (it needs `ffmpeg` on the computer) and it also reads the keyboard's compressed recordings, which are a twelfth of the size. Without it, the first phrase tells the keyboard so, and that server gets WAV from then on. An address ending in `/inference` always gets WAV.
### Cloud services
The same engine reaches the hosted APIs, if you would rather pay per minute than run a machine:
| Service | Server address | Model |
|---|---|---|
| Groq | `https://api.groq.com/openai/v1` | `whisper-large-v3-turbo` |
| OpenAI | `https://api.openai.com/v1` | `whisper-1` or `gpt-4o-mini-transcribe` |
Both need an **API key**. LocalAI and other OpenAI-compatible gateways work too.
## The protocol
This is everything the keyboard sends and expects, so you can build or proxy a server of your own.
**Request.** `POST ` with `Content-Type: multipart/form-data`, and these parts:
| Part | Sent | Contents |
|---|---|---|
| `file` | always | the clip, up to 30 seconds, as `speech.ogg`, `audio/ogg`: Opus in Ogg, 16 kHz mono at about 20 kbit/s (about 75 KB for 30 seconds). As `speech.wav`, `audio/wav` (16-bit PCM, 16 kHz, mono, under 1 MB) instead while the compression library is not on the phone, to an address ending in `/inference`, and to a server that could not read the Opus file |
| `model` | when **Model** is filled in | the text of the field |
| `language` | when **Send the keyboard language** is on and Whisper knows the language | an ISO-639-1 code such as `en`, `bn` or `de` |
| `prompt` | when there are words to listen for, or **Prompt** is filled in | your words to listen for, then the text of **Prompt**. See [Words and the prompt](#words-and-the-prompt) |
| `response_format` | always | `json` |
An `Authorization: Bearer ` header is added when **API key** is filled in. Clips shorter than 0.3 seconds, and clips with nothing louder than a quiet room in them, are never sent. See [A silent clip types nothing](https://wmkeyboard.pages.dev/tools/whisper/#details--edge-cases).
**Reply.** HTTP 200 with `{"text": "the words"}`. Other fields are ignored. A plain-text body is accepted too, and is used as the transcript.
**Errors.** A 400, 415 or 422 answer to an Opus file is taken to mean the server cannot read Opus: the same phrase is sent again at once as WAV, and once a WAV is read, that address gets WAV for as long as the keyboard keeps running. Otherwise any status outside 200–299 fails the phrase. When the body is `{"error": {"message": "…"}}`, that message is what the voice panel shows. Otherwise the panel shows the keyboard's own wording for the status (401 and 403 read as a rejected key, 429 as a rate limit).
Timeouts are 10 seconds to connect and 60 seconds for the reply.
## Words and the prompt
A server gets the [words to listen for](https://wmkeyboard.pages.dev/tools/voice/#words-to-listen-for) inside OpenAI's `prompt` field, followed by whatever you type into **Prompt**. The model reads the prompt as the text that came before your clip, so a name in it is a name the model has just "seen" spelled your way. The **Prompt** field is also where to set a style, such as "Casual chat between friends." or a sentence in the spelling you want.
Whisper models, which is what most servers run, read only the last 224 tokens of the prompt, and drop the rest without saying so. That's a limit of the model: Whisper's decoder has room for 448 tokens and gives half of them to the prompt. 224 tokens is roughly 150 short English words, fewer for long names or for scripts that take several tokens a character, so it can't hold a whole dictionary. Newer models such as OpenAI's `gpt-4o-transcribe` read far more.
So the keyboard builds the prompt for that limit. It sends at most 150 words, and puts them in order of importance with the most important at the end, where a Whisper model keeps them: your **Prompt** text last, then your own list, then the words you added by hand and the personal dictionary, and the learned words at the front. When a Whisper server has to cut, it cuts the learned words first. The whole prompt is capped at 4,000 characters.
## Privacy
With this engine, the recording of each phrase leaves your phone. It goes to the address you typed, or the one you set for the language you are typing in, and nowhere else, and only after you start dictating. Nothing is sent in the background. The API keys are stored with your other settings and are left out of backups unless you choose to include API keys. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/).
An `http://` address sends the audio unencrypted. That is fine on your own network. Use `https://` for anything reached over the internet.
With **Listen for my words** on, the prompt carries up to 150 of your own words, as described in [Words and the prompt](#words-and-the-prompt). Turn it off, and leave **More words to listen for** and **Prompt** empty, and no prompt is sent at all.
Voice typing stays off in password fields, whichever engine is chosen.
## Details & edge cases
- **Mobile data.** [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) has a **Transcription server** row. It is allowed by default. Set it to **Ask each time** and the first phrase on mobile data is held with a note. Pressing the microphone again sends it and allows the rest of that session. **Turn off** stops the engine on mobile data.
- **No address yet.** Choosing the engine without filling in the address makes the voice panel say so, with a shortcut to these settings and a button that switches back to the system recognizer.
- **Server unreachable.** The panel shows why: no connection, a timeout, or a server that refused the connection. Nothing is typed and the rest of your text is untouched. Press the microphone to try again. With [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) on and a [Whisper model](https://wmkeyboard.pages.dev/tools/whisper/) downloaded, the phrase is transcribed on the phone instead, and with no connection at all the next phrase goes to the Whisper model, or to the voice typing of your phone, from the start.
- **No live words.** Like offline Whisper, the server gets the whole phrase at once, so nothing appears in the field while you speak.
## How much it sends
Each phrase goes as Opus, the codec voice calls use, at about 20 kbit/s: around 75 KB for 30 seconds of speech, where the same recording as WAV is just under 1 MB. On 73 LibriSpeech recordings (8 minutes) the Opus files were 12.7 times smaller, and a Whisper small.en server got 5.74% of the words wrong from them against 5.65% from WAV: one word more in 1,150.
The compression library is one of the [feature libraries](https://wmkeyboard.pages.dev/privacy/network/#downloads-dictionaries-models-and-addons), fetched the first time you dictate with this engine (from Play on a Google Play build, and on an F-Droid build only after you turn on **Download feature libraries**). Until it is there, the phrase goes as WAV.
[Network activity](https://wmkeyboard.pages.dev/privacy/network-activity/) shows the saving on each request: press a Transcription server row, and **Saved** says how much less was sent than the WAV would have been.
---
# Translate
URL: https://wmkeyboard.pages.dev/tools/translate/
Section: Tools
Summary: Type in one language, read the translation live, and drop it into the field without switching apps. Online, or on the device with no connection.
Translate opens a small typing window right above the keys. It grows with your phone's font size, so the translation under the language chips stays readable at a large text size. Type a sentence, or paste one, watch the translation appear as you go, then send it to your field with a tap. It translates whatever is in the panel's own text box, so it works the same in a chat app, a note, or a search bar. The one thing it reads from your field is text you selected before you opened it.
## What you need
- **The standard GitHub and F-Droid builds:** nothing. Open the tool and type. It uses the free Google endpoint until you change [Providers](#providers).
- **The Google Play build, or the On device engine:** one download. Type something, and when the panel says a model is missing, press **Download** in the panel. It translates as soon as the model lands.
- **The offline build:** translates with Firefox Translations only, from files you [import](https://wmkeyboard.pages.dev/privacy/offline/#translation-models). You need its library and the files of each language direction, fetched on another device. It needs a 64-bit phone with Android 9 or later.
## Using it
Open **Translate** from the toolbar, or from the toolbox if you haven't pinned it (it isn't pinned by default). On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press R.
The panel has its own text box at the top, which wraps onto several lines and scrolls once it holds more than four or so. Enter starts a new line in it rather than closing it. What you type there is what gets translated, not the text already in your field. On a phonetic layout such as Avro, the box spells in your script as you type, the same as a text field does, and Space settles the word. It's the same key-rerouting trick the GIF, sticker, and Wikipedia panels use: while the panel is focused, your keystrokes go to its text box instead of the app behind the keyboard. Opening Translate over a chat you're mid-way through never touches what you already wrote.
Once you've typed, the keys cover most of the translation. Press Back, or tap the keyboard button at the right end of the suggestion strip, and the keys go down: your text stays in the box and the panel takes its full height again, so a long translation shows whole. Tap the box to bring the keys back. See [Putting the keys down](https://wmkeyboard.pages.dev/tools/overview/#putting-the-keys-down).
**Typos in the box.** Since 0.6.6 the box autocorrects as you type, the way a text field does, so a slip doesn't reach the translator as a word it can't read. Space corrects the word you just finished, by the same rules and with the same **Autocorrect** switch and confidence as everywhere else, and ⌫ straight after puts back what you typed. It needs a layout that types letters straight in; on a phonetic layout such as Avro the box spells in your script and isn't corrected.
**Translating text you selected.** Select some text in the field first, then open Translate, and the selection is already in the box with its translation under it. That is the way to translate a message in a language you can't type. The panel reads the selection once, as it opens, and never from a password field. With nothing selected, the box starts empty. The [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/#language) bar has a **Translate** chip that does the same in one tap.
**Translating text in any app.** Text you cannot type into, such as a web page or a message you were sent, can be translated without copying it. Select it and pick **WM Keyboard** from the menu over the selection, then **Translate**, or turn on **WM Translate** to have an entry that translates straight away. The translation uses this tool's target language, engine and services, and can replace the selection in a box you can edit. See [In any app's selection menu](https://wmkeyboard.pages.dev/smart/selection-actions/#in-any-apps-selection-menu).
**Pasting into the box.** The clipboard icon in the box's top corner pastes the text you copied last, without opening the clipboard. An image on the clipboard is skipped. If you weren't typing in the box, the translation runs straight away and stays on screen with the keys out of the way; if you were, the text goes in at the cursor and you carry on typing. You can also long-press the box where there is no word (an empty box anywhere) and a **Paste** bar comes up at the caret, with **Select all** beside it once the box holds text. Long-press a word to select it instead, and the bar offers **Cut**, **Copy**, **Paste** and **Select all**. The toolbar's **Paste** tool, and a layout's Paste key, paste into the box too while it has the keys. This works in every one of the keyboard's own text boxes, not only this one; see [A captured box is a real text field](https://wmkeyboard.pages.dev/tools/overview/#a-captured-box-is-a-real-text-field).
**Clearing the box.** Once the box holds text, an ✕ sits beside the clipboard icon. It empties the box and its translation and hands the keys back to the box, so after inserting one translation you can start typing the next message straight away. To have **Insert** do that for you, turn on **Clear after inserting** under Tools / Translate (off by default): the translation goes into your field and the box is empty and ready for the next one.
The header reads left to right like a sentence: *from* this language, *to* that one, *using* this engine.
- **Source language** is the first chip. It starts on **Detect language** and switches to the language it found once a result comes back, for example "Bengali". Tap it to pick the source yourself when the detection is wrong, which mostly happens with a word or two. Your pick lasts until you close the panel. Pick **Detect language** to hand it back.
- **Swap** is the arrows between the chips. It turns the translation round: the result becomes the text, its language the source, and the language it was read as the new target. It lights up once there is a result to swap.
- **Target language** is the second chip. Tap it to open a dropdown of all 68 languages and pick one. You can also change it from the keys: type a slash and a language code at the start of an empty box, then Space. `/ja` makes Japanese the target and `/bn:ja` translates from Bangla to Japanese. The box empties, ready for the text. A code no language has stays as ordinary text.
- **Engine** is the chip on the right: **Online**, **On device** or **Automatic**. See [On-device translation](#on-device-translation) below. It shows wherever an on-device engine runs: every build on a 64-bit phone with Android 9 or later, and every build but F-Droid's everywhere. The offline build has only the device, so it shows no chip and uses Firefox Translations.
- The translation updates automatically about 400 milliseconds after you stop typing. Pressing return translates immediately, and changing the target language re-translates what's already in the box right away.
- Once a translation is showing, two actions light up below it:
- **Insert** types the translation into your field at the cursor, leaving whatever was already there in place.
- **Replace text** swaps out the field's *entire* contents for the translation. Use this when the field only holds the sentence you were translating and you want a clean swap rather than an insertion. When the panel opened on a selection, it replaces only that selection, as long as the text is still selected. If you have since moved off it, the translation goes in at the cursor instead, so the text around the passage is never lost.
While you type, the panel shrinks to leave room for the keys, and a long text gives way first: the box shows three lines, then scrolls, so the language chips, **Replace text** and **Insert** always stay on screen below it. While your text is shorter than that, the translation gets the spare room. With the keys away, the box shows up to five lines.
On a phone turned sideways the panel is short, so it rearranges rather than stacking: the language chips, **Replace text** and **Insert** move up into the panel's header, and the text you type sits on the left with its translation beside it on the right. While you type, with the keys back under the panel, there is only room for one row, so that row holds your text, its translation, **Replace text** and **Insert**, and the language chips move below it when the screen has space for them.
Long input is capped: anything past 2,500 characters is silently cut off before it's sent, with no on-screen warning that it happened.
Hardware-keyboard focus-ring navigation reaches the whole panel: Tab and arrow-key cycling take in the header chips and the swap button, the open menu's rows, and the action buttons (or the download button while that is the offer), the same way they do in the GIF or search panels. One rough edge is left. Esc while a menu is open closes the whole panel rather than just the menu, so press Enter on the chip to bring the list back.
## On-device translation
The keyboard can translate on the phone, with no connection and without the text leaving the device. Three engines can do it:
- **ML Kit** google is Google's. It has one model per language, about 30 MB each, and comes in the builds that carry ML Kit.
- **Firefox Translations** is Mozilla's Bergamot engine with the models Firefox translates web pages with. It is free software and needs no Google services, so it is the on-device engine of the F-Droid edition and of phones without Google Play services. Every build has it on 64-bit phones with Android 9 or later. Its models are per direction: French → English and English → French are two downloads.
- **NLLB-200** is Meta's No Language Left Behind model, run by the open-source CTranslate2 engine. One download of 630 MB covers 200 languages and translates between any two of them directly, so it does the pairs the other two cannot: Swahili, Amharic, Yoruba, Khmer, or Bengali into Hindi without going through English. It is the slow, heavy one. A sentence takes seconds rather than a fraction of one, and the model holds about 1 GB of memory while the panel is open. Every build has it on 64-bit phones with 6 GB of memory or more. The model is licensed for non-commercial use only, so the keyboard shows that licence before the first download.
All three are rows in the [Providers](#providers) list, in that order by default. The order decides which is asked first, and a pair that one of them has no models for goes on to the next. So with the default list, a pair Firefox Translations has downloaded translates there, a pair it has not goes to NLLB-200 if that is downloaded, and if neither has it the panel offers the smaller Firefox Translations download first. If you downloaded Japanese for Firefox Translations and nothing for ML Kit, Japanese translates with Firefox Translations. Switch a row off to keep an engine out of it, or set **How to use them** to **Choose in the panel** to pick one each time.
Pick the engine from the chip on the panel or under Options:
- **Online** is what Translate has always done: the online [providers](#providers) in your list, such as Google's endpoint, your Cloud key, a LibreTranslate server, [DeepL](#deepl) or [your own translation server](#your-own-translation-server). It knows the most languages and gives the best translations. It is the default on GitHub and F-Droid.
- **On device** never sends the text anywhere. It needs models, downloaded once.
- **Automatic** uses the device whenever the languages are downloaded, and the online service for everything else: a language with no model, a pair you have not downloaded, text the device cannot read. The engine chip's icon shows which one produced the result on screen.
**On Google Play, the engines are downloads too.** The Play edition keeps ML Kit's translator (about 16 MB), Bergamot (about 7 MB) and CTranslate2 (about 3 MB) out of the install, because most people never use them. **On device** is the Play edition's default engine, so the first translation offers the download, and picking **On device** or **Automatic** later asks Google Play for ML Kit straight away. Bergamot comes with its first model, and CTranslate2 with the NLLB-200 model. The panel and the Tools / Translate page show that download and carry on by themselves when it lands, with no restart. If Play cannot deliver it, the panel says so and the button tries again. APKs from GitHub carry ML Kit inside and fetch Bergamot's and CTranslate2's libraries with their first model; the F-Droid edition builds both from source and fetches them once you turn on **Download feature libraries** under [Privacy](https://wmkeyboard.pages.dev/reference/settings/privacy/).
**Models.** When a translation needs a model you do not have, the result area says which and how big, and the action row becomes a **Download** button. The same button cancels while the download runs. When the models arrive, the text you typed is translated at once. Downloads also show in the notification shade, and they carry on if you close the panel. With [data saver](https://wmkeyboard.pages.dev/reference/settings/data-saver/) holding downloads on a metered connection, the button asks first and a second press is the yes.
- **ML Kit**: each language is one download, and every model translates to and from English. Bengali to German therefore needs both the Bengali and the German model and goes through English on the way, which costs some accuracy. English itself is part of the engine and is never downloaded.
- **Firefox Translations**: each direction is one download of 13.5 to 36.5 MB (see [the table](#firefox-translations-models) below). A translation needs only the directions on its way: French into English needs French → English, and French into German needs French → English and English → German, run through English in one pass. The engine's own library is listed beside the models as **Firefox Translations engine** on builds that fetch it.
- **NLLB-200**: one download of 630 MB for every language, plus its engine library, CTranslate2, of a few megabytes on builds that fetch it. The first time, the panel shows the model's licence (Creative Commons Attribution-NonCommercial 4.0) above the button, and the button reads **Accept and download**: pressing it is agreeing. Under Tools / Translate the same licence opens as a dialog with **Read licence**. The answer is remembered, and a download that stops part way picks up where it left off.
**Which languages.** The list has 127, and NLLB-200 has all of them. ML Kit has 59: everything in the first 68 except Azerbaijani, Basque, Bosnian, Malayalam, Nepali, Punjabi, Serbian, Uyghur and Traditional Chinese (its Chinese model is Simplified). Firefox Translations has 58: the same 68 except Esperanto, Georgian, Haitian Creole, Irish, Macedonian, Maltese, Nepali, Punjabi, Swahili and Welsh, and it does have Traditional Chinese. The other 59, from Amharic and Armenian to Yoruba and Zulu, belong to NLLB-200 and the online services. With the engine on **On device** or **Automatic**, the language menus mark the languages that need no download with a tick and the ones neither engine on your list can do with **Online only**, and list the downloaded ones first.
**A short list on the device.** With the engine on **On device**, each language menu lists only what it can use now: the downloaded languages (and any on their way), the languages you type in, and the current selection. **All languages**, the last row, opens the rest of that menu, so you can still pick a language and download it from the offer. The next menu opens short again. **Automatic** always lists every language, because it sends the ones you have not downloaded online. Both behaviours are switches under [Options](#options).
**Detecting the source.** None of the engines works out by itself what language it is reading. Where ML Kit is on the phone, its on-device language identifier (bundled, no download) does the detecting for all of them. It is reliable on a sentence and shaky on a word or two. Without ML Kit (the F-Droid and offline editions), two open-source identifiers take its place, together a download of about 2.5 MB fetched the first time a translation detects its source: Meta's fastText, which knows 176 languages and holds up on a few words, and Google's CLD3, which answers when fastText is unsure and tells the languages apart in a text that mixes scripts. Until it is there, Firefox Translations and NLLB-200 make a rougher guess: from the script (Bengali, Korean, Greek, Thai and Khmer each have their own; Ukrainian, Belarusian and Serbian have letters Russian lacks, and Urdu and Uyghur letters Arabic lacks) and, for Latin letters, from the commonest short words of each language, which a sentence of five or more words nearly always has. When neither is sure, the keyboard falls back on the languages you type in, the active one first, and shows the source with a question mark, as in "French?". Tap the chip to correct it. Hindi, Japanese, Russian, Arabic, Greek, Bulgarian or Chinese typed in Latin letters is recognised for what it is and refused on the device, because no model reads it; Automatic sends it online instead.
**What to expect.** On-device translation is good for messages and short text and noticeably less exact than the online service on long, technical or idiomatic text. Line breaks and blank lines survive: each line is translated on its own. The engines let go of their models when you close the panel, since a loaded language pair holds tens of megabytes of memory and NLLB-200 about a gigabyte. NLLB-200 also takes a few seconds to load its model the first time you translate after opening the panel. It translates sentence by sentence, so a long paragraph keeps its end.
### Firefox Translations models
Sizes are what each direction downloads. On the phone it takes about half as much again, unpacked. The models come in two sizes: *tiny* ones of about 14 MB and larger *base* ones of about 24 MB (about 36 MB for Chinese, Japanese and Korean), whichever Mozilla currently ships for Firefox on Android.
| Language | Into English | From English |
|---|---|---|
| Afrikaans | 25.0 MB | 24.8 MB |
| Albanian | 14.3 MB | 14.8 MB |
| Arabic | 24.7 MB | 23.8 MB |
| Azerbaijani | 24.6 MB | 24.1 MB |
| Basque | 24.5 MB | 24.2 MB |
| Belarusian | 24.0 MB | 23.6 MB |
| Bengali | 14.4 MB | 14.2 MB |
| Bosnian | 24.6 MB | 24.0 MB |
| Bulgarian | 23.6 MB | 24.5 MB |
| Catalan | 24.2 MB | 24.6 MB |
| Chinese (Simplified) | 36.2 MB | 36.1 MB |
| Chinese (Traditional) | 36.4 MB | 35.2 MB |
| Croatian | 24.6 MB | 23.8 MB |
| Czech | 24.5 MB | 24.5 MB |
| Danish | 14.2 MB | 14.0 MB |
| Dutch | 24.3 MB | 25.0 MB |
| Estonian | 24.2 MB | 24.1 MB |
| Filipino | 24.8 MB | 24.5 MB |
| Finnish | 24.6 MB | 23.6 MB |
| French | 25.0 MB | 24.6 MB |
| Galician | 24.6 MB | 24.6 MB |
| German | 23.9 MB | 24.5 MB |
| Greek | 14.2 MB | 14.0 MB |
| Gujarati | 14.6 MB | 13.9 MB |
| Hebrew | 14.8 MB | 14.4 MB |
| Hindi | 14.6 MB | 14.2 MB |
| Hungarian | 24.1 MB | 24.0 MB |
| Icelandic | 24.7 MB | 23.7 MB |
| Indonesian | 14.5 MB | 13.9 MB |
| Italian | 25.1 MB | 25.6 MB |
| Japanese | 36.0 MB | 34.9 MB |
| Kannada | 14.9 MB | 14.3 MB |
| Korean | 35.2 MB | 36.5 MB |
| Latvian | 14.4 MB | 25.0 MB |
| Lithuanian | 14.4 MB | 23.9 MB |
| Malay | 14.2 MB | 14.8 MB |
| Malayalam | 14.6 MB | 13.6 MB |
| Marathi | 23.5 MB | 23.8 MB |
| Norwegian | 15.2 MB | 14.5 MB |
| Persian | 14.7 MB | 14.6 MB |
| Polish | 24.2 MB | 23.6 MB |
| Portuguese | 24.2 MB | 24.8 MB |
| Romanian | 14.4 MB | 13.5 MB |
| Russian | 24.1 MB | 22.4 MB |
| Serbian | 24.6 MB | 23.8 MB |
| Slovak | 14.7 MB | 23.9 MB |
| Slovenian | 25.3 MB | 23.6 MB |
| Spanish | 25.1 MB | 24.1 MB |
| Swedish | 14.6 MB | 14.3 MB |
| Tamil | 25.2 MB | 24.2 MB |
| Telugu | 15.1 MB | 13.8 MB |
| Thai | 24.4 MB | 24.0 MB |
| Turkish | 14.1 MB | 14.5 MB |
| Ukrainian | 14.1 MB | 23.8 MB |
| Urdu | 24.0 MB | 23.5 MB |
| Uyghur | 24.8 MB | 25.0 MB |
| Vietnamese | 14.5 MB | 26.1 MB |
The models are Mozilla's, released under the MPL-2.0 like the engine, and pinned in the app by their SHA-256: a file that does not match is never used.
## Providers
Tools / Translate / Providers lists who translates, in order: the on-device engines (**On device (ML Kit)** in every build but F-Droid's, **On device (Firefox Translations)** on 64-bit phones with Android 9 and later, **On device (NLLB-200)** on 64-bit phones with 6 GB of memory or more), then the online services. It works like every other [provider list](https://wmkeyboard.pages.dev/tools/providers/): drag to reorder, switch a row off to skip it, and use **Add a provider** for Google Translate, DeepL, LibreTranslate, a [translation front end](#translation-front-ends) (Mozhi, SimplyTranslate, Lingva or Translite) or your own translation server. Every build offers all of them. A LibreTranslate server, a front end or your own server can be added more than once, each with its own address.
The first provider that answers is used. If one fails (no connection, a wrong key, a language it does not have), the next one that is on is asked. Set **How to use them** to **Choose in the panel** to pick the provider yourself instead: the panel's engine chip becomes a chip naming the provider, and its menu lists the providers that are on, the phone included. Only the one you pick translates. A provider that is off is never asked, so the text goes only to the ones that are on. The engine decides which part of the list is used: **On device** asks only the phone, **Online** only the services, and **Automatic** the whole list in its order.
A fresh install starts with Google Translate, which needs no key, and the on-device engines at the top: ML Kit first where it is there, then Firefox Translations. Updating the app keeps the order your earlier settings gave, and puts Firefox Translations after the on-device rows you already have (at the top if you had none).
## Your own translation server
You can send translations to a server that you run, or to a service that you pay for, as long as it answers OpenAI chat completion requests (`/v1/chat/completions`). llama.cpp's `llama-server`, Ollama, LM Studio, vLLM and LocalAI all do, and so do most hosted gateways. The keyboard itself then does no translating work: the phone only sends the text and shows the answer.
- **Turning it on.** Add **Your translation server** under [Providers](#providers) and type its address. The **Online** engine then sends translations to your server. On **Automatic**, only the translations that the device cannot do go to it. **On device** never uses it.
- **Nothing else gets the text.** A new server goes to the top of the online part of the list, and the other online providers are switched off, so when the server fails (it is off, the address is wrong, the model is missing) the panel shows the error and the text goes nowhere else. Switch another provider back on if you want it as a fallback. A small **Your server** label beside the engine chip says where the translation on screen came from.
- **What the server gets.** A chat with two messages: an instruction to translate into the target language (and from the source language, if you picked one in the panel), then your text as the second message. The instruction tells the model to reply with the translation only and to translate questions and requests rather than answer them. The answer comes back as it is written, and the keyboard removes a `` block and a pair of quotation marks around the whole answer, if the model adds them.
- **The address.** A bare `host:port`, the API root (`http://192.168.1.10:8080/v1`, `https://openrouter.ai/api/v1`) or the whole `…/chat/completions` address all work. With no path, the keyboard adds `/v1`. With no `http://` or `https://`, it uses `https://`, so type `http://` for a server on your home network.
- **Which language it read.** The server does not say what language the text was in, so the source chip shows a language only when you picked one.
- **Speed.** A language model is slower than a translation service. On a small model, expect a second or more for a sentence; a model that reasons first takes longer.
## Translation front ends
A front end is a free server that passes your text on to Google Translate, DeepL or another service and hands the answer back. The service behind it sees the server, not your phone. WM Keyboard knows four kinds:
| Kind | Translates with | Public instances |
|---|---|---|
| [Mozhi](https://codeberg.org/aryak/mozhi) | Google, DeepL, DuckDuckGo, Yandex or Reverso: you choose the **Engine** | [list](https://codeberg.org/aryak/mozhi/src/branch/master/instances.json) |
| [SimplyTranslate](https://codeberg.org/ManeraKai/simplytranslate) | Google, Reverso or iCIBA: you choose the **Engine** | [list](https://codeberg.org/ManeraKai/simplytranslate/src/branch/main/instances.json) |
| [Lingva](https://github.com/thedaviddelta/lingva-translate) | Google | [list](https://github.com/thedaviddelta/lingva-translate#instances) |
| [Translite](https://codeberg.org/gospodin/translite) | Google | [list](https://codeberg.org/gospodin/translite/src/branch/master/instances.json) |
- **Adding one.** Under [Providers](#providers), tap **Add a provider**, pick the kind and paste the address of an instance: a public one you trust, or your own. A bare host such as `mozhi.example.org`, the address from the browser's address bar, or an `/api/...` address all work. **Test** sends one word to check it. There is no default instance, because public instances come and go.
- **Engine.** Mozhi and SimplyTranslate can pass the text to more than one service. The engine shows beside the address in the list, so you can add the same instance twice with two engines. An instance that does not run the engine you chose fails, and the next provider is asked.
- **Languages.** Each engine spells some language codes its own way (Hebrew is `iw` on one and `he` on another). The keyboard reads each instance's language list once and uses the code that the engine knows. A language the engine does not have goes to the next provider. Traditional Chinese is never sent as Simplified.
- **Detecting the source.** DeepL and Reverso behind Mozhi cannot detect the language you typed. With **Detect language** as the source, they pass the text to the next provider. Pick the source language in the panel to use them.
- **When an instance breaks.** Front ends read the services behind them in ways those services do not support, and an instance can stop working without notice. An empty answer, an error or a Cloudflare check counts as a failure, so the next provider in the list answers. Keep Google Translate or another provider on below the front end if you want a fallback.
## DeepL
DeepL is an optional online service that you bring yourself, with your own DeepL API key. Nothing about it shows or runs until you add **DeepL** under [Providers](#providers) with a key (or a server address). The keyboard has no DeepL key of its own.
- **Translate with DeepL.** DeepL is asked in its place in the list. A small **DeepL** label beside the engine chip says when DeepL made the translation on screen. For a language pair that DeepL does not have, or when DeepL fails, the next provider in the list translates and the label goes away. On **Automatic**, DeepL only takes the part that would otherwise go online. To keep the key for DeepL Write only, switch the DeepL row off.
- **DeepL Write.** Off until you turn it on. Write corrects and rewrites text in its own language: grammar, wording, style and tone. It adds a button to the [Grammar check](https://wmkeyboard.pages.dev/smart/grammar/#deepl-write) panel and a **DeepL Write** action to [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/). DeepL sells Write with API Pro plans only, so a Free key gets an error from it (Free keys still translate).
- **Which server.** DeepL API Free keys end in `:fx` and go to `api-free.deepl.com`; every other key goes to `api.deepl.com`. The keyboard picks the server from the key. **DeepL server** replaces both, for a proxy that you run and that answers DeepL's `/v2/translate` requests.
- **Limits.** The Free plan includes 500,000 characters each month. When the month's characters are used up, the panel says so.
## Options
Tools / Translate
- **Turn on**: already on, like every tool. Switching it off removes Translate from both the toolbar and the toolbox.
- **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) are turned on globally. It overrides Translate's icon color.
- **Keyword shortcut**: a **Trigger words** field pre-filled with "translate", plus a **Match capitals** switch that's off by default. Type the word on its own and the suggestion strip offers to open the tool. See [smart suggestion chips](https://wmkeyboard.pages.dev/smart/chips/).
- **Translate into**: the target language, from the same 127-language list as the panel's picker. Defaults to **English**. Changing it here also changes what the panel opens to next time.
- **Clear after inserting**: off by default. On, **Insert** empties the text box and its translation once the translation is in your field, so the next message starts in an empty box.
- **Translate "in …" where it stands**: off by default, new in 0.6.6. Type `good morning in spanish` and the strip shows a [translate hint chip](https://wmkeyboard.pages.dev/smart/chips/). With this off, tapping it opens Translate on "good morning", already in Spanish, for you to **Replace**. With it on, tapping the chip puts the Spanish in place of "good morning in spanish" straight away, without opening the panel, and the chip's Translate button still opens it for a look first. The hint has to name its language; `translate this` always opens the panel. If no translation comes back (no connection, a language pair whose models aren't downloaded, a metered connection the panel has to ask about), the panel opens on the words instead, with the reason.
- **Translation engine**: **Online**, **On device** or **Automatic**. The same choice as the panel's engine chip. The default is **On device** on Google Play and **Online** in the APKs from GitHub and F-Droid. Shown wherever an on-device engine runs.
- **Providers**: who translates, in order. See [Providers](#providers). Each kind's setup asks for what it needs:
- **Google Translate**: an optional Cloud Translation API key. Without one, translation uses Google's free public endpoint, or the key built into the build if it has one. With one, it uses the official API.
- **DeepL**: a DeepL API key, a DeepL server for a proxy, or both. See [DeepL](#deepl).
- **LibreTranslate**: the address of a LibreTranslate server, your own or one you trust, and an optional key for instances that ask for one.
- **Mozhi** and **SimplyTranslate**: the address of an instance and the **Engine**. See [translation front ends](#translation-front-ends).
- **Lingva** and **Translite**: the address of an instance.
- **Your translation server**: the address, an optional model name (for example `qwen3:4b` on Ollama, blank for a server with one model), and an optional key sent as a bearer token. See [your own translation server](#your-own-translation-server).
- **Advanced**, a closed fold under the list, shown wherever an on-device engine runs:
- **Downloaded languages first**: on by default. On **On device** and **Automatic**, the panel's language menus start with the languages on the device. Off keeps the usual order: English and Bengali, then the rest by name.
- **Only downloaded languages**: on by default. On **On device**, the panel's language menus show the downloaded languages, the languages you type in and the current selection, with **All languages** at the end for the rest. Off lists every language.
Each provider's own settings are on its screen, opened by pressing its row:
- **On device (ML Kit)**: google **Languages on the device**, the on-device models. The list starts with English (built in), the language you translate into, the languages you type in, and anything already downloaded or on its way. **More languages** below it folds out the rest. Each row downloads, cancels or deletes one model and shows its progress. A download started here shows as running in the panel, and the other way round.
- **On device (Firefox Translations)**: **Firefox Translations models**, one row per direction with its download size. The list starts with the directions into and out of the language you translate into and the languages you type in, and anything already downloaded or on its way; **More directions** folds out the rest. On builds that fetch the engine's library, **Firefox Translations engine** is the first row until it is here. 64-bit phones with Android 9 and later.
- **On device (NLLB-200)**: **NLLB-200 model**, one row for the 630 MB model, with **CTranslate2** above it on builds that fetch the engine's library. The first download shows the model's licence. Swipe the row to delete the model. 64-bit phones with 6 GB of memory or more.
- **DeepL**: **DeepL Write**, shown once DeepL is set up, off by default. On adds DeepL Write to the Grammar check panel and to selection actions. Needs a DeepL API Pro key. **Writing style** is shown while DeepL Write is on: **As it is** (default), **Simple**, **Business**, **Academic**, **Casual**, **Friendly**, **Confident**, **Diplomatic** or **Enthusiastic**. A language that DeepL has no styles for gets a plain rewrite.
- **Google Translate**: on the F-Droid build, the addresses of Google's two translation services.
The keyboard sends the text that you translate only to the providers that are on, and only while the translate panel is open. Google's free endpoint is unofficial and rate limited. A Cloud Translation key makes it official and reliable.
If you back up your settings with **Include API keys** turned on, a Translate key, DeepL key or translation server key you've pasted in travels with the backup along with your GIF, search, and AI keys. Without that switch, none of them leaves the phone in a backup.
## Details & edge cases
- **On the device, nothing is sent.** With the engine on **On device**, the text you type is translated on the phone and goes nowhere. The only network use is the model downloads: ML Kit's come from Google's servers (`dl.google.com`) through Android's download manager, plus ML Kit's own [diagnostics](https://wmkeyboard.pages.dev/privacy/network/); Firefox Translations models come from Mozilla's attachment server (`firefox-settings-attachments.cdn.mozilla.net`), the same files Firefox downloads, and Bergamot sends nothing else. The NLLB-200 model comes from Hugging Face (`huggingface.co`), pinned to one revision and checked against its SHA-256. On **Automatic**, text goes online only when the device cannot translate it.
- **Google, or a LibreTranslate server.** With Google Translate in the list and no key, WM Keyboard calls Google's free public translation endpoint. It's the same unofficial one browser translate extensions use: fine for personal typing volumes, with no uptime guarantee and its own rate limits. With a key, it calls the official Cloud Translation v2 API instead, billed to your Google Cloud project and reliable. Add a LibreTranslate server above Google to send translations there first, on any build. Whichever answers, only the text in the panel's box is sent: what you typed, pasted, or had selected when you opened it. Nothing else in the field is.
- **Your own server, when you set it up.** With only the server on, every online translation goes to that server and to nothing else, errors included (see [Your own translation server](#your-own-translation-server)). It receives the text as the user message of a chat request, in a POST body, with the instruction as the system message and your key, if any, in a request header.
- **DeepL, when you set it up.** Translations go to DeepL in its place in the list (see [DeepL](#deepl)). A pair that DeepL turns down goes to the next provider, and DeepL is not asked about that pair again until the keyboard restarts. Other DeepL errors (a wrong key, a used-up month) also hand the text to the next provider that is on. If DeepL is the only one on, the panel shows the error.
- **Sent, precisely:** the text currently in the panel's search box (truncated to 2,500 characters), the target language code, the source language code if you picked one, and your key where one applies. DeepL receives the text in a POST body and the key in a request header. The free Google endpoint receives the text as part of the request URL. The official Google API and a configured LibreTranslate server both send it in a POST body, LibreTranslate's carrying its optional key alongside. Mozhi, SimplyTranslate and Translite receive the text in a POST body. Lingva's API takes the text only as part of the request URL, so a Lingva instance can see it in its logs. The first translation through a front end each session also asks the instance for its language list, which sends no text.
- **When it's sent:** automatically ~400ms after you stop typing, immediately on return or retry, and immediately again if you change the target language, all only while the panel is open. Closing the panel cancels any translation in flight.
- **Power Saving mode's "Background network" toggle doesn't touch this.** That toggle only stops fetches that happen without you asking: link previews, QR auto-scan, dictionary look-ups. Tools you open yourself, translate included, keep working even with it on.
- **Not available before your phone is unlocked once after a restart.** Translate isn't on the short list of tools that work on a locked device, and the on-device models live in storage that is locked until then. It won't appear if you're typing on the lock screen before that first unlock.
- **Online translation works the same in every build.** ML Kit is not in the F-Droid build. Firefox Translations is in every build, so the F-Droid build has the engine chip and its model list too, on 64-bit phones with Android 9 and later. Elsewhere, without ML Kit, there is no engine chip and Translate is online only, as before.
- **64-bit Android 9 and later for Firefox Translations.** The engine's library needs parts of Android's C library that arrived in Android 9, and its ARM code needs a 64-bit processor (arm64), so older phones and 32-bit ones do not list it. An x86_64 Chromebook or emulator has it too.
- **6 GB of memory for NLLB-200.** While it translates, the model and its working copy take about 1.25 GB, so phones with less memory, and 32-bit ones, do not list it. It is also the slowest of the three engines.
- **Deleting a model** frees its space at once. If the panel was using it, the next translation asks for it again.
- If a request fails outright, from no connection, a bad key, or a timeout, the panel shows the raw error message rather than a simplified one. An offline failure can read as a technical exception string instead of "No internet connection."
- **Online, with no connection.** Turn on [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) and the Online engine hands the text to the on-device engine when the phone is offline or the service does not answer, as long as both languages are downloaded. The result says **On device, offline**. This goes for your own translation server too: the text still stays on the phone.
Looking for something different named "translate"? The [AI tool](https://wmkeyboard.pages.dev/tools/ai/) has its own Translate action, which uses an LLM for more natural phrasing than a literal translation. When you give it a chip of its own on the selection actions bar, that chip reads **AI translate**, to tell it apart from the **Translate** chip that opens this tool. [Offline voice dictation](https://wmkeyboard.pages.dev/tools/whisper/) has a separate "Translate to English" toggle for what you *say* rather than what you type. Both are configured on their own settings screens, independent of this one. For the full picture of what leaves your device and when, see [network policy](https://wmkeyboard.pages.dev/privacy/network/).
---
# Utility tools
URL: https://wmkeyboard.pages.dev/tools/utilities/
Section: Tools
Summary: Calculator, unit and currency converters, a symbol picker, QR and password generators, a text repeater, and a typing-speed test: eight related tools, seven of them entirely offline.
This page covers eight tools that share a theme: arithmetic, conversion and generation. The toolbox draws every tool as one ranked grid with no sections, so the split shows up on the Tools screen instead. A scientific **Calculator**, a **Unit converter**, a **Special symbols** picker, a **QR code generator**, a **Password generator** and **Text repeat** sit under **Create and convert**, alongside **Typing speed test**, which measures rather than generates but lands in the same group. **Currency converter** is filed under **Online tools**, because it's the only one that has to touch the network for live exchange rates. The other seven run their math or generation entirely on the device.
tools/calculator-panel — The Calculator panel: live expression and result, the scientific function row, and the custom digit keypad.
For Wikipedia, web/image search, and the AI writing tools (also reachable from the toolbox but documented on their own pages), see [Search, Wikipedia & dictionary](https://wmkeyboard.pages.dev/tools/search/) and [AI tools](https://wmkeyboard.pages.dev/tools/ai/). For what the toolbar and toolbox are and how pinning works, see [The toolbar](https://wmkeyboard.pages.dev/tools/overview/).
## Doing the math
Open **Calculator** from the toolbar or toolbox. It isn't pinned by default. On a hardware keyboard, double-tap Ctrl for the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/), then press L.
The panel evaluates your expression live as you type. It takes `+ - × ÷ * / mod % ^ ( )`, implicit multiplication (`2π`, `3(4+1)`), and the constants `π`/`pi` and `e`. A scrolling function row offers `C`, `sin`, `cos`, `tan`, `π`, `e`, `^`, `√`, `ln`, `log` (base 10), `abs` and `mod` as one-tap chips. Typed out by name, `asin`/`acos`/`atan`/`sinh`/`cosh`/`tanh`/`sqrt`/`cbrt`/`exp`/`floor`/`ceil`/`round` all work too. A trailing `%` reads the way it does on a pocket calculator: added or subtracted it is a share of what came before, so `100+20%` is `120` and `1500-20%` is `1200`, and a second one compounds on the new total. On its own, or multiplied and divided, it is a plain hundredth: `20%` is `0.2`, `100×20%` is `20`, `100÷20%` is `500`. Wrap it in brackets to force that reading where you don't want the share: `100+(20%)` is `100.2`. A `%` followed by a number is modulo, same as `mod`. Permutations and combinations use the keypad shorthand, `5p3` and `4c2`.
- The **deg**/**rad** chip in the scrolling function row flips trig between degrees and radians, and remembers your choice.
- **=** replaces the expression with its result, so `12*4` becomes `48` and the next thing you type carries on from that number.
- An **Insert** chip appears next to the live result whenever it evaluates successfully, and types the formatted number at the cursor.
- Opening Calculator from a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) (the inline `96` that appears when you type `12*8` on the strip) prefills the expression with what you already typed.
The custom keypad (`7 8 9 ÷ ⌫`, `4 5 6 × (`, `1 2 3 − )`, `0 . % + =`, or `1 2 3` on top with **Phone layout**) replaces the letter rows entirely while the panel is open. There's nothing to type past what the calculator itself accepts.
On a phone turned sideways there's no room for a row of chips over the keypad, so the functions move beside it instead: `C`, the eleven functions and the **deg**/**rad** toggle become keys, four to a row, to the keypad's left.
### Options
Tools / Calculator / Options
- **Suggestion chip**: shows whether Calculate as you type is on and opens [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), where the switch lives.
- **Degrees**: on by default. Off switches trig functions to radians.
- **Phone layout**: off by default. It puts 1 2 3 on the keypad's top row, like a dialer and the [numpad](https://wmkeyboard.pages.dev/tools/text-editing/), instead of 7 8 9 like a desk calculator. The operator keys stay where they are.
- **Result precision**: a slider from 0 to 12 decimal places, defaulting to **8**. Trailing zeros are stripped either way, and this same setting shapes the unit converter's output below.
## Converting units and currencies
**Unit converter** and **Currency converter** share a layout: category or currency chips up top, a swap icon between the two sides, a live result, and the same digit keypad. Only one of them touches the network. That keypad is simpler than Calculator's, just digits, a decimal point and backspace, because neither converter evaluates an expression.
tools/unit-converter-panel — The Unit converter panel: category chips, From/To unit rows, the swap icon, and the shared digit keypad.
**Unit converter** covers 14 categories, all converted locally: length, mass, temperature, area, volume, speed, time, data, energy, power, pressure, angle, frequency, and fuel economy. Pick a category. Pick a From and a To unit. Type a value, then tap **Insert**. The panel remembers the unit pair you used last *per category*, so switching to Volume and back to Length restores each one's own last pair rather than sharing a single memory slot.
**Currency converter** needs live exchange rates. It fetches from `open.er-api.com` (roughly 160 currencies, updated daily) and falls back to `frankfurter.dev` (European Central Bank, roughly 30 currencies) if that fails. The F-Droid edition starts from the jsDelivr `currency-api` instead (about 340 codes, coins included, published as open data), with Frankfurter behind it, so a fresh install there reaches only sources that are free software. The others stay one setting away. None of them needs a key. Rates are quoted against USD and cross-converted locally, so one fetch covers every pair. A refresh icon on the panel forces a new fetch. Otherwise it only refetches once the cached rates are older than the refresh interval set below, and that cache doesn't survive the keyboard process restarting, so the first open after a while can show a brief "Downloading the exchange rates…" message.
tools/currency-converter-panel — The Currency converter panel: a From/To currency pair, live converted amount, and the refresh icon.
The currency picker pins 15 common codes to the front of the list: USD, EUR, BDT, GBP, INR, JPY, CNY, AUD, CAD, SGD, AED, SAR, MYR, TRY, PKR. Everything else the current rate table has follows, sorted alphabetically.
**Cryptocurrency** rides in the same panel. With coins on (they are by default) the picker grows a **Crypto** section between the pinned codes and the alphabetical tail. Picking a coin on either side converts against it exactly like a currency. Coin prices come from `api.coinbase.com`, with the jsDelivr `currency-api` behind it and CoinGecko as the other choice (the F-Droid edition starts from `currency-api` alone), and they're only fetched when you actually pick a coin or type a coin amount. Coin results keep far more of the number than the two decimals a currency gets, since a fraction of a bitcoin at two decimals is just `0.00`. In the panel that means up to twelve decimal places with the trailing zeros stripped. The inline strip chip settles for five significant digits instead, because it has a chip's worth of room.
Both converters are also reachable inline. Typing `1 ft` or `150 usd` on the strip offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) with the converted amount, and the sliders icon at the end of the chip opens the matching panel with the category or pair already set. That inline mechanism is covered on the smart chips page, not here.
### Options
Tools / Unit converter / Options
- **Suggestion chip**: shows whether Convert as you type is on and opens [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), where the switch lives.
- **Feet and inches**: on by default. Anything converting into feet is read as feet plus inches, so 1 metre comes back as `3 ft 3.37 in` rather than `3.2808399 ft`. Turn it off for the plain decimal.
Tools / Currency converter / Options
- **Suggestion chip**: shows whether Convert as you type is on and opens [Smart chips](https://wmkeyboard.pages.dev/smart/chips/), where the switch lives.
- **Fetch rates as you type**: on by default, off in the F-Droid edition. On, a currency chip fetches the rates it needs as soon as you type an amount. Off, the chip reads **Get the rate** and fetches nothing until you tap it. The panel fetches when you open it either way, and rates the keyboard already has convert with no fetch.
- **Decimal places**: a slider from 0 to 6, defaulting to **2**.
- **Write the result as**: how a currency chip names the currency it converted into. **Name** (the default) writes `96.04 Rupee`, **Symbol** writes `₹96.04` and **Code** writes `96.04 INR`. A currency with no symbol of its own falls back to its code, and coins keep their ticker whichever you pick.
- **Refresh rates every**: a slider from 1 to 48 hours, defaulting to **6**. Upstream rates only update about once a day, so anything under 24 hours mostly changes how soon a *failed* fetch gets retried.
- **Currency rates from**: a [provider list](https://wmkeyboard.pages.dev/tools/providers/) of where fiat rates come from, best first: ExchangeRate-API, Frankfurter, AllRatesToday (European Central Bank reference rates, rebased to USD), Coinbase and the jsDelivr currency-api. The tool asks the first one that is on and the next only when it fails. Drag to reorder, switch a source off, or add one back. It starts with ExchangeRate-API then Frankfurter, or currency-api then Frankfurter in the F-Droid edition. Switch every source off and nothing is fetched.
Tools / Currency converter / Cryptocurrency
- **Convert coins**: on by default. Reads amounts like `1 btc` and shows the Crypto section on the panel.
- **Coin decimal places**: a slider from Auto to 12, defaulting to **Auto**. On Auto the panel keeps up to twelve decimal places and trims the trailing zeros, and the inline chip keeps five significant digits. Pick a number instead and both work to that count.
- **Refresh coin prices every**: a slider from 1 to 60 minutes, defaulting to **5**.
- **Coin prices from**: the same kind of list for coins: Coinbase, the jsDelivr currency-api and CoinGecko. It starts with Coinbase then currency-api, or currency-api alone in the F-Droid edition.
- **Coins**: a chip for each of the 39 coins the keyboard knows, 20 of them on to begin with, plus **Add a ticker** for anything else your source quotes. At least one coin always stays on.
[Power saving mode](https://wmkeyboard.pages.dev/typing/power-saving/) doesn't touch either converter's own network fetch. Its background-network toggle only forces off clipboard link previews, QR-scan link previews and dictionary auto-lookup, never the currency panel opening or refreshing. See [network policy](https://wmkeyboard.pages.dev/privacy/network/) for what leaves the device and when.
## Typing special symbols
**Special symbols** is a tap-to-type picker for characters that are otherwise a hunt to find: fractions, real math operators, Greek letters, arrows, currency signs, super/subscripts, punctuation marks, shapes and stars, and a Misc catch-all, nine categories in all. Tap a category chip, then tap a symbol to type it exactly like a regular key.
tools/symbols-panel — The Symbols panel: category chips over an adaptive grid, with a Recents chip once you've used a few.
Every symbol you tap is remembered under a **Recents** chip at the front of the category row, capped at 24 and most recent first. A repeat tap moves a symbol back to the front instead of duplicating it. A few characters render invisibly, so they get a text label instead of a blank cell: non-breaking space shows as "NBSP", zero-width space as "ZWSP", zero-width joiner as "ZWJ".
Swipe sideways to move between categories. The last chip, **Unicode**, is every character the phone knows, with a chip per Unicode block to jump to. Its **Search** chip finds a character by its English name or its code, so `right arrow` and `U+2192` both find `→`, and Chinese characters and Korean syllables come up by code only, since their names are only their codes. Hold a character there to read its code and name. To change a category, open the tool's settings and press it under **Your symbols**: type the symbols you want with a space between each, or press **Reset** for the built-in set.
Hold a symbol to see it larger, with its relatives beside it: the double and long arrows by an arrow, the other case of a Greek letter, the subscript by a superscript. Slide to one and let go to type it. Letting go where you started types the symbol you held. In your own list, join the ones that belong together with `|`: `→|⇒|⟶` shows `→` in the grid and offers the other two when held.
This tool is separate from the **symbol row** (off by default), a strip of one-tap characters that sits above the key grid and is covered under [Modes & the symbol row](https://wmkeyboard.pages.dev/typing/modes/#the-symbol-row). That's a persistent row you configure once, while Special symbols is a picker you open on demand.
### Options
Tools / Special symbols
- **Clear recent symbols**: a single action. The row shows how many symbols are currently remembered, or "No recents yet".
## Making a QR code
**QR code generator** turns whatever you type into a scannable code and sends it as an image. Like the typing test below, it keeps the letter rows on screen instead of replacing them, because you type into it. Opening it seeds its own editable buffer with the focused field's text as a starting point. Opened from a clip's **QR code** button in the [clipboard](https://wmkeyboard.pages.dev/tools/clipboard/), it starts from that clip's text instead. From there it's independent: your keystrokes route into the QR content rather than the field, and the field itself is never touched until you send.
tools/qr-generator-panel — The QR generator panel: editable content line, live QR preview, and Send as image.
The preview updates as you type, up to **Longest text** characters (500 to 4,000, default 2,000). Past that it replaces the code with "The text is too long for one QR code," followed by your current character count. A longer payload encodes at a lower error correction level, which is why the two rows sit together in settings. **Send as image** commits the PNG through the same `commitContent` path the clipboard and sticker tools use. If the receiving field or app doesn't advertise support for it, the code falls back to the system clipboard instead, with a toast telling you to paste it in.
### Options
Tools / QR code generator / Options
- **Image size**: a slider from 256 to 2048 px, defaulting to **1024**. This only affects the PNG that gets sent. The on-panel preview is a fixed small size regardless.
- **Send as**: Image or Sticker, defaulting to **Image**. It's the same Image/Sticker switch the GIF and sticker tools use, but it has no visible effect here. A WhatsApp-style sticker only goes out for WebP source images, and a generated QR code is always saved as a PNG, so it sends as a plain image either way.
- **Save to gallery**: off by default. Also copies each generated code into **Pictures/WM Keyboard**.
- **Error correction**: a segmented L/M/Q/H row, defaulting to **M**. Higher levels survive more smudging and damage but fit less text into the same code.
- **Longest text**: a slider from 500 to 4,000 characters, defaulting to **2,000**. Raise it to fit a longer link or note. If the code then refuses to build, lower the error correction above it, or shorten the text.
## Generating a password
**Password generator** makes passwords, passphrases or PINs with `SecureRandom` entirely on the device. Nothing generated here is ever stored or logged, and there's no history to clear because none is kept.
tools/password-generator-panel — The Password generator panel: generated result, entropy estimate, and the Password/Passphrase tabs.
**Password mode** picks one character from every enabled class first, fills the rest at random, then shuffles the whole string so the guaranteed characters aren't front-loaded. **Passphrase mode** draws words from the keyboard's own bundled English dictionary, filtered down to short (4–8 letter), plain-lowercase, distinct words. That works regardless of which language you're actually typing in, with no separate wordlist to download. **PIN mode** makes a code of digits only, for a phone, an app or a bank card: 6 digits by default, anywhere from 4 to 12 with the stepper. With **No easy PINs** on (the default) it draws again past the PINs many apps refuse and guessers try first: one digit repeated (`1111`), a run up or down (`1234`, `9876`), a short block repeated (`1212`, `123123`), and anything that reads as a year or a date (`1987`, `2512`, `250390`). All three modes show a rough "≈N bits of entropy" estimate, and both have **Copy** and **Insert** chips plus a regenerate icon. Once in a long while the entropy line picks up a second caption. See [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#read-your-password-generators-mind).
### Options
Tools / Password generator
**Password**
- **Length**: a slider from 4 to 64, defaulting to **16**.
- **Uppercase letters**, **Digits**, **Symbols**: all on by default.
- **Symbols to use**: empty by default, which means the built-in punctuation set. Shown only while **Symbols** is on. Some sites accept only a few marks. Type just those and the generator draws from them alone, so you don't have to generate again and again until a password comes out clean.
- **Exclude look-alikes**: off by default. Strips easily-confused characters (`Il1O0o5S8B` and a few punctuation marks) from the pool.
**Passphrase**
- **Words**: a slider from 2 to 10, defaulting to **4**.
- **Separator**: free text between words, defaulting to `-`. Leave it blank for no separator.
- **Capitalize words**: off by default (`correct-horse` → `Correct-Horse`).
- **Include a digit**: off by default. Appends a random digit to one random word in the phrase.
**PIN**
- **Digits**: a slider from 4 to 12, defaulting to **6**.
- **No easy PINs**: on by default. Skips repeated digits, runs, repeated blocks and dates.
## Repeating text
**Text repeat** writes a piece of text many times over in one go: "ha" fifty times, a numbered list of the same line, "no" with its last letter stretched out. Type the text into the panel's box (the keys go to the panel, not the app), pick a count and what goes between the copies, and the card under the controls shows what Insert will write, with the number of copies and characters. **Insert** types it into the field, **Copy** puts it on the clipboard, and **Undo** takes the last insert back as long as the field still ends with it.
Three modes sit in the header. **Repeat** writes the text the set number of times with the separator between. **Stretch** lengthens the last letter instead: "no" with a count of 4 becomes "nooooo", and an emoji or an accented letter stays whole. **Stairs** grows a copy at a time, "ha", "haha", "hahaha", one per line, which is the picture the name promises. The count has a stepper that keeps stepping while you hold − or +, faster the longer you hold, plus one-tap chips for 2, 3, 5, 10, 25, 50 and 100. The separator is **None**, **Space**, **New line**, **Comma**, or **Custom**, which opens a second box for your own, up to eight characters. Two toggles work in Repeat and Stairs: **1. 2. 3.** puts a number in front of each copy, and **a A a A** writes every second copy in capitals.
The panel opens on the selection when there is one, else on the word before the cursor, as a convenience the box's × undoes in one tap. Select some text and the [selection bar](https://wmkeyboard.pages.dev/smart/selection-actions/) offers **Repeat**, which opens the panel on the selection. Type the same thing three times in a row and the strip offers the tool by itself; see [Repeated text](https://wmkeyboard.pages.dev/smart/chips/#repeated-text) for that chip and what it does with the copies you already typed.
The texts you repeat are remembered, up to ten, as chips at the end of the panel's bottom row. They live in a file of their own on the phone, never in a backup, and nothing is remembered in incognito mode, in a password field, or in a field that asks not to be learnt from.
### Options
Tools / Text repeat
The panel edits the first group live, so these are also how it opens next time.
- **Times**: 1 to 1000, defaulting to **10**.
- **Mode**: **Repeat**, **Stretch** or **Stairs**, default **Repeat**.
- **Between the copies**: **None**, **Space**, **New line**, **Comma** or **Custom**, default **Space**. Picking Stairs with None switches it to New line, since a staircase with nothing between its steps is one long word. **Custom separator** appears for Custom.
- **Number each copy** and **Alternate the case**: both off by default.
- **Longest result**: 500 to 20,000 characters, default **5,000**. The panel stops at the last whole copy that fits and says so, rather than cutting a copy in half.
- **When opened from the chip**: **Replace them** (default) or **Add after them**. See [Repeated text](https://wmkeyboard.pages.dev/smart/chips/#repeated-text).
- **Chip after this many repeats**: 2 to 6, default **3**. The same row lives under Smart chips.
- **Remember recent texts**: on by default, with a **Clear recent texts** row under it.
## Testing your typing speed
**Typing speed test** leaves the key rows alone while a run is going, the same way the QR generator does. You type the prompt on the real keys while the panel scores it from above. The results screen needs no keys, so the panel takes their height back for the scores. Shift is forced off the moment a run starts, so a field's auto-capitalization can't turn your first keystroke into a false miss. The clock doesn't start until you type your first character either, so time spent reading the prompt costs you nothing.
tools/typing-test-panel — A typing test in progress: the prompt with per-character correctness coloring, above the live key rows.
Three modes decide when a run ends: **Time** (the default, 30 seconds), **Word count** (default 25 words), and **Quote** (types out one short public-domain quotation, however long it runs). Time and Word-count prompts draw from the 200 most common words of the test's language, deliberately short and high-frequency so the test measures typing rather than vocabulary. Turning on **Punctuation** or **Numbers** sprinkles capitals, commas, full stops and the occasional numeral into the prompt. Both are off by default, and both are hidden entirely in Quote mode.
### Other languages
The test runs in whichever language the keyboard is in. The last chip on the settings strip names that language. Tap it to pick any language your enabled layouts cover, and the keyboard switches to that language's layout and deals a fresh prompt. A spacebar language swipe during a run does the same. English, Bangla and Hindi ship their own word lists and quotations. Any other language builds its prompt from the most common words of its downloaded or imported dictionary, so a language with no dictionary yet shows a note instead of a prompt, and Quote mode is greyed out for it. On a transliterating layout such as Avro or Hindi phonetic you type the romanised keys as usual, and the test scores the native-script word that comes out. It waits for the whole word before scoring it, because a key there can't be judged as it lands: `k` is ক on its way to খ, and a conjunct isn't itself until its last key. So the syllable under the cursor stays uncoloured while you spell it, and the keys behind it are marked right or wrong once the space closes the word. Keys you backspaced away still count against you, exactly as a mistyped letter does on a direct layout. A layout that converts what you type, such as Pinyin or kana, can't be scored, and the panel says so.
Numbers and punctuation follow the language too: a Bangla prompt gets Bangla numerals and ends its sentences with the danda. Personal bests are kept per language, so a 30-second Bangla record never overwrites a 30-second English one. The records list in settings names the language on every non-English row.
### Glide and suggestions
Two more chips on the strip, **Glide** and **Suggestions**, let the keyboard's own helpers take part in a run so you can compare scores with and without them. Both are off by default.
- **Glide** lets you swipe words during a test. The stroke is decoded exactly as it would be in a text field, and the word lands as if you had typed every letter, followed by its space. A stroke that decodes to the wrong word costs what mistyping it would have. The chip is greyed out where gesture typing isn't available for the language and layout at all. This switch is independent of the main **Glide typing** setting, so you can measure gestures even if you keep them off day to day.
- **Suggestions** puts the keyboard's own suggestion strip back where it always sits, in the row directly above the keys, with next-word predictions between words. It is the real strip, in the same slots, at the same size, under your own strip settings, so practising with it is practice at the thing you actually type against. Tapping one finishes the current word and moves on. The tap counts as one keystroke, correct only if the word matched. The words go into the test and never into the field behind the panel.
Both switches also live under Tools / Typing speed test / Typing aids.
tools/typing-test-results — Typing test results: WPM, accuracy, consistency, and the Insert score chip.
Results show four numbers. Words per minute is correct characters ÷ 5 ÷ minutes, the usual "5 characters = 1 word" convention. Raw speed counts every character you typed whether or not it matched. Accuracy is keystroke-level, so it still counts a mistake after you correct it. Consistency scores how even your pace stayed through the run. **Insert score** types the result straight into your field as `"83 WPM · 96% accuracy (30s)"`, with your own numbers and config in place of that example. Personal bests are tracked per configuration, so a 15-second sprint and a 2-minute run don't share a record, alongside a rolling history of your last 24 runs and a running count of tests completed. There are also four achievement badges to chase, listed with their conditions on [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#earn-typing-test-achievements).
### Options
Tools / Typing speed test
- **Default test**: Timed / Word count / Quote chips. The app opens on whichever you used last.
- **Length**: for Timed, a slider from 15 to 120 seconds (default **30**); for Word count, a slider from 10 to 100 words (default **25**). Quote mode has no length control, since a quote runs to its own end.
- **Difficulty**: the **Punctuation** and **Numbers** toggles, both off by default. This group is hidden entirely while Quote mode is selected.
- **Records**: tests completed, a recent average once you have history, a best-WPM row per configuration you've actually run, and **Clear records** to wipe all of it.
- **Achievements**: all four badges, listed whether you've earned them or not. **100 wpm club** (finish a run at 100 wpm or faster), **Flawless run** (nothing wrong, extra or missed in a run of at least 30 characters), **Quick brown fox** (finish a quote that uses every letter of the alphabet), and **50 runs**. Earned ones carry a green tick, the rest a faded padlock, so this group is where the goals are visible. The panel's own results screen only shows badges you already have. Same list, with the conditions written out, on [Easter eggs](https://wmkeyboard.pages.dev/reference/easter-eggs/#earn-typing-test-achievements).
The calculator and the two converters are the **Calculator and converters** bundle on [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/); the QR code maker, the password generator and text repeat are the **Generators** bundle, and the typing test is the **Typing test** bundle. A bundle that is off takes its tools off the toolbar, the toolbox and the Tools list until it is switched back on; the special symbols tool belongs to none.
## Details & edge cases
- **What's offline, and what's direct-boot safe.** All eight tools work with no network connection except Currency, which needs one to fetch rates. Separately, Symbols, Calculator, Unit converter, Password generator, QR generator and Text repeat all still work during **direct boot**, the window right after a restart before you've unlocked the device for the first time (Text repeat's recent texts read empty until then). Currency doesn't, having no network yet, and neither does Typing test, for reasons the app doesn't surface anywhere. See [The toolbar](https://wmkeyboard.pages.dev/tools/overview/#which-tools-work-before-you-unlock-your-phone) for the full direct-boot picture across every tool.
- **Hardware shortcuts.** With a [physical keyboard](https://wmkeyboard.pages.dev/typing/hardware-keyboard/) attached, double-tap Ctrl then a letter: Y for Symbols, L for Calculator, U for Unit converter, Q for QR generator, P for Password generator. Currency converter, Text repeat and Typing test have no default letter. Reach them through the toolbox (T) or [assign one yourself](https://wmkeyboard.pages.dev/reference/shortcuts/).
- **Discoverable by typing.** Typing one of a tool's keywords as a standalone word offers a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) straight into it: `symbol`/`symbols`, `calc`/`calculator`, `convert`/`unit`, `currency`/`forex`, `qr`, `password`/`passgen`, `repeat`, `wpm`. Each list is editable per tool under that tool's own **Keyword shortcut** field.
- **None of these eight are build-gated.** Unlike the scanners and handwriting, which lean on ML Kit, every build ships all eight identically (see [Which build you have](https://wmkeyboard.pages.dev/start/editions/) if you're comparing builds).
- Every tool here has its own **Enabled** switch, and an **Icon colour** override if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/#browsing-every-tool) are on globally. Both live at the top of that tool's own settings screen, above everything documented in this page's Options sections.
---
# Vocabulary
URL: https://wmkeyboard.pages.dev/tools/vocabulary/
Section: Tools
Summary: Word cards, gentle nudges towards stronger words, and flashcards, from downloadable vocabulary packs.
The **Vocabulary** tool teaches you words while you type. Install a pack (the Word Smart and Barron's GRE lists come first), and when you type a plain word that one of the pack's words says better, a chip on the suggestion strip offers it: **hate → abhor**. Tap the chip for the word's card: its meaning, how to say it, where it comes from, and the words around it. Hold the chip to swap the word in. The same packs feed flashcards, in the keyboard and in the app, and a word of the day.
tools/vocabulary-card — The card for abhor: pronunciation, senses, synonyms and antonyms, the word family, and where the word came from.
## Getting a pack
Vocabulary is one of the [Extensions](https://wmkeyboard.pages.dev/reference/settings/extensions/): the tool, the packs, the word of the day and the nudges all go when the **Vocabulary** bundle is off, and come back with your progress kept.
Open Tools / Vocabulary / Vocabulary packs and download a list: Word Smart 1 and 2, Barron's 333, 800 and 1100, Magoosh 1000, Kaplan 900, Powerscore 700, SparkNotes 1000, Manhattan Prep 1000 and GregMat 1100. Each pack is a study list turned into a small dictionary: for every word, the part of speech, pronunciation in American and British English, up to three senses with an example sentence and dated quotations, synonyms and antonyms, the word family, the etymology, and a mnemonic. Definitions come from Wiktionary, with WordNet filling any gaps.
Packs also come from [add-on repositories](https://wmkeyboard.pages.dev/addons/overview/) (type `vocabulary`), from a `.wmvocab.json` file opened in a file manager (gzipped or not), or from your own lists (see below). Several packs can be on at once; a word that appears in more than one shows every list it belongs to on its card.
When the download cannot run here (the no-internet build, or a network that blocks it), **Get the pack files** under the installed packs lists every pack's address, each with a copy and an open button. Fetch the `.wmvocab.json.gz` file in a browser, then pick it with **Import a pack or flashcards**.
### Bringing your flashcards
**Import a pack or flashcards** at the bottom of the installed packs takes the cards you already have elsewhere, as well as `.wmvocab.json` files:
- **Anki**: a deck (`.apkg`) or a whole collection (`.colpkg`), from any Anki version, including the newer exports that older readers cannot open. Only the notes are read; images and sounds stay behind.
- **Quizlet**: the text from **Export**, with a tab or a comma between term and definition and a new line between cards. Save it as a `.txt` file and pick that.
- **Any CSV or TSV**: a spreadsheet saved with a word column and a meaning column. Anki's **Notes in Plain Text** export works too.
The front of each card becomes the word and the back its definition. When the columns or the note's fields have names, they are used: *Front*, *Word* or *Term* is the word, *Back*, *Meaning* or *Definition* the definition, and a field called *Example* or *Sentence* fills the example, *Reading* or *Pronunciation* the pronunciation. Formatting is removed. A word that is on more than one card gets one entry with each meaning as a sense. Cloze cards, and cards whose front is a sentence rather than a word (more than 64 characters), are skipped. The pack takes the file's name, or the deck's name for a whole collection, and appears with the other installed packs, where it can be switched off or deleted.
Translations are separate, small downloads per pack and language. The languages your keyboard is set to are fetched on their own when a pack lands or when you add a language (a romanised layout such as Banglish reads the Bangla glosses; a Croatian keyboard takes the Serbo-Croatian sidecar when a pack has no closer one), and you can pick others under **Translation languages** on the tool's page. The full list of a pack's languages folds out under it on the packs screen. Every gloss in another script carries a romanisation, so the card can show "ghrina" beside ঘৃণা.
Tools / Vocabulary / Browse words lists every word across your installed packs, searched by word or definition. Chips filter it to one pack or to all of them, and to All, New, Learning or Learnt; tap a word for its card, and a check mark shows the ones you have learnt.
## The nudge
The chip appears once you have typed the whole word, stays through the space or full stop after it, and goes away when you start the next word. It only ever offers; nothing is changed until you tap or hold.
| Setting | What it does |
|---|---|
| Offer stronger words | Turns the nudge on. Off, the chip and every other row below it disappear |
| Tapping the chip | Opens the card, or swaps the word. Holding does the other one |
| Also for the words themselves | Typing *abhor* offers its card, so you can check a word you half remember |
| Which words nudge | Words you have not learnt (the default), every word, or only words you have learnt, as practice |
| Sensitivity | How much plainer the typed word has to be. *Low* nudges on "hate" but not on "detest" |
| Repeat a nudge | Every time, once per text field, or once a day |
Nudges run last among the smart chips: a sum, a currency, "define hate", and a tool keyword all win over one.
A gloss in one of your languages nudges too: type ঘৃণা on a Bangla layout, or "ghrina" on the romanised one, and the chip offers *abhor*. A gloss of more than one word ("হেয় করা") is matched as a phrase.
## The card
The headword, its pronunciation and a speaker button come first, with the parts of speech, the lists the word is in and how far along you are as small tags. The recording is Wiktionary's when it has one and the network allows; otherwise a downloaded [offline voice](https://wmkeyboard.pages.dev/tools/read-back/#offline-voices) for the language says the word, or the phone's speech synthesiser does, in the accent you chose. Speech speed and pitch sliders tune the synthesiser, and disappear once Voice is pinned to the Wiktionary recording alone; **Play a sample** speaks *serendipity* to try the current voice, accent, speed and pitch together. In the app the rest is one section at a time: **Meaning** (senses grouped by part of speech, each with its example; quotations from print open on a tap, each with its year and a trimmed reference), **Related words**, the mnemonic set apart under **Remember it**, **In your languages** for the translations (each gloss with its own romanisation beside it; a language you downloaded by hand shows even when it is not one of yours), and **Where it comes from** with the origin chain up front and the etymology folded behind it. In the app, tapping a related word opens its own page when it is in a pack; any other is looked up online and shown in a sheet, from which it can go into *My words*. In the keyboard, a related word that is in a pack opens its card and one that is not is typed; holding always types it. Tools / Vocabulary / Tapping a synonym or antonym can change this, so a tap always types the word instead, or sends it to the Dictionary tool. The **Full dictionary** action hands the word to the [Dictionary tool](https://wmkeyboard.pages.dev/tools/search/#looking-up-definitions). The keyboard's card also has **Copy**, which copies the card as text, and with the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) on, **Ask AI**, which opens a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#asking-about-another-tools-text) about the card, and a long press on a definition or an example selects it, with **Ask AI**, **Copy** and **Insert** on a bar over it.
Every section of the card can be shown in the keyboard and the app, in the app only, or not at all, under Tools / Vocabulary / Card sections. The word family, the broader and narrower words, topics, etymology, origin chain, quotations from print, rhymes, hyphenation, forms, the Proto-Indo-European root, the Wikipedia link and when the word was first used are in the app only by default; the mnemonic and translations are everywhere.
## Learning
Mark a word **Learnt** from its card and it stops nudging. The **Review** tab (in the keyboard) and the Review screen (in the app) run flashcards over everything due today, plus new words up to the count set by Tools / Vocabulary / New words a day (10 by default): read the word, flip, grade yourself. The back of the card has the meaning in your language under the English one, unless Tools / Vocabulary / Card sections keeps translations off (in the keyboard, it takes translations shown there). Two schedulers are offered under Tools / Vocabulary / Flashcards: five **Leitner boxes** with gaps of 1, 3, 7, 14 and 30 days, or **SM-2**, the rule Anki uses. Both write the same record, so switching keeps your progress. Once today's queue is done, or there was nothing due to start with, a **Study ahead** row lets you pull tomorrow's cards in early.
The **word of the day** is the same for everyone who has the same packs: the words are laid out in a fixed order and each day takes the next, so every word comes round once before any repeats, and a device that has learnt the day's word moves on to the next unlearnt one. It shows as a card on the settings home, with the word's meaning in your language under the English one when a translation is downloaded, and as a chip on the suggestion strip. The chip comes back in a few text fields spread over the day (how many is a setting) until you tap it or dismiss it. The card's **X** puts it away until the next word. Under Tools / Vocabulary / Word of the day you can turn either off, or have a new word every 12, 6 or 3 hours, or every hour.
## Your own lists
Tools / Vocabulary / My lists shows every list you have made, each with its own switch, and a **New list** button. Name a new list to open its editor: type a word and the details fill in from an installed pack when one knows it, or from the network when you tap **Look up online**: Wiktionary's full entry (through kaikki.org, the same source the packs are built from), with the free dictionary API and Wiktionary's own definition endpoint as fallbacks when it is down. That order is the **Providers** list under Tools / Vocabulary, a [provider list](https://wmkeyboard.pages.dev/tools/providers/) you can reorder or switch sources off in. A look-up is one tap and a few kilobytes, so it is not held back by [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/). **Add many words** takes a pasted list, one word per line. **Add to my list** on any card files the word into a list called *My words*, and the Dictionary panel has the same chip.
A list exports as a `.wmvocab.json` file, the same format the packs use, so it can be shared, opened on another phone, or published in an add-on repository. Lists and imported packs travel in [backups](https://wmkeyboard.pages.dev/reference/file-formats/#backups); downloaded packs do not, since they can be downloaded again.
## Privacy
Everything here runs on the phone: the packs, the index that finds triggers while you type, and your learning record. The only network requests are the ones you start or set up: downloading a pack, the translations for the languages you set (fetched on their own only when Data saver allows downloads outright), a look-up you tap for in the list editor, and playing a Wiktionary recording.
---
# Voice typing
URL: https://wmkeyboard.pages.dev/tools/voice/
Section: Tools
Summary: Dictation through your device's speech engine: a full panel, a compact bar over the keys, or a draggable bar with the keyboard out of the way.
Voice typing turns speech into text using your device's own speech recognizer: no setup, works in any language your device supports. Tap the mic, talk, and words appear as you speak.
tools/voice-panel-listening — The voice panel: a pulsing mic button on the left, the handwriting-style action rail on the right.
## Starting dictation
Open **Voice typing** from the toolbar (or the toolbox if you haven't pinned it). What opens depends on **Voice typing view**, covered below. There are three of them:
- **Full panel** (the default) replaces the whole keyboard with a large mic button and a level-driven pulse ring. A four-key action rail runs down the right side: Delete, Space, a context-aware Enter, and a key that closes the panel and returns you to normal typing. In a search box that Enter key shows a search icon, rather than promising a newline it won't insert. Drag sideways on Delete to delete by word or by letter, the way the keyboard's own ⌫ swipe does, highlighting what letting go will take and giving it back as you drag back; drag sideways on Space to move the cursor. The collapsed bar's backspace takes the same delete swipe. Next to it a column of punctuation keys, . , ? !, puts a mark in by hand without saying it (see [Fixing it while you talk](#fixing-it-while-you-talk)).
- **Compact bar over the keys** replaces the suggestion strip with a small mic and status text, and the keys stay visible underneath. Dictate a sentence, then tap a letter key to fix a misheard word without leaving the view. While the mic is off and you're typing a word, the suggestion strip comes back over the bar, so you can pick the word (or, in Chinese, the characters for your pinyin). The bar returns as soon as the word is in.
- **Collapsed bar, keyboard hidden** hands the keyboard's whole window back to the app you're typing in and leaves a small draggable pill of dictation controls floating over it. See [The collapsed bar](#the-collapsed-bar).
You aren't locked into the one you picked. The panel and the compact bar each carry a collapse button down to the pill, and the pill carries a button back up, so switching is one tap from wherever you are.
Tap the mic to start listening. Tap it again to finish. While it's listening you don't have to hit the mic itself: in the full panel the whole area left of the action rail is the stop button (it picks up a faint tint to show it), and on the compact bar anywhere that isn't another button stops it. The stop press tends to happen mid-thought without looking, so it gets the biggest target on the screen.
The Voice tool on the toolbar is a stop button too. Tap it while a dictation is running on the panel or the compact bar and the phrase is finished exactly as if you had tapped the mic, then the view closes once the words are in. So one button starts a dictation and ends it, which matters most with [Whisper](https://wmkeyboard.pages.dev/tools/whisper/) or a [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/), where nothing is typed until you say you're done. To throw a dictation away instead, use the compact bar's close button or the panel's keyboard key.
With the Voice tool pinned to the toolbar, press and hold it instead of tapping and a small menu lists the three [voice typing modes](#typing-while-you-dictate), with the one in force ticked. Pick one and dictation starts in it at once, and that mode stays the mode until you pick another. It is the quick way to switch between one block at a time and typing while you speak without a trip through settings; **Hold the tool to pick a mode** on the Voice typing screen gives the hold back to the toolbar if you would rather bind it to another tool. The full panel adds walkie-talkie dictation on a long press: holding the mic past the **Hold to talk** threshold (200 to 1500 ms, default 600) starts listening only while your finger stays down, and releasing stops it immediately. A quick tap-and-release still toggles listening on, same as before. Neither bar has the gesture. Both bar mics are plain tap-to-toggle buttons.
long-press — Hold the mic in the full panel to dictate walkie-talkie style.
Recognized words stream into the text field live as you talk, underlined until the phrase is finished. That's the system recognizer and [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), the offline engine that hears as you speak. [Offline voice](https://wmkeyboard.pages.dev/tools/whisper/) covers how Whisper's panel differs: a whisper.cpp model shows its words under the microphone while it transcribes, and types them once it's done. WM Keyboard works out the spacing around what you dictate from the characters already next to the cursor, so starting mid-sentence or dictating over a selection doesn't leave a double space or a missing one. With **Automatic space after punctuation** on, a phrase that ends on a full stop, comma or other mark gets the space after it that a typed mark would, and a letter you type next on the keys takes a capital after a full stop the way it would after a typed one.
Every word you dictate is learned the same way a typed word is, so frequent dictated vocabulary starts showing up in suggestions and autocorrect too.
### If the mic can't open
- **Password and other secure fields** never get a mic. Tapping the voice tool in one shows a brief "You cannot use voice typing in a password field" toast and opens nothing. If you land in a password field with a view already open, the panel reads "Voice typing does not work in password fields." and the compact bar reads "Not available in password fields".
- **No microphone permission** shows an explanation and an "Allow microphone" button. Tapping it opens a system permission prompt through a small trampoline screen (IMEs can't show permission dialogs directly), and the keyboard rechecks automatically once you're back. The bars have room for one line, so they say "The app needs microphone permission" with an **Allow** chip.
- **No speech recognizer on the device** (a phone without Google services, such as a recent Huawei) means the system engine can't run. The panel says so, with a **Choose a voice engine** button that opens Voice typing settings, where an engine that runs on the keyboard itself, such as Whisper, works without any recognizer. Either bar reads "Speech recognition is not available".
### Opening on voice typing
If you dictate more than you type, switch on **Reopen voice typing where you left it** on the Voice typing screen. After that, closing the keyboard with the voice panel, the compact bar or the collapsed bar on screen means the next field you tap opens on the same view and starts listening. That holds when you send a message, switch apps or tap into another box.
You turn it off by leaving voice typing yourself. Press Back or the close button on the panel or the compact bar, the keyboard button on the collapsed bar, or the Voice tool, and the keyboard opens on its keys from then on. Open voice typing again and it sticks again. Password fields always open on the keys, and without the microphone permission the view asks for it instead of listening.
## Typing while you dictate
Voice typing / Voice typing mode
By default, dictation owns the field while it runs. The words being recognized sit there as composing text, and the first key you press ends the session. **Voice typing mode** offers two other ways to work, both of which leave the mic open while you use the keyboard.
- **One block at a time** (the default) is the behaviour described everywhere else on this page. Speak, watch the words appear live, and the first keystroke stops the mic.
- **Type while you speak** keeps the mic running through everything you do. Nothing goes into the field until you pause: each phrase lands whole, at wherever your cursor is by then. Keys, glide typing, suggestions, cursor moves, layout switches and panels-that-aren't-a-panel all carry on around it. A small mic chip sits in front of the suggestions; press and hold it for the whole control bar, with the language, the microphone and Cancel, in place of the suggestions. The down arrows on the bar fold it back to the chip.
- **Type while you speak, plain text** is the same, minus every text rule. No spoken punctuation, no punctuation from the recognizer, and no spaces added around what lands. You control all of it from the keys. It's the one to pick for code, terminals, and anywhere you want exactly the words you said and nothing else.
The two type-while-you-speak modes only make sense while the keys are on screen, and the only voice view that leaves them there is **Compact bar over the keys**. The full panel and the collapsed bar both take the keyboard's place, so with either of those the mode menu on the Voice tool offers **One block at a time** alone, and the hold gives itself back to the toolbar. Plain text also needs the system recognizer: it is the only engine that can be asked for no punctuation, while the offline models and a transcription server punctuate on their own, so plain text is left out of the menu for them. Settings lists the same choices, plus whichever mode is in force so you can move off it, and with another view it says which view brings the other two.
A pause is not a full stop. The recognizer capitalizes the first word of every phrase as if it opened a sentence, because it can't see the field; the keyboard can, and it keeps that capital only where the words in front of your cursor end a sentence: nothing at all, a new line, or a full stop, question mark or exclamation mark, in whatever script wrote it. A phrase that lands after a comma, or after a word you just typed, starts small. A phrase that does open a sentence gets its capital even from a recognizer that writes in lower case, so "done period" followed by "and then" comes out as "Done. And then". A first word spelled with a capital further in (iPhone, eBay) is left as it is. This is about what's written, not about the keys: the capital lands on the first word of an empty field whether or not that field asks the keyboard for sentence capitals, and whether or not **Automatic capitals** is on. The word I and its contractions keep their capital anywhere, and so does anything with a second capital inside it (NASA, McKinsey). A name at the start of a phrase can't be told from a sentence capital, so shift it the way you would a typed one. Spacing and the capital are read again after every phrase, so a long dictation with pauses in it comes out as one sentence rather than a row of them. Plain text is judged the same way. It asks the recognizer for no capitals, so usually there is nothing to decide, but the system recognizer puts one on regardless and Whisper has no such switch at all. When a capital does arrive, where your cursor sits still decides it. Plain text never adds one the recognizer left off, though. What plain text drops is the punctuation and the spacing, not the first letter of a sentence.
Sentences *inside* one phrase get the same treatment. A recognizer cases the phrase it was handed and nothing within it, so dictating "ready period this one" came back as "ready. this one". The keyboard raises the first letter after each full stop, question mark or exclamation mark. That works in any script that has capitals, behind a closing quote or bracket, and only where a space follows, so `3.5` and `site.com` are left alone. A stop that closes a single letter is read as an initial rather than an end: "e.g. this" and "9 a.m. tomorrow" keep their small words. Everything else stays as the recognizer cased it.
The reason the default can't do this is worth knowing: a partial result is cumulative. Each one rewrites the whole phrase, not just the new words, so a letter you type into the middle of it gets overwritten by the next partial a moment later. The two interactive modes solve that by never putting a partial in the field at all. You lose the live preview of the sentence, and in exchange you get the whole keyboard back while the mic is open.
Pair them with **Compact bar over the keys**, which is where they make the most sense. In that view an interactive session shrinks to a single pulsing mic at the left of the suggestion strip, and the strip keeps its room, so your candidates, the emoji row and the clipboard chip all stay reachable while you talk. Tap the mic to end the session. Tapping the voice tool does the same and puts the mic away once the last phrase has landed; with nothing running it just puts it away. If something goes wrong (no permission, no recognizer, no offline model), the full compact bar comes back for as long as it has something to tell you.
Two smaller things change in these modes. They always keep listening, whatever **Keep listening** is set to, because a session that ended at the first pause would leave you typing into a closed mic. And they're far more patient with silence: twelve quiet retries instead of two, which is roughly a minute and a half of not talking, because silence is the normal state of a mode where you speak a line and then spend a while fixing it by hand.
## Dictating into the keyboard's own boxes
The keyboard's own text boxes, such as the AI chat and its Custom prompt, the emoji, GIF, dictionary and clipboard searches, Find and replace, the clip editor and a plugin's text box, carry a small mic at the right end of their suggestion strip. Tap it and speak: the phrase goes into that box at its caret, in place of any selected text, and never into the app behind the keyboard. The Voice tool does the same while one of these boxes has the keys.
While it listens, the strip shows what has been heard so far in place of the suggestions. Tap the mic or anywhere on that line to finish, or the close button to throw the phrase away. Dictation stops at the first pause, whatever **Keep listening** is set to, and it ends by itself if the box closes before the words arrive.
It uses the same engine, language, spoken punctuation, spacing and capitals as dictation anywhere else. A search box drops the full stop a recognizer puts at the end of a phrase, so you search for `cat` rather than `Cat.`. Nothing dictated into these boxes is learned, the same as typing into them. A password field behind the keyboard doesn't stop you here, because the words never reach it.
The mic sits on the suggestion strip, so it only shows while **Suggestions** is on (Typing → Suggestions). The typing test, the word card's spelling and the Learn from text speller don't get one. Neither do the calculator and the converters, which have no strip.
## The collapsed bar
The third surface isn't a keyboard at all. Pick **Collapsed bar, keyboard hidden**, or tap the collapse button in the panel or the compact bar, and the keyboard gives its whole window back to the app you're typing in. What's left is a small pill of dictation controls floating on top. Only the pill itself is touchable: taps anywhere around it go through to the app underneath, so you can scroll the thing you're dictating into while you talk.
Drag it wherever you want it. Where it settles is remembered, so it comes back in the same place next time:
- Lying flat, it snaps to the nearest of three rests across the screen (left, centre or right) and keeps whatever height you dragged it to. It starts centred and docked at the bottom.
- Standing upright, it docks to whichever side edge you let go nearest, and keeps its position along that edge. It starts on the right, halfway down.
The hamburger swaps the row of controls for a second page: back, undo, the language chip, a button that stands the bar upright (or lays it flat again), and a shortcut into the voice settings. The first page has the status line, undo when there's something to undo, an exit button, backspace, and the mic.
That exit button is two different buttons depending on how you got here, which is worth knowing because they don't do the same thing:
- **Arrived by collapsing** from the panel or the compact bar, you get an expand button that takes you back to the surface you came from. It undoes the switch.
- **Chose the collapsed bar in settings**, and you get a keyboard button instead. That brings the keys back for now, and the bar stays your default for the next time you open voice typing.
The bar isn't just for one field. It stands in for the keyboard on every new field until you bring the keyboard back, and it survives the keyboard's process being killed in between. Two things put the keys back on their own without disarming it: a password field, which never gets the bar, and a panel opened over the top (a hardware shortcut can do that). Both end the current dictation session, and closing the panel brings the bar back.
## Undoing a dictated phrase
An **Undo** control appears (bottom-left in the panel, as a small icon on either bar) right after a phrase commits, as long as it's still sitting immediately before your cursor. Tapping it removes exactly that dictated text in one step. It disappears again once you dictate something else, edit the text some other way, or the text it would remove is no longer where dictation left it.
It stays while the mic is open, too. Tapped while a phrase is still being heard, it takes back that phrase instead: the words heard so far go, and listening starts over, so a sentence that came out wrong can be said again without stopping. Between phrases, with nothing being heard, it removes the last phrase that landed, as above.
The engines that record a clip and type it once it's transcribed, such as [Whisper](https://wmkeyboard.pages.dev/tools/whisper/), a [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/), [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/) and [Moonshine](https://wmkeyboard.pages.dev/tools/moonshine/), have no words to show while you talk, so for them Undo counts a phrase as being heard as soon as the clip has picked up speech. It shows from that moment, and tapping it throws the clip away untranscribed and starts a new one. Before you've said anything it removes the last phrase that landed, the same as with any engine. While a clip is being transcribed it can't be taken back yet; Undo then removes the phrase before it.
### Fixing it while you talk
In the default mode a phrase being heard sits in the field as one piece that the recognizer keeps rewriting, so nothing can be typed into it. The keys on the voice surfaces work around that rather than ending the dictation. A punctuation key, Delete, Space or Enter on the panel's rails, or the collapsed bar's backspace, pressed in the middle of a phrase lands the words heard so far as if you had paused, puts the key in after them, and goes straight back to listening. So "meet me at noon", a tap on ?, then "or later" comes out as one line with your question mark in it. The few words you say in the moment the mic is restarting can be missed, so press at a natural break.
The engines that record a clip get the same keys, a phrase at a time. A key pressed once the clip has picked up speech ends the clip there and sends it off to be transcribed; the key waits, goes in after the words when they land, and then the mic opens again for the next phrase, whether or not **Keep listening** is on. Keys pressed while a clip is still being transcribed, after a key, a pause or a tap on the mic, wait for it the same way and go in in the order you pressed them, so a quick ? then Enter still reads right. What these engines can't do is show the phrase in the field while you say it: there is no live text to type into until the clip is transcribed, which with a server or a large model can take a second or two. The mic stays open through that "Transcribing" stretch, so you can carry on talking: what you say goes into the next clip and lands after the key. A key pressed before you've said anything just goes in. Keys on the keyboard itself still end the dictation, as described under [Continuous mode](#continuous-mode); [Type while you speak](#typing-while-you-dictate) is the mode for typing freely with the mic open.
## Continuous mode
Voice typing / Keep listening
With **Keep listening** on (the default), finishing one sentence doesn't stop dictation. The next listening session starts on its own, so you can keep talking sentence after sentence without re-tapping the mic. Turn it off and each session ends after one utterance. Tap the mic again for the next one.
**Stop after a pause**, next to it, is for the engines that record a whole clip, [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) among them: off by default, and from 0.5 to 4 seconds it ends their clip by itself once you have said something and then gone quiet for that long. [Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/), [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) and [Offline Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/) write while you speak and hear the same pause with the **Phrase end detector** below. The system recognizer already stops on a pause and ignores it. With **Keep listening** off the pause ends the whole dictation, so nothing has to be pressed to finish.
### Hearing the end of a phrase
Voice typing / Phrase end detector
**Phrase end detector** decides, for Asr4All, Sherpa, Omnilingual, Vosk and Parakeet, when you have stopped talking: that is when **Stop after a pause** starts counting, and where Omnilingual cuts its phrases. It shows on the screen once one of those engines is on the **Recognition engine** list. Each of those engines' own pages (press its row on the list) also has an **End of a phrase** group with **Stop after a pause** and the detector, the same two settings as here. **Stop after a pause** is off by default, and while it is off a phrase only ends when you press the microphone, whichever detector is picked.
- **Auto**, the default, uses Silero where it can run and loudness everywhere else. Sherpa and Omnilingual models carry Silero, so they get it. Asr4All, Vosk and Parakeet get it once ONNX Runtime and a Silero copy are on the phone, and loudness until then.
- **Silero VAD** is a 2 MB network that tells speech from other sound, so a fan or traffic does not hold the phrase open. Sherpa and Omnilingual bring their own copy; for Asr4All, Vosk and Parakeet, the row under the choice downloads one (1.8 MB).
- **FireRedVAD** is a newer detector, 2.3 MB, downloaded from the row under the choice. On its makers' benchmark of 102 languages it scored an F1 of 97.6 against Silero's 96.0, and raised far fewer false alarms (2.7% of silence taken for speech, against 9.4%).
- **Loudness only** uses no model. Anything loud enough counts as speech, the way the pause stop has always worked for Whisper and the server: it costs nothing and downloads nothing, but a noisy room can keep it listening and a soft voice can fall under it.
- **Your own model** takes a Silero or TEN VAD `.onnx` file from **Pick file**. The keyboard opens it first and keeps it only if it is a model sherpa-onnx can run; for TEN, that means the copy in sherpa-onnx's releases, which carries the details it needs, not the one from TEN's own page. TEN is not offered as a download because its licence adds conditions to Apache-2.0.
The model detectors run on ONNX Runtime, the runtime Sherpa uses. A detector that cannot run yet (no runtime, or its file not downloaded) gives way to what **Auto** would pick, so dictation always has some way to stop by itself.
**Speech threshold**, under it, is how sure the detector must be that a moment is speech, from 10% to 90% (50% by default). Lower keeps listening through soft or distant speech, but noise can hold the phrase open; higher ends the phrase sooner in a noisy room, but a quiet voice can be taken for silence. For **Loudness only** it sets how loud counts as speech, and 50% is the level the pause stop has always used.
**Smart Turn**, off by default, adds a second opinion at each pause. [Smart Turn v3.2](https://huggingface.co/pipecat-ai/smart-turn-v3) listens to the last 8 seconds and judges from your voice, not your words, whether you finished what you were saying or stopped mid-thought: the falling voice at the end of a sentence, a word left hanging, an "um". When you sound finished, the phrase ends after a short pause (0.3 seconds, or 0.5 for Omnilingual, which waits that long to cut a phrase anyway); when you don't, it waits out the whole **Stop after a pause**. You can then set a long pause for thinking and still not wait at the end of every sentence. It was trained on 23 languages, downloads 8.7 MB from the row under the switch, and runs on ONNX Runtime. Until its model is on the phone, every pause is waited out as without it.
Whisper, Whistle, Moonshine and the transcription server record a whole clip and keep the recorder's own loudness stop; the detector does not apply to them.
**Reduce background noise**, below it, cleans the microphone's audio before an offline engine hears it: Whisper, Whistle, Asr4All, Parakeet, Moonshine, Sherpa, Omnilingual and Vosk. The system recognizer and the transcription server get the microphone as it is. It has three settings, and Off is the default.
- **Light** runs [RNNoise](https://github.com/xiph/rnnoise), a tiny network that adds about 10 ms. It makes the audio quieter to listen to, but a recognizer can lose words to it: on noisy test recordings (street, café and fan noise mixed into LibriSpeech at 0 to 10 dB) RNNoise 0.1.1 raised Whisper Tiny's word error rate from 27% to 33%. The 0.2 model it runs now did no better: on a second set made the same way it took Whisper Tiny from 23% to 29%, as 0.1.1 did. It is part of the offline engines' shared libraries, so on the Play build, or a GitHub or F-Droid build that has not fetched those yet, the first phrase after you turn it on goes through as recorded while they download.
- **Strong** runs [DPDFNet](https://github.com/ceva-ip/DPDFNet), a DeepFilterNet model, on ONNX Runtime, the runtime [Offline Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) uses. It keeps a little of the microphone under the cleaned audio, because a recognizer copes better with some noise left in than with speech scrubbed thin. On the same recordings it brought Whisper Tiny's error rate down from 27% to 24%, and Sherpa's English model's from 33% to 31%. It costs more: a 9 MB model that downloads from the row under the setting, about five times RNNoise's work for the phone, and 40 ms of delay. Until the model and the runtime are on the phone, Strong runs as Light.
Between sentences the mic is really closed for a moment: the recognizer has stopped hearing and is working out the words. The pulse ring goes away and the line shows "…" for exactly that stretch, so you can see when to wait. It comes back when the next sentence is listening. Pressing the mic while it's working out the words makes that sentence the last one.
That stretch has a limit. A recognizer that stops on its own pause is expected to send the words it heard, but some don't always: Transcribro, a Whisper-based recognizer often installed on phones without Google's, shows the words a moment after the pause and then sends its result many seconds later, or never, which left the spinner turning. Once the words of a finished sentence are on screen, the keyboard waits 3 seconds for the recognizer to confirm them; with no words at all it waits 12. Then it ends the sentence itself, keeps the words already shown, and carries on listening.
Continuous mode is also patient with silence: a pause that would normally time out the recognizer restarts listening quietly instead of surfacing an error, but only for two silent retries in a row. After that it gives up and goes idle, so an abandoned open mic doesn't sit there listening forever.
Dictation ends completely the moment you type a key, swipe, or tap a suggestion. There's no pausing and picking up where you left off. Partial results build up as one continuous chunk, so typing in the middle of an utterance would corrupt it rather than interrupt it. What was heard before the key stays in the field. With an engine that records a clip, such as Whisper or a transcription server, that means the clip isn't thrown away either: once it has picked up speech, the key finishes it, and its words land when the transcription is done. They go in after whatever you typed in the meantime, since the key can't wait for them. Whichever view you're in stays open afterward, so you can tap the mic to resume. The keys on the voice surfaces themselves don't end it (see [Fixing it while you talk](#fixing-it-while-you-talk)). A tap on the Voice tool is the exception among the ways out: it finishes the phrase first (see [Starting dictation](#starting-dictation)). Dictation also stops when you close the panel, dismiss either bar, or switch to a different tool panel.
That last paragraph describes the default mode. If you want the mic to survive typing, see [Typing while you dictate](#typing-while-you-dictate). Both interactive modes ignore the **Keep listening** setting and always chain.
## Spoken punctuation
Voice typing / Spoken punctuation
With this on (the default), saying certain words types the punctuation mark instead of the word itself. It only applies to finished phrases, never to the live partial text, and it knows four sets of words: English, Bangla, Portuguese and Spanish. The sentence marks come first:
| Say (English) | Types | Say (Bangla) | Types |
|---|---|---|---|
| "new paragraph" | two line breaks | "নতুন প্যারা" | two line breaks |
| "new line" | line break | "নতুন লাইন" | line break |
| "question mark" | `?` | "প্রশ্নবোধক চিহ্ন" / "প্রশ্নবোধক" | `?` |
| "exclamation mark" / "exclamation point" | `!` | "বিস্ময়সূচক চিহ্ন" / "বিস্ময়বোধক" | `!` |
| "full stop" / "period" | `.` | "দাঁড়ি" | `।` |
| "comma" | `,` | "কমা" | `,` |
| "colon" | `:` | "কোলন" | `:` |
| "semicolon" | `;` | "সেমিকোলন" | `;` |
Punctuation attaches straight to the word before it with no space. The trade-off is deliberate. While this is on, a sentence that uses one of these words as a word types the symbol instead. Turn it off if you want to say "period" or "comma" out loud and have it come out as a word.
English also knows the marks past the end of a sentence. Each one sits against its neighbours the way it would if you had typed it: an opening mark closes up to the word after it, a joining mark closes up on both sides, and an operator keeps a space on each side.
| Say | Types | Spacing |
|---|---|---|
| "ellipsis" / "dot dot dot" | `…` | after the word: `wait…` |
| "open quote", "close quote" | `"` | around the words: `said "hi" twice` |
| "open parenthesis", "close parenthesis" | `(` `)` | around the words: `see (page two) now` |
| "open bracket", "close bracket" | `[` `]` | same |
| "open brace", "close brace" | `{` `}` | same |
| "apostrophe" | `'` | joins: `rock'n` |
| "hyphen" | `-` | joins: `well-known` |
| "dash" | `-` | stands apart: `one - two` |
| "en dash", "em dash" | `–` `—` | joins |
| "slash" / "forward slash" | `/` | joins: `and/or` |
| "backslash" | `\` | joins |
| "underscore" | `_` | joins: `snake_case` |
| "at sign" | `@` | joins: `me@example` |
| "caret" | `^` | joins |
| "hashtag" / "hash sign" / "pound sign" | `#` | before the word: `#blessed` |
| "dollar sign" | `$` | before the word: `$5` |
| "tilde" | `~` | before the word |
| "percent sign" | `%` | after the word: `50%` |
| "degree sign" | `°` | after the word |
| "asterisk" | `*` | after the word |
| "copyright sign", "registered sign", "trademark sign" | `©` `®` `™` | after the word |
| "ampersand" | `&` | stands apart: `salt & pepper` |
| "plus sign", "equals sign" | `+` `=` | stands apart: `two + two = four` |
| "greater than sign", "less than sign" | `>` `<` | stands apart |
| "vertical bar" | `\|` | stands apart |
A symbol whose plain name is an everyday word only answers to its longer name, which is why so many of these end in "sign". "Fifty percent is greater than ten plus one" stays a sentence; "fifty percent sign" is `50%`.
Portuguese and Spanish know the names Gboard uses. A bare "ponto" or "punto" is not among them, because both are everyday words; say "ponto final" or "punto final" for the full stop.
| Say (Portuguese) | Say (Spanish) | Types |
|---|---|---|
| "novo parágrafo" | "nuevo párrafo" | two line breaks |
| "nova linha" | "nueva línea" | line break |
| "ponto de interrogação" / "interrogação" | "signo de interrogación" | `?` |
| "ponto de exclamação" / "exclamação" | "signo de exclamación" | `!` |
| "ponto final" | "punto final" | `.` |
| "vírgula" | "coma" | `,` |
| "dois pontos" | "dos puntos" | `:` |
| "ponto e vírgula" | "punto y coma" | `;` |
| "reticências" | "puntos suspensivos" | `…` |
| "abre aspas", "fecha aspas" | "abrir comillas", "cerrar comillas" | `"` |
| "abre parênteses", "fecha parênteses" | "abrir paréntesis", "cerrar paréntesis" | `(` `)` |
| "hífen" | "guion" | `-` |
| "travessão" | | `—` |
| "arroba" | "arroba" | `@` |
Any other language has no table, so its dictation is typed exactly as it was heard.
## Adding punctuation and capitals
Voice typing / Add punctuation and capitals
Three of the offline engines write the words they hear and nothing else. Omnilingual, [Vosk](https://wmkeyboard.pages.dev/tools/vosk/), and every [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) model but the German, Spanish, Korean and eight-language ones, hand over a phrase in lower case with no full stops or commas, so a minute of dictation would come out as one long run of words. **Add punctuation and capitals**, on by default, fixes that on the phone. When a phrase is finished, a punctuation model reads it and puts in full stops, commas and question marks, and the first letter of every sentence becomes a capital. The row shows only while Sherpa or [Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/) is on the engine list, and not in **Type while you speak, plain text**.
The marks are the language's own: `।` after Bangla and Hindi, `،` and `؟` in Arabic, `۔` in Urdu, `。` and `,` in Chinese, `、` in Japanese, and an opening `¿` on a Spanish question. Names get their capitals from the keyboard's word lists and from the capitals it has learned from your typing, so "london" becomes "London" once a list or you have spelled it that way. An English phrase also takes the name capitals of Sherpa's **Punctuation (English)** model when that is downloaded.
The model is Horizon Labs' punctuation-restoration-small, an mmBERT model trained in 88 languages, made smaller for phones. Download it from the **Punctuation model** row under the setting: 151 MB from the keyboard's data repository at a fixed version, every file checked against its SHA-256, and about 90 MB of memory while it is loaded. A phrase takes a few tens of milliseconds after you stop. Until it is downloaded, English still gets punctuation from **Punctuation (English)** if you have that model, and other languages stay as the engine wrote them. It needs ONNX Runtime, the same on-demand part Sherpa and Omnilingual run on.
**Which punctuation model**, under the setting, narrows that. **Whichever is on the phone** is the behaviour above. **Multilingual only** ignores the English model. **English only, the small one** ignores the big model, so an English dictation gets by on 8 MB and nothing else is punctuated. **Import a punctuation model** takes any [sherpa-onnx online punctuation model](https://k2-fsa.github.io/sherpa/onnx/punctuation/index.html): pick its `.onnx` file and `bpe.vocab` together, name it, and give it language codes. It goes before the listed models for those languages. Sherpa's other kind of punctuation model, the offline CT-Transformer, is refused.
It was picked by measurement. News paragraphs from FLORES-200, with their punctuation and capitals taken out, were put back together by each candidate. On the marks it scored 0.81 F1 in English, 0.73 in Bangla, 0.78 in Hindi, 0.84 in Spanish, 0.73 in Arabic and 0.83 in Chinese, ahead of every other model tried in each of them. Full stops and question marks are its strong side; commas are where it misses most. It never types a colon, a dash, an exclamation mark or quotes. Thai, Lao, Khmer, Burmese and Tibetan are not among its languages, and a dictation in a language it lacks is left alone.
Engines that punctuate for themselves are never touched: Whisper, Whistle, Asr4All, Parakeet, Moonshine, those four Sherpa models, the phone's recognizer and a transcription server. Nor is dictation into the keyboard's own search boxes. [Spoken punctuation](#spoken-punctuation) still works with it on: a mark you say goes in first, and the model adds nothing next to it.
## Tidying dictation with AI
A recognizer writes down what you said, word for word: the "um"s, the false starts, the word you said twice, and both halves of "let's meet at three, no, four". **Tidy dictation with AI**, off by default, hands each phrase to the model the [AI tool](https://wmkeyboard.pages.dev/tools/ai/) uses, the way Gboard's Rambler does on Google's servers, and puts the cleaned version in its place. Filler and repeats go, a spoken correction keeps only the correction ("let's meet at four"), and punctuation and obvious mishearings get fixed. Your words, your language and your tone stay; it doesn't summarize, translate or answer what you said.
The phrase goes into the field as heard first, and the surface says **Tidying up…** while the model works. The cleaned text only replaces it if the phrase is still exactly what sits in front of your cursor, so typing, moving the cursor or leaving the field in the meantime just keeps what you said. So does a model that fails, gives nothing back or is still thinking after 15 seconds. Nothing you dictated is ever lost to the rewrite. The next phrase of a continuous dictation starts once the answer is in, and tapping the mic while it's tidying makes that phrase the last one. **Undo** takes back the tidied phrase.
It uses whatever provider and model the AI tool is set to, cloud or on-device, and is skipped without a word when that provider isn't set up yet, when [data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is holding cloud AI back, in password fields, in the keyboard's own search boxes, for phrases of only a word or two, and in **Type while you speak, plain text**, whose point is the words exactly as said. A small on-device model tidies less reliably than a cloud one, and it holds the next phrase back for longer.
## Language and engine
Voice typing / Voice typing language
Out of the box, dictation listens for the language of the layout you're typing on, and switching layouts switches it. **Voice typing language** lets you break that link. It opens a list with **Follow the keyboard** at the top, your keyboard languages under it, and then the other languages speech recognition commonly handles:
- **Follow the keyboard** is the default and works the way dictation always has.
- **One language ticked** keeps dictation in that language whatever the layout. Type on your Bangla layout and still dictate in English, say.
- **Several ticked** lets you speak any of them. Dictation starts in your keyboard's language when it's one of the ticked ones, otherwise in the first you ticked.
How well "several" works depends on the engine. The system recognizer is asked to detect the language among your picks, and to follow a switch in the middle of a sentence, on Android 14 and later. That's a request, and only recognizers that support it (Google's is one) act on it. Others, and older Android versions, hear the starting language alone. [Offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) work the language out themselves with several picked. A whisper.cpp Whisper model chooses among your picks only, whatever they are. A LiteRT Whisper model chooses among every language it knows, so give it a multilingual one that covers all of them; its grouped models can only be told one language, and with one picked, they are.
A language chip sits in the corner of the voice panel whenever there's a choice to make: two or more keyboard languages, or a choice already made. It shows what dictation listens for, **EN** or **EN+BN**, and it's drawn brighter when you've picked rather than followed the keyboard. Tap it for a menu: **Follow the keyboard**, each of your keyboard languages, **All my languages** to listen for every one of them at once, and **More languages…** to open the full list in settings. A pick from the menu is saved, the same as one made in settings, and a session that's listening starts again in it.
The compact bar over the keys can carry the same chip. Turn on **Language button on the compact bar** (it shows under **Voice typing view** while the compact bar is the one picked) and the chip sits between the status text and the undo button, with the same menu behind it. It's off by default because that bar has room for one line.
On Android 12 and up the system recognizer prefers to run fully on-device, once your phone has that language's model installed. It's faster, needs no network, and skips the little beep before it starts listening. If a language's on-device model can't handle recognition, WM Keyboard falls back to the network recognizer transparently. The same happens when the on-device recognizer opens the microphone and then hears nothing at all, with no level and no words for the first couple of seconds. Some phones' on-device model stalls that way, which used to look like a dead microphone. That language then stays on the network recognizer until the keyboard restarts. **Prefer on-device recognition**, shown while the phone's recognizer is on the engine list, turns the on-device recognizer off altogether.
**Recognition service**, on the same screen, picks which app does the recognising. **Phone's default** is whichever one Android has set; the list also holds every other app on the phone that offers speech recognition to other apps, such as Marmalade, Transcribro or FUTO Voice Input. Pick one and the mic sends your speech to it instead, with no change to anything else on this page. **Prefer on-device recognition** is hidden while another app is picked, since it only steers the phone's default. If that app is uninstalled, dictation goes back to the phone's default, and the row says the app is gone until you pick again.
### Hinglish, Banglish and other romanized languages
A romanized language is dictated as the language itself. No engine knows "Hindi in Latin letters" as a language of its own, so with **Hinglish · Hindi (Romanized)** picked, every engine is asked for Hindi, and the Devanagari it writes is then spelled the way you'd type it on the Hinglish layout. "कल मीटिंग है, प्लीज कैलेंडर पे डाल देना" lands as "Kal meeting hai, please calendar pe daal dena". The live words change script before they reach the field, so you never see the Devanagari.
Each word goes through that language's spelling lists first. An English word the recognizer wrote in the native script comes back as the English word when the lists know it (मीटिंग is "meeting", not "miting"), and a common word takes the spelling people chat in. Every other word is spelled out by rules for its script. Tamil, Telugu, Kannada, Malayalam and Sinhala keep every vowel they say (nuvvu ekkada unnaavu, kohomada). Roman Russian follows Translit (privet, horosho). Words the recognizer already wrote in Latin letters stay as they are, and native digits come out as 0–9. Konkani's Romi has spelling rules of its own, so it is dictated as before.
Languages & layouts / Hinglish / Voice typing / Romanized dictation model
Rules spell a letter the same way every time, so a long or rare word can come out stiffer than anyone would write it. The **Romanized dictation model** is for those words. It is IndicXlit, the transliteration model by [AI4Bharat](https://github.com/AI4Bharat/IndicXlit) behind [Neural suggestions](https://wmkeyboard.pages.dev/languages/phonetic/#neural-suggestions), trained the other way round: from Bangla, Hindi, Urdu, Assamese, Marathi, Nepali, Gujarati, Punjabi, Odia, Tamil, Telugu, Kannada or Malayalam script back to Latin letters. Download it from the romanized language's page (Hinglish, Banglish and the rest), or from the native language's page under Neural suggestions. There is no switch: once it is on the phone, dictation uses it.
The phrase still lands the moment it's heard, spelled as above. Then, before the next phrase starts, the model spells again each word of five letters or more that the lists didn't know, and the phrase is swapped for that version if it is still right in front of the cursor. Live words aren't touched. Shorter words stay with the rules: a common word like है or কি that the lists leave out reads better spelled by the rules, and the model tends to hear it as an English word ("key" for की). On 1,500 test words a language from AI4Bharat's Aksharantar set, the model spelled 54% the way people write them, against 29% for the rules alone; Hindi went from 39% to 60%, Bangla from 26% to 44%.
It is a 13 MB download from the data repository's `models` release, checked against a SHA-256 built into the app, plus ONNX Runtime (about 33 MB) if nothing else has fetched it yet. It runs on the phone, and nothing you say leaves it. When the model isn't there, or hasn't got through a phrase's words within about half a second, the rules' spelling stays. Delete it from the same row, or from **Downloads** on the storage screen.
Hinglish dictation runs on whatever engine, model and server Hindi is set to, and the same goes for every romanized language. Whistle knows none of them and steps aside for the next engine on the list. Picking Hindi and Hinglish together leaves the Devanagari as it was heard, since nothing says which of the two a word was meant for. With [Tidy dictation with AI](#tidying-dictation-with-ai) on, the model is told the phrase is Hindi (or Bangla, or Tamil) in Latin letters, so it keeps it in Latin letters and puts an English word the rules spelled by sound back to its English spelling.
### An engine for each language
Voice typing / Engine for each language
**Engine for each language** gives each of your keyboard languages its own engine, and for Offline Whisper, Asr4All, Parakeet, Moonshine, Sherpa, Omnilingual and Vosk its own model: English on the system recognizer and Bangla on a Whisper model, say. Tap a language to see what can take it on this phone. Only engines that know the language and models that are downloaded are listed. **Default** follows the **Recognition engine** list. Dictation picks the engine for the language it starts in.
Turn on **Change engine from the voice tool** and the voice panel gets an engine chip in its bottom left corner. It names the engine the language you dictate in uses, and a tap lists the same choices for that language, with **As in settings** to go back to the default. A pick is saved the same way as one made in settings, and a session that's listening starts again on it.
On Android 13 and up, a chip offers to download a model that's available for your language but not installed yet. It's named for the language you're actually dictating in: "Get Spanish for offline voice typing", "Get Bangla for offline voice typing", and so on. A dictation language the catalogue doesn't recognise reads "Get this language for offline voice typing" rather than guessing. Tap the chip to start the system's own download. Progress shows on the chip until the model is installed.
## Choosing the microphone
Voice typing / Microphone
All of this is behind **Microphone switch**, which is on by default. Turn it off and voice typing always listens through the microphone Android picks: the keyboard never moves the audio to a headset, the **Microphone** row is hidden and the switch leaves every voice view.
By default voice typing listens through whichever microphone Android picks, which is a wired or USB headset when one is plugged in and the phone's own microphone otherwise. Android does not switch dictation to a Bluetooth headset on its own. **Microphone** changes that: choose **Bluetooth headset**, **Wired headset**, **USB microphone** or **Phone microphone**, and voice typing uses that kind whenever one is connected. When none is, it goes back to the automatic choice, so a headset left at home doesn't leave you with a dead mic.
When more than one microphone is connected, every voice view gets a microphone switch: the bottom right corner of the panel, beside the collapse button on the compact bar, and next to the status line on the collapsed bar (in the column on the upright one). Its icon is the kind in use: a phone, a headset, USB or Bluetooth. It's filled while a microphone you picked is the one listening, and outlined while Android's own choice is. Tap it to move to the next connected microphone. The pick is saved as the **Microphone** setting, and a session that's listening starts again on the new one.
When a session starts on a microphone the keyboard hasn't named yet, it says which, with the device's own name: "Using Bluetooth mic (X7)", "Using Built-in mic (Pixel 8)". The panel shows it as a toast along its bottom edge for a couple of seconds, and the two bars show it in their status line until the first words arrive. It's said after a tap on the switch, when the microphone changes, and on the first dictation each time the keyboard opens, not on every sentence of a continuous session. In **Type while you speak**, the compact bar shrinks to a single microphone button so the suggestions keep their room, and that button has no switch beside it. Change the microphone in settings there, or from the panel.
How far the choice reaches depends on the engine. [Offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) and the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) record with the keyboard's own capture, so every kind works for them. The system recognizer records the audio itself, and the only way to move it is to route the phone's call audio to a Bluetooth headset, so with it the chip offers **Auto** and **Bluetooth** only. A Bluetooth headset's microphone works over the same link a phone call uses, so while it's in use music on that headset drops to call quality. The keyboard gives the link back about a second after dictation stops.
## Options
Voice typing
Everything below lives on the Voice typing screen, under Features on the settings home screen, rather than on the tool page. The microphone can be reached from a key or a hardware shortcut with the tool nowhere on the toolbar, so these aren't really the tool's settings.
| Setting | Default | What it does |
|---|---|---|
| Use voice typing | On | The switch at the top of the screen. Off removes the microphone from the whole keyboard and hides every other row here. See [Turning voice typing off](#turning-voice-typing-off). |
| Voice typing view | Full panel | A choice of three, not a switch: which surface the mic button opens. **Full panel**, **Compact bar over the keys**, or **Collapsed bar, keyboard hidden**. |
| Language button on the compact bar | Off | Shown while **Voice typing view** is the compact bar. Adds the language chip to the bar, so you can pick what dictation listens for without opening the panel. See [Language and engine](#language-and-engine). |
| Voice typing mode | One block at a time | Another choice of three: whether the keys work while the mic is open. **One block at a time**, **Type while you speak**, or **Type while you speak, plain text**. See [Typing while you dictate](#typing-while-you-dictate). |
| Hold the tool to pick a mode | On | A press and hold on the toolbar's Voice tool opens a menu of the three modes; picking one makes it the mode and starts dictation. Off gives the hold back to the toolbar's own press-and-hold action. |
| Reopen voice typing where you left it | Off | Close the keyboard with a voice view open and the next field opens on that view, already listening. See [Opening on voice typing](#opening-on-voice-typing). |
| Pause media during voice typing | Off | The keyboard takes the audio focus while the microphone listens, so music, podcasts and video pause. They resume about a second after dictation ends, which keeps a continuous session from restarting the music between sentences. An app that ignores the request keeps playing. |
| Microphone switch | On | Off leaves the microphone to Android and takes the switch off the voice views. See [Choosing the microphone](#choosing-the-microphone). |
| Microphone | Automatic | Which microphone voice typing listens through: **Automatic**, **Bluetooth headset**, **Wired headset**, **USB microphone** or **Phone microphone**. A pick is preferred whenever one of its kind is connected, and automatic stands in when none is. See [Choosing the microphone](#choosing-the-microphone). |
| Change engine from the voice tool | Off | An engine chip on the voice panel for the language you dictate in. See [An engine for each language](#an-engine-for-each-language). |
| Hold to talk | 600 ms | How long you hold the microphone before a tap becomes a press-and-hold, from 200 to 1500 ms. Raise it if a slow press keeps stopping your dictation. Lower it if you mean to hold and it keeps latching on. |
| Keep listening | On | Starts the next sentence automatically after each one commits. |
| Keep the screen on | On | The screen stays on while the microphone is open or a clip is being transcribed. The screen turning off closes the keyboard, and that ends the dictation: a press of the power button still does, and loses what was being said. |
| Stop after a pause | Off | Ends a phrase by itself once you have said something and then gone quiet for this long, from 0.5 to 4 seconds. See [Continuous mode](#continuous-mode). |
| Phrase end detector | Auto | How Asr4All, Sherpa, Omnilingual and Vosk hear that pause: **Auto**, **Silero VAD**, **FireRedVAD**, **Loudness only** or **Your own model**. See [Hearing the end of a phrase](#hearing-the-end-of-a-phrase). |
| Speech threshold | 50% | How sure the phrase end detector must be that a moment is speech. |
| Smart Turn | Off | At each pause, ends the phrase early when you sound finished, and waits out the whole pause when you don't. Needs its 8.7 MB model. |
| Spoken punctuation | On | Saying "comma", "question mark" or "দাঁড়ি" types the mark. |
| Add punctuation and capitals | On | Full stops, commas, question marks and sentence capitals for Sherpa, Omnilingual and Vosk, which write bare words. Needs its 151 MB model, downloaded from the row under it. See [Adding punctuation and capitals](#adding-punctuation-and-capitals). |
| Tidy dictation with AI | Off | Sends each phrase to the AI tool's model to take out filler words, repeats and spoken corrections. Hidden in plain text mode. See [Tidying dictation with AI](#tidying-dictation-with-ai). |
| Voice input for other apps | Off | Puts WM Keyboard on Android's list of voice input apps, so other apps' microphone buttons can use these engines. See [Voice input for other apps](#voice-input-for-other-apps). |
The screen reads from the top: **Recognition engine**, then a **Languages** group with **Voice typing language** and **Engine for each language** (see [Language and engine](#language-and-engine)), then **Dictation** with the view, the mode, **Keep listening**, **Stop after a pause**, **Phrase end detector** (with **Speech threshold** and **Smart Turn** under it, while Asr4All, Sherpa, Omnilingual or Vosk is on the list), **Spoken punctuation**, **Add punctuation and capitals** and **Tidy dictation with AI**, then **Other apps** with **Voice input for other apps**. The rest sit in a closed **Advanced** fold under it: **Hold the tool to pick a mode**, **Reopen voice typing where you left it**, **Pause media during voice typing**, the microphone switch and pick, **Change engine from the voice tool**, **Hold to talk** and **Reduce background noise**. **Words to listen for** is a fold of its own.
At the top of the screen, **Recognition engine** is a [provider list](https://wmkeyboard.pages.dev/tools/providers/) of dictation engines. Dictation uses the first one on it that is switched on and can run on the phone; the others are there for a language that has an engine of its own, and the phone's recognizer answers when nothing on the list can. **Add a provider** offers **Phone's recognizer**, **Whistle** on a 64-bit ARM phone, a small fast model for seven languages (see [Offline voice (Whistle)](https://wmkeyboard.pages.dev/tools/whistle/)), **Whisper** in an edition with the offline engine built in (see [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/)), **Asr4All**, English dictation that shows the words while you speak (see [Offline voice (Asr4All)](https://wmkeyboard.pages.dev/tools/asr4all/)), **Parakeet**, 25 European languages that the model tells apart (see [Offline voice (Parakeet)](https://wmkeyboard.pages.dev/tools/parakeet/)), **Moonshine**, a small quick English model (see [Offline voice (Moonshine)](https://wmkeyboard.pages.dev/tools/moonshine/)), **Sherpa**, streaming models for 13 languages including Bangla that type while you speak (see [Offline voice (Sherpa)](https://wmkeyboard.pages.dev/tools/sherpa/)), **Omnilingual**, one model for about 380 keyboard languages, many with no other voice typing (see [Offline voice (Omnilingual)](https://wmkeyboard.pages.dev/tools/omnilingual/)), **Vosk**, small models for 29 languages that type while you speak and suit an older phone (see [Offline voice (Vosk)](https://wmkeyboard.pages.dev/tools/vosk/)), and **Transcription server** in every build, which sends each phrase to a speech-to-text server you run (see [Transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/)). A transcription server can be added more than once, each with its own address, key, model and route; the first one on the list is the one dictation sends to. Press an engine's row to open its own screen: its models for an offline engine, **Recognition service** and **Prefer on-device recognition** for the phone's recognizer, **Translate to English** for Whisper, and what every server is sent with for a transcription server.
Until you edit the list, it is worked out from your earlier choice: that engine first, then your transcription server if you had one, then the phone's recognizer. "Use the system recognizer" on the voice panel moves the phone's recognizer to the top of the list. The phone's recognizer's **?** carries this note about what it sends:
> Recognition uses the speech recognizer of your device. On Android 12 and later it runs on your device when the language model is installed. If it is not, the audio goes to the recognizer service while you speak. Press and hold the microphone to speak in walkie-talkie style. It stops when you let go.
### Turning voice typing off
If you never dictate, turn off **Use voice typing** at the top of the Voice typing screen. With it off, the keyboard never opens the microphone:
- The Voice tool leaves the toolbar and the toolbox, and a key, a hardware shortcut or a press-and-hold bound to it does nothing. On the Tools screen its row says that voice typing is off, and its own switch can't be turned on until you turn voice typing back on.
- The keyboard's own boxes (the search, translate and AI fields, and the rest) lose the microphone at the end of their strip.
- The [collapsed bar](#the-collapsed-bar) doesn't show, even if you left the keyboard collapsed to it.
- A dictation that's running when you switch it off stops, and its view closes.
Nothing of voice typing is loaded while it's off. The system recognizer is only asked for when a dictation starts, and an [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/) model is only read when a phrase is transcribed, so a keyboard that never dictates never holds either one in memory. The rest of the screen is hidden while the switch is off, but your choices are kept for when you turn it on again.
### Words to listen for
A recognizer trained on everyone's speech spells a name its own way. The **Words to listen for** group tells it about yours. It shows for the system recognizer, the transcription server, [Whistle](https://wmkeyboard.pages.dev/tools/whistle/), Whisper's whisper.cpp models and [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/#words-to-listen-for). Engines that can't take a hint (Whisper's older LiteRT models, Asr4All, Parakeet, Moonshine, Omnilingual, Vosk) don't show the group while they're the first engine on the list.
- **Listen for my words** (on by default) sends a list of your own words with each phrase: the words you added by hand, the words in Android's personal dictionary (when **Use the Android dictionary** is on), and the words the keyboard learned from your typing that no word list has, such as names, most used first. Common words the recognizer already knows are left out, since they don't help. The system recognizer also gets permission to use what it knows about your phone itself, such as your contacts; the keyboard never reads your contacts for this.
- **More words to listen for** takes your own list, separated by commas. These go first, ahead of the words from your dictionary.
The system recognizer gets up to 200 words as a biasing list. It reads the list on Android 13 and later, and only if the recognizer on your phone supports biasing: Android leaves that to the recognizer, and one that doesn't ignores it. A transcription server gets up to 150 words as part of its prompt; see [Words and the prompt](https://wmkeyboard.pages.dev/tools/transcription-server/#words-and-the-prompt) for why that list is shorter and how it's ordered. Sherpa gets the same 200 as hotwords, less any its model can't spell.
Tools / Voice typing
The tool page carries only what belongs to the button:
- **All voice typing settings**: opens the screen above.
- **Enabled**: whether Voice typing appears on the toolbar and in the toolbox. Turning the tool off only takes the button away; the microphone is still reachable from a bound key and from the keyboard's own boxes. To switch voice typing off everywhere, use [Use voice typing](#turning-voice-typing-off) instead.
- **Icon colour**: only shown if [colorful tool icons](https://wmkeyboard.pages.dev/tools/overview/) is turned on globally. Overrides just this tool's icon color.
- **Keyword shortcut**: the words that make the [Smart chips](https://wmkeyboard.pages.dev/smart/chips/) suggestion offer to open this panel.
## Voice input for other apps
Voice typing / Voice input for other apps
Many apps have a microphone button of their own: the search box in a browser or a launcher, a notes app, a messaging app's voice search. Those buttons ask Android for its voice input, which on most phones is Google's. Turn on **Voice input for other apps** and WM Keyboard joins the list Android picks from, so those buttons can hear with the engines set up on this screen, with the keyboard closed.
Turning the switch on changes nothing by itself. Pick WM Keyboard as the phone's voice input with **Choose the phone's voice input**, the row that appears under the switch. It opens Android's own page for it. Where that page lives depends on the phone: under **Voice input** in the language and input settings, or behind the gear of **Digital assistant app** in **Default apps** on recent Android versions. Turn the switch off again and WM Keyboard leaves the list; Android then goes back to another voice input app on its own.
There are two ways an app can ask, and both are covered:
- **The voice input window.** Most microphone buttons open a small window over the app. WM Keyboard's comes up at the bottom of the screen and starts listening at once, with the app's own prompt or "Speak now" over the microphone. Engines that hear as you speak (Sherpa, Omnilingual, Vosk, Asr4All) show the words as they come. The words go back to the app when you pause, or when you tap the microphone. Cancel, Back or a tap above the window returns nothing. If no engine is ready for the language, the window says so and offers **Set up**, which opens this screen.
- **The speech recognizer.** Some apps, and the assistant, talk to Android's speech recognizer without opening a window. Once WM Keyboard is the phone's voice input, it answers those requests the same way, and apps that show the words as they come get them from the engines that can tell.
Each request uses the engine of its language, as the keyboard's own microphone does. The language is the one the app asks for when WM Keyboard knows it, so a search box set to French is heard in French; otherwise it is your **Voice typing language**, or your first keyboard language when that follows the keyboard. A phrase ends at a pause. **Stop after a pause** sets how long; it is off by default for the keyboard, where you stop with the microphone, so other apps get 1.5 seconds unless you set it or the app asks for another length. **Spoken punctuation**, **Add punctuation and capitals**, **Reduce background noise**, **More words to listen for** and the picked microphone all apply. Things that only make sense in a text field, such as undo, the spacing around the cursor and AI tidying, are left to the app.
Where the audio goes follows the engine you picked, and never anywhere else:
- An offline engine (Whisper, Whistle, Sherpa and the rest) keeps it on the phone. If its model isn't downloaded, another downloaded model for the language stands in; if there is none, the request ends with an error rather than going to a recognizer you didn't pick.
- A transcription server gets the phrase. Without an address, a downloaded model stands in.
- With **Phone's recognizer** as the engine, the request goes on to another app's recognizer: the one picked under **Recognition service**, or the phone's own. On a phone with no other recognizer, a downloaded model stands in.
With **Use voice typing** off, WM Keyboard's engines never hear anything. If WM Keyboard is still the phone's voice input then, each request goes straight on to another app's recognizer, so other apps' microphones keep working.
The first request needs the microphone permission. The voice input window asks for it. A request through the speech recognizer can't ask, so WM Keyboard opens its permission prompt over the app if Android allows it, and the request ends with a permission error until you grant it.
## Privacy: what leaves your device
On-device recognition (Android 12+, once the language's model is installed) never sends audio anywhere. Without that model installed, dictating with the system recognizer sends your audio to the OS's speech recognition service while you talk (the same behavior any app using Android's standard speech APIs has). See [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the fuller picture of what WM Keyboard can and can't send.
Want a hard guarantee that audio never leaves the device, whichever model is installed? [Offline voice typing with Whisper](https://wmkeyboard.pages.dev/tools/whisper/) is a second dictation engine that transcribes entirely on-device. It's a bigger download and it doesn't stream words live the way the system recognizer does. In exchange it makes no network calls at all, beyond the one-time model download.
Want the phone to do less work instead? A [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) sends each phrase to a server you choose, on your own network or a hosted service, and types what comes back. The audio goes to that address and nowhere else.
With **Listen for my words** on, the list of your own words goes to the same place the audio goes: the system recognizer, or your transcription server. On-device recognition, and the offline engines (Whistle, Whisper, Sherpa), keep it on the phone too.
With **Tidy dictation with AI** on, the text of each phrase (never the audio) also goes to the AI tool's provider. Pick an on-device model there to keep it on the phone.
[Voice input for other apps](#voice-input-for-other-apps) sends the audio of another app's request to the same place the keyboard's microphone would, and the words back to that app. The app gets the text, as it would from any voice input.
## Details & edge cases
**Errors show specific messages.** A network problem says "No connection. Check your network and try again." A busy recognizer, or another app holding the mic, says "The microphone is busy. Close the other apps that use it and try again." A language the device can't recognise at all says "Speech recognition does not support this language on your device." A network hiccup mid-utterance keeps whatever was already heard instead of throwing it away. The Microphone access tile in Quick Settings, or the developer Sensors off toggle, can let a session start and then hand it silence. The panel notices and says "The microphone is blocked. Turn on Microphone access in Quick Settings, then try again." Either bar says "Microphone blocked in Quick Settings". Tap the mic again once you've turned access back on.
**Switching tools closes dictation.** Opening any other panel (Handwriting, Clipboard, Emoji, and so on) ends the current dictation session first.
**Every row carries the usual restore button** once you move it off the default listed above. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). Where the collapsed bar last rested isn't one of those rows. It has no control on the settings screen, and you reset it by dragging the bar somewhere else.
**The three views aren't full equivalents.** Continuous mode, spoken punctuation, and undo behave the same way whichever one you're in, because they live in the dictation session rather than in the view. Two things are panel-only: hold-to-talk, and the offline-model download chip. The language chip sits on the panel and, behind the hamburger, on the collapsed bar. The compact bar gets one only with **Language button on the compact bar** on. Without it, dictation still listens for the language you picked, or follows your layout, and you change it in settings or with a layout switch instead. Neither bar prompts you to download an offline model.
Related: [Offline voice (Whisper)](https://wmkeyboard.pages.dev/tools/whisper/) covers the second dictation engine, including its model catalog, per-language routing, downloads, and the full offline privacy guarantee.
---
# Offline voice (Vosk)
URL: https://wmkeyboard.pages.dev/tools/vosk/
Section: Tools
Summary: Small offline dictation models for 29 languages, about 40 MB each, with words that appear while you speak: Alpha Cephei's Vosk on your phone.
Offline Vosk runs [Vosk](https://alphacephei.com/vosk/android), Alpha Cephei's speech recognizer built on the Kaldi toolkit, on your phone. Its small models are made for phones: most are about 40 MB, load in a second or two and run on older hardware. They decode while you speak, so the words go into the field as you say them. Each language has its own model, 32 in all for 29 languages. Once a model is downloaded, no audio leaves the phone. A language with no model on the phone uses the system recognizer instead.
## Setting it up
Voice typing
1. Add **Vosk** to the **Recognition engine** list and move it to the top, or pick it for one language under **Engine for each language**.
2. Press **Vosk** on the list to open its screen, which holds the **Vosk models** group. The first time, a banner offers the Vosk library itself: a one-time download of about 10 MB (arm64), from Google Play on the Play build and from Maven Central on the GitHub build; the offline build carries it already.
3. Download the model for each language you dictate in. Each one is a zip from alphacephei.com, checked against a SHA-256 built into the app before it is unpacked on the phone. On mobile data it asks first.
| Model | Language | Download | Licence |
|---|---|---|---|
| Vosk English (US) | English | 41 MB | Apache-2.0 |
| Vosk English (UK) | English | 43 MB | Apache-2.0 |
| Vosk English (India) | English | 38 MB | Apache-2.0 |
| Vosk Arabic | Arabic | 104 MB | Apache-2.0 |
| Vosk Arabic (Tunisian) | Tunisian Arabic | 166 MB | Apache-2.0 |
| Vosk Catalan | Catalan | 43 MB | Apache-2.0 |
| Vosk Chinese | Mandarin | 44 MB | Apache-2.0 |
| Vosk Czech | Czech | 46 MB | MIT |
| Vosk Dutch | Dutch | 40 MB | Apache-2.0 |
| Vosk Esperanto | Esperanto | 44 MB | Apache-2.0 |
| Vosk French | French | 42 MB | Apache-2.0 |
| Vosk Georgian | Georgian | 46 MB | Apache-2.0 |
| Vosk German | German | 46 MB | Apache-2.0 |
| Vosk Gujarati | Gujarati | 108 MB | Apache-2.0 |
| Vosk Hindi | Hindi | 44 MB | Apache-2.0 |
| Vosk Italian | Italian | 50 MB | Apache-2.0 |
| Vosk Japanese | Japanese | 50 MB | Apache-2.0 |
| Vosk Kazakh | Kazakh | 60 MB | Apache-2.0 |
| Vosk Korean | Korean | 87 MB | Apache-2.0 |
| Vosk Kyrgyz | Kyrgyz | 51 MB | Apache-2.0 |
| Vosk Persian | Persian | 53 MB | Apache-2.0 |
| Vosk Polish | Polish | 53 MB | Apache-2.0 |
| Vosk Portuguese | Portuguese | 32 MB | Apache-2.0 |
| Vosk Russian | Russian | 46 MB | Apache-2.0 |
| Vosk Spanish | Spanish | 40 MB | Apache-2.0 |
| Vosk Swedish | Swedish | 304 MB | MIT |
| Vosk Tajik | Tajik | 52 MB | Apache-2.0 |
| Vosk Telugu | Telugu | 61 MB | Apache-2.0 |
| Vosk Turkish | Turkish | 37 MB | Apache-2.0 |
| Vosk Ukrainian | Ukrainian | 144 MB | Apache-2.0 |
| Vosk Uzbek | Uzbek | 51 MB | Apache-2.0 |
| Vosk Vietnamese | Vietnamese | 34 MB | Apache-2.0 |
These are the small models from [Alpha Cephei's list](https://alphacephei.com/vosk/models). The large server models (1 to 2 GB each) are left out: they need several gigabytes of memory, which a phone keyboard cannot spare. Unpacked, a model takes a little more room than its download.
## How it works
While you speak, the words go into the field as underlined composing text, rewritten as they grow, and the line under the microphone shows them too. When the phrase ends, the finished text replaces them and goes through the same spacing, spoken punctuation, casing and undo as any other dictation (see [Voice typing](https://wmkeyboard.pages.dev/tools/voice/)).
**Where a phrase ends.** Vosk splits what it hears at its own short pauses, but those only divide the phrase; they never end it. The phrase ends after the pause set in **Stop after a pause**, heard by the [phrase end detector](https://wmkeyboard.pages.dev/tools/voice/#hearing-the-end-of-a-phrase) you picked, the same as for Asr4All. Vosk models carry no Silero, so **Auto** uses loudness until ONNX Runtime and a Silero copy are on the phone. With the setting off, the phrase runs until you press the microphone or two minutes pass.
**Which model.** When two downloaded models know the language, the larger one dictates, except that the American English model is used over the British and Indian ones and the Arabic model over the Tunisian one. The model list marks each one in use, and **Engine for each language** can pin a language to a particular model.
**Punctuation.** Vosk writes lower-case words with no punctuation. With [Add punctuation and capitals](https://wmkeyboard.pages.dev/tools/voice/#adding-punctuation-and-capitals) on, the default, a finished phrase gets full stops, commas, question marks and sentence capitals from the punctuation model for 88 languages, once that is downloaded. The Chinese and Japanese models put a space between words, which those languages do not write, so the spaces between their characters are taken out.
## Vosk, Sherpa or the others
Vosk is the light one. Its models are a fraction of the size of [Whisper](https://wmkeyboard.pages.dev/tools/whisper/)'s or [Parakeet](https://wmkeyboard.pages.dev/tools/parakeet/)'s and need far less memory, so it suits an older or low-memory phone, and it streams words while you speak in many languages the other streaming engines lack: Catalan, Czech, Dutch, Esperanto, Georgian, Gujarati, Hindi, Italian, Kazakh, Kyrgyz, Persian, Polish, Portuguese, Swedish, Tajik, Telugu, Turkish, Ukrainian and Uzbek. Whisper and Parakeet are more accurate when the phone can run them. Where [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) has a model for your language, its Zipformers are usually more accurate than Vosk's small models too. Alpha Cephei's own tests put most small models at 10 to 30% of words wrong; the Telugu model is far weaker, with most words wrong on its test set.
## Details & edge cases
**Words to listen for.** Vosk does not take them. Its small models can be told a fixed list of phrases to choose from, which suits voice commands, not dictation, so the **Words to listen for** group stays hidden while Vosk is the engine.
**Several languages at once.** Each Vosk model knows one language, so a dictation that listens for more than one at a time cannot run on Vosk. The panel asks for a model, or another engine takes it.
**Not in the F-Droid build.** The Vosk library is Alpha Cephei's prebuilt binary, and F-Droid builds everything it ships from source, so the F-Droid build leaves Vosk out.
**Where the library comes from.** The app never carries a rebuilt copy of the Vosk library. The Play build gets it from Play, the offline build carries Alpha Cephei's own file inside the APK, and the GitHub build reads Alpha Cephei's file straight out of their vosk-android package on Maven Central, checked against a SHA-256 built into the app. An 8 KB bridge of the keyboard's own talks to it. See [Network activity](https://wmkeyboard.pages.dev/privacy/network/).
**The licences.** Vosk is Apache-2.0, built on Kaldi and OpenFst (both Apache-2.0). The models are Apache-2.0, apart from the Czech and Swedish ones, repackaged from the Rhasspy project under MIT. All of them are credited in the app's About screen.
---
# Offline voice (Whisper)
URL: https://wmkeyboard.pages.dev/tools/whisper/
Section: Tools
Summary: Dictation that never touches the network: Whisper models running on your device.
Offline Whisper is a second dictation engine that runs entirely on your phone. Instead of streaming audio to a speech-recognition service, it records what you say and transcribes it locally with an OpenAI Whisper model, so nothing ever leaves the device.
For English, German, French, Spanish, Italian, Dutch and Polish there is also [offline Whistle](https://wmkeyboard.pages.dev/tools/whistle/): one 17 MB model that answers faster, where Whisper has a catalog that covers far more languages. For English alone, [offline Asr4All](https://wmkeyboard.pages.dev/tools/asr4all/) writes the words while you are still speaking.
tools/whisper-engine-choice — Recognition engine choice on the Voice typing screen.
## Why offline dictation
The system recognizer, covered in [Voice typing](https://wmkeyboard.pages.dev/tools/voice/), is fast and shows words as you speak. It also leans on your OS and its installed languages, and without the on-device language pack it sends audio to a recognizer service. Offline Whisper trades all of that away for a hard guarantee: it works across many languages and never sends audio anywhere.
You feel that trade every time you dictate, because Whisper doesn't stream. It records into a buffer and transcribes the whole clip once you stop talking, or once you hit the 30-second cap (see [Details & edge cases](#details--edge-cases)). The panel shows "Listening. Press when you are done." rather than live partial words. That press doesn't need aim: the whole panel left of the action rail takes it, so does anywhere on the compact bar, and so does the Voice tool on the toolbar (see [Starting dictation](https://wmkeyboard.pages.dev/tools/voice/#starting-dictation)).
## Two kinds of model
Offline Whisper runs two kinds of model, and the catalog lists both.
- **whisper.cpp models** run on [whisper.cpp](https://github.com/ggml-org/whisper.cpp), which is built into the app. They are the ones pointed at first, and they can do everything below that the other kind can't:
- **Any language, by name.** Each of Whisper's 99 languages, Bangla included, can be told to the model instead of left for it to guess. A dictation in [several languages](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine) is detected among those languages only, so a short phrase is never read as some third one.
- **Your own words.** The same **Words to listen for** list the system recognizer gets goes to the model as a prompt, so names and terms come out spelled your way.
- **Words while it works.** The line under the microphone shows the transcript as it is decoded, instead of only saying "Transcribing".
- **Longer clips.** A clip can run to two minutes instead of 30 seconds.
- **A closer search** on the smaller sizes (beam search), which gets a few more words right, and a transcription you cancel stops at once instead of finishing for nobody.
- Each one is a single `.bin` file, checked against its published SHA-256 once it downloads.
- **LiteRT models** are the community `.tflite` conversions the app started with. Each has the whole decoding loop compiled in, so none of the above can be asked of it, and a few can only be told one of a fixed set of languages. They still work, and a model you already downloaded keeps working. Where whisper.cpp is available their names end in "(LiteRT)".
whisper.cpp is in every build, on 64-bit and 32-bit ARM phones and on x86_64. A phone whose cores all have the ARMv8.2 dot-product instructions (most from 2018 on) gets a build that uses them, which is several times faster at the arithmetic these models are made of. On a phone with a Qualcomm Snapdragon chip (an Adreno GPU), the whisper.cpp models can also run on the GPU; see [Running on the GPU](#running-on-the-gpu).
Bigger models trade download size and speed for accuracy:
- **Tiny**: fastest multilingual model. Fine for clear speech, struggles with noise.
- **Base**: balanced accuracy and speed.
- **Small** *(recommended)*: the best accuracy that still runs comfortably on a phone, noticeably better on accents and background noise than Base.
- **Medium**: near-desktop accuracy. Expect several seconds per phrase and close to a gigabyte of RAM while it runs.
- **Turbo**: large-v3-turbo, so it's about as accurate as Large at half the download, and much better than the small models at working out which language you're speaking. An 820 MB download. It was trained for transcription only, so translation isn't offered for it.
- **Large**: the most accurate graphs there are, and far too big for most phones. A 1.5 GB download that only high-memory devices can load. Two are listed, and if you're going to spend the 1.5 GB, **Large v3** is the one to take: it's the newer model and it recognises languages much more reliably than the original Large.
## The model catalog
On the Play Store build the interpreter that runs the LiteRT models is not in the app you install: it is a small extra part (about 2 MB) that Google Play delivers the first time you need it. The Whisper settings screen offers it at the top until it is here, the keyboard asks for it on its own the first time you dictate with a model downloaded, and until it lands dictation says so instead of recording. Direct-download builds carry it from the start.
The F-Droid build fetches whisper.cpp's library the first time too, but only after you turn on **Settings › Privacy › Feature libraries**, which F-Droid requires apps to ask for. Until then the Whisper screen shows a banner that turns it on, and dictating from the keyboard says the downloads are off rather than that something is downloading. The offline build can't download it at all, so the keyboard points you to the import on the same screen.
The catalog has 10 whisper.cpp models: Tiny, Base, Small, Medium, Turbo and Large v3, which take any language, and English-only Tiny, Base, Small and Medium, which show once English is enabled. Tiny to Small are 8-bit, which loses nothing measurable; Medium and up are 5-bit, which keeps the downloads to 540 MB for Medium, 574 MB for Turbo and 1.1 GB for Large v3. Beside them sit 29 LiteRT models. Ten are multilingual graphs: seven auto-detect the spoken language, and three are grouped models you can pin to one language. The other 19 are single-language models covering 13 languages, several at more than one size: English, German, Spanish, French, Hindi, Italian, Japanese, Portuguese, Russian, Tamil, Telugu, Urdu, and Chinese. A single-language model only appears in the catalog once you've enabled that language on the Languages screen.
tools/whisper-catalog — Model catalog with size and language filters, grouped by tier.
The screen is four sections in a row: the per-language routing list, then **Your voice models**, then a short **Suggested for your languages** shortlist, then a collapsed browse section holding everything else. Browse carries one filter, a chip per size, and it splits what's left into the models that take any language and the ones locked to a single one. Each row wears its size, how many languages it covers, and its download size as small chips. Small is the only model carrying a Recommended badge: the whisper.cpp one where it runs, else the LiteRT one.
Turbo and Large v3 need a 128-band mel spectrogram, where every older Whisper model needs 80. Each one downloads a different filterbank file alongside its graph, and the audio pipeline reads the band count from that file. If the two files ever disagree, dictation fails with a message instead of decoding noise into confident-looking words.
### When a language can only be guessed at
With a whisper.cpp model nothing has to be guessed: it is told the language you dictate in, whichever of Whisper's 99 it is. The rest of this section is about the LiteRT models, and only matters if you use one.
Every LiteRT model has to decide which language you're speaking, unless it was built for one language or belongs to a grouped model that takes the language as an input. The grouped models list 40 and 26 languages, and those lists are compiled into the graph itself, so a language outside them cannot be forced no matter what the app asks for.
For those languages, and Bangla is one of them, a model can decide you're speaking a bigger neighbouring language and hand back fluent sentences in the wrong one. Nothing errors, because as far as the model is concerned it did its job. Two things help:
- The model-per-language list says so on any affected language, and points at Turbo and Large v3, which get the language right far more often than the small models do.
- When the wrong language happens to be written in a different alphabet, WM Keyboard converts it back. Bangla read as Hindi is the common case: the model still transcribed the Bangla words it heard, it just spelled them in Devanagari, and the two alphabets line up closely enough to map across. You get readable Bangla instead of a line you can't read at all. It can't rescue a wrong language, only a wrong alphabet.
Whisper itself has an "initial prompt" that biases spelling and vocabulary, and its own way of forcing a language. The whisper.cpp models use both. The LiteRT models can't: the community `.tflite` conversions were exported with the entire generation loop traced inside the graph. That leaves two inputs: the spectrogram, and on a grouped model a number selecting one of the languages it was built with.
### Picking a model per language

Rather than one global model for every language, WM Keyboard resolves a model per enabled language and falls back in this order:
1. A model you've explicitly pinned to that language, if it's downloaded and covers it.
2. The largest downloaded single-language model for that language.
3. The model that **Model for other languages** names, if it's downloaded and covers the language. See [Options](#options).
4. The best-ranked downloaded model that covers the language at all.
5. As a last resort, that same fallback or the first downloaded model, even if it can't actually transcribe that language.
That last step is deliberate: it produces wrong words rather than a dead microphone. When a language's assigned model can't cover it, the row is flagged in the error color with a note to download one that does.
## Downloading and managing models

Each catalog row carries its own **Download** button on the right. Tapping the row itself does something else. It expands the row to show what the model is and which languages it covers, so you can read up before spending the bandwidth. Once a model is on disk, that button becomes a delete icon. A failed attempt offers **Retry**, and an interrupted one offers **Resume**.
A whisper.cpp model downloads as one `.bin` file from a fixed revision of the whisper.cpp project's repository, and is checked against its published SHA-256 before it counts as downloaded; a damaged file is deleted and the row says so. A LiteRT model downloads as two files (the `.tflite` graph and a companion vocabulary/filterbank file), with the smaller vocabulary file fetched first so a broken connection fails fast. Downloads are resumable: if you lose connection or leave the app, the next attempt picks up where it left off instead of restarting.
Only one model downloads at a time. Before it starts, WM Keyboard checks that you have enough free space: the remaining bytes plus a safety margin. If you don't, the download stops with a message telling you roughly how much to free up. On a metered connection, any model at or above roughly 150 MB asks you to confirm before it uses mobile data.
Downloaded models live in the app's private storage, not shared storage: the screen shows total space used and lets you delete a model to reclaim it. Deleting a model also clears any per-language pin that pointed at it, and clears the app's own fallback if that was the deleted model. If an app update ever drops a model from the catalog, leftover files show up as reclaimable space with a one-tap "Free up" button.
If you downloaded **Tiny (English)** in version 0.5.8 or earlier and dictation with it came back empty most of the time, that was the model file, not your microphone. Its source has since changed to a conversion that works, and the old file is recognised and discarded, so the model shows as not downloaded until you download it again.
The English-only LiteRT models (Tiny, Base, Small and Medium, each marked English) had a subtler fault of their own. Their conversion told the model to start every phrase from the wrong prompt, so a short phrase often came back empty, and more often still with other people talking nearby. The keyboard now corrects that inside the file the first time each one is used, so there's nothing to download again. The first phrase after the fix takes a moment longer while it happens. On recorded test speech it took Tiny (English) from 12 to 9 wrong words in a hundred, and from five empty results in sixteen short phrases to none.
A phone that cannot download can import a model instead. Below the catalog, **Get the files** lists both files of each model that suits your languages, to fetch in a browser on any device, and **Import** installs them: pick the `.tflite` and its `filters_vocab_*.bin` together, or one after the other in either order. A model imported without its vocabulary file is reported as still needing it. See [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/#voice-models).
When exactly one model is on disk, the app quietly adopts it as that fallback, so dictation works right after your first download with no setup step in between. It does the same again if you delete whichever model was holding the job.
## Options
Voice typing
The **Recognition engine** list on the **Voice typing** screen is where you add Whisper; **Add a provider** offers it in any build wherever whisper.cpp or LiteRT can run. Move it to the top to make it the engine for every language. Press its row to open Whisper's own screen, which holds the rest of the table: **Translate to English**, **Compute** and the model catalog.
| Setting | Default | What it does |
|---|---|---|
| Recognition engine | System recognizer | Switches dictation between the system speech recognizer and offline Whisper. On the F-Droid build it offers the whisper.cpp models only, since that build has no LiteRT. |
| Translate to English | Off | Speaks any language and types its English translation instead of a transcription, when the routed model supports it. |
| Model per language | Automatic | Pins a specific downloaded model to a language, overriding automatic routing. One row per enabled language, each with a **Change** button. |
| Model for other languages | Automatic | The fallback step 3 of the routing order uses. Only shown once more than one model is on disk. |
| Compute | Automatic | Where the whisper.cpp models run: **Automatic**, **CPU** or **GPU**. Only shown once a whisper.cpp model is on disk. See [Running on the GPU](#running-on-the-gpu). |
**Model for other languages** sets that fallback by hand. It sits under **Your models**, below the per-language list, and it only appears once you have more than one model on disk, where the choice means something. **Automatic** is the default and keeps the old behaviour: the best downloaded model that covers the language you're typing in. Pick a model instead and every unpinned language uses that one.
Dictation still resolves per language, from the language of the active layout, the same way the system recognizer follows the layout's locale. A [Voice typing language](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine) you picked takes the layout's place. With several picked, the model for the first is used and left to detect which one you speak, so pick a multilingual model that covers them all. The app also fills the fallback in for you when a model is the only one on disk, and clears the choice if you delete that model.
Nine of the 10 multilingual models support translation. Turbo doesn't, because large-v3-turbo was trained for transcription only, and none of the 19 single-language models do either. If a language routes to one of those, the translate toggle has no visible effect for it.
### Running on the GPU
**Compute** decides where the whisper.cpp models run. The LiteRT models always run on the processor (CPU), whatever it says.
- **Automatic** *(default)*: the GPU for Base and larger models on a phone with a Snapdragon chip (an Adreno GPU). Tiny stays on the CPU, which reads a phrase about as fast as the GPU takes to get going. Other GPUs are used only when you pick **GPU**.
- **CPU**: always the processor.
- **GPU**: the GPU for every size, Tiny included.
On Qualcomm's Adreno, the GPU in Snapdragon phones, whisper.cpp runs through the OpenCL driver, and its GPU code comes with the engines. Other GPUs (Mali, Xclipse, PowerVR) run it through Vulkan, from a build of whisper.cpp that is a separate download of about 12 MB (38 MB on the phone), fetched when you pick **GPU**: it carries every shader compiled in. On many of those GPUs it is no faster than the CPU, which is why **Automatic** leaves them out. A phone with neither runs on the CPU whatever you pick; the row's subtitle says so. The GPU is quickest at the part of the work that reads the sound (the encoder), which is most of the wait with Small and the bigger models, and it frees the processor while it runs. The words come out the same, give or take a comma in a rare phrase, since the GPU does its sums at a different precision.
The first dictation on the GPU after an install or an update can take some seconds longer while the GPU prepares itself; the app keeps what it prepared, so later ones start at once. If the GPU won't start, has no room for the model, or stops in the middle of a phrase, that phrase goes to the CPU and comes out as usual, and from then on the CPU runs that model on this phone. The subtitle says when that has happened. Pick **GPU** again to give it another try; an update of the app or of Android does the same.
## Details & edge cases
**A whisper.cpp clip runs to two minutes.** whisper.cpp moves Whisper's 30-second window along a longer clip, so its models record for up to two minutes before a clip ends by itself, with the same countdown over the last five seconds when dictation stops there. A transcription works through it window by window, and the words appear under the microphone as they come.
**A LiteRT clip is a fixed 30-second window.** Whisper always processes a full 30-second spectrogram: a short utterance is padded, and hitting the 30-second mark auto-triggers transcription of whatever was captured. With **Keep listening** off, anything you say after that mark isn't recorded, so for the last five seconds the line under the microphone counts down ("Stops in 3 seconds") instead of saying "Listening", and it turns to "Transcribing…" the moment recording ends. The model's own work is therefore roughly constant per phrase rather than scaling with how long you spoke. The preparation in front of it isn't: the padding is silence, whose spectrogram is known without computing it, so only the part you spoke is analysed. The model also loads while you're still talking instead of after you stop, so the first phrase of a session doesn't wait for it.
**Long dictation keeps every word.** With **Keep listening** on, the microphone never closes between clips. When one ends by itself, at the 30-second mark (two minutes on whisper.cpp), after a pause, or at a key on the panel, the next clip starts recording from the very next moment while the last one is transcribed, so you can keep talking through "Transcribing…" and nothing you say in that gap is lost. Its words land after the last clip's, in order. In a clip's last three seconds a short pause between words ends it early, so the cut falls between two words rather than through one, and there is no countdown because nothing is cut off. The microphone indicator stays on the whole time. Pressing stop, typing on the keyboard or closing the voice surface still closes the microphone at once. Engines that turn speech into text as you talk, [asr4all](https://wmkeyboard.pages.dev/tools/asr4all/), [Sherpa](https://wmkeyboard.pages.dev/tools/sherpa/) and [Omnilingual](https://wmkeyboard.pages.dev/tools/omnilingual/), keep their own clip handling.
**A silent clip types nothing.** Whisper has no way to answer "I heard nothing". Hand it a silent window and it writes the likeliest caption for one, which after training on subtitled video is "Thank you." or "Thanks for watching!". So a clip shorter than 0.3 seconds, or one whose loudest moment is no louder than a quiet room, is never transcribed at all. A clip that is faint but not silent is transcribed, and if all that comes back is one of those stock phrases it isn't typed. Say "thank you" at speaking volume and it's typed like anything else. A transcript that is only a sound caption, such as `[Music]` or `(coughs)`, is never typed, and a word or short phrase the decoder repeats five or more times in a row is folded back to one. The [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) gets the same treatment, which also saves the upload.
**A pause can end the clip.** Set **Stop after a pause** under Voice typing (off by default, 0.5 to 4 seconds) and the clip ends by itself once you have said something and then gone quiet for that long, instead of waiting for a press or the 30-second mark. The quiet before you start talking never counts. With **Keep listening** off that ends dictation too, so the whole exchange is hands-free; with it on, the next clip starts at once on the same microphone, as described below.
**An open microphone winds down.** With **Keep listening** on, a full 30-second clip with nothing in it starts the next one quietly, but only twice in a row; after that dictation goes idle. The [type-while-you-speak modes](https://wmkeyboard.pages.dev/tools/voice/#typing-while-you-dictate) allow twelve, as they do for the system recognizer.
**A microphone that drops out keeps what it had.** If capture fails in the middle of a clip, because another app tore it down or the audio service restarted, what you said up to that point is transcribed as though you had pressed stop. If it fails before anything was recorded, the panel says the microphone couldn't be opened.
**Only three rows here can be reset.** **Translate to English**, **Model for other languages** and **Compute** are ordinary settings, so each grows the usual restore button once you move it off the default. The **Recognition engine** list has a **Reset to default** row of its own once you change it. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). The per-language routing rows and the catalog aren't settings rows at all, so they have no button. You undo a pin by opening that language's dialog and picking **Automatic** again.
**Speed has no benchmarked numbers.** The app carries no measured per-device timing table, for the CPU or the GPU. The only official guidance is the relative sizing above, with Tiny fastest and the two Large graphs slowest and heaviest on RAM, plus a general note that bigger models are more accurate and slower to download and to transcribe.
**Every build, different catalogs.** The whisper.cpp models run in every build, F-Droid's included. The LiteRT models need the LiteRT runtime, which the F-Droid build doesn't carry, so its catalog lists the whisper.cpp models alone. A build made without the whisper.cpp library offers no Whisper engine at all. See [Which build you have](https://wmkeyboard.pages.dev/start/editions/).
**Power saving can silently override your choice.** Its "On-device models" option is on by default. Power saving never turns on by itself unless you set **Turn on by itself**, but once it's on, by hand or by a trigger you chose, dictation falls back to the system recognizer even with Whisper selected and a model downloaded. If dictation seems to have switched engines on its own, check your power-saving settings first.
**No mic opens without a model.** With Whisper selected and nothing downloaded, the panel says so and offers both ways forward rather than opening the microphone. One chip opens this screen so you can download a model. The other switches back to the system recognizer and starts listening right away, and that switch is saved, the same as changing the engine in settings. The compact bar has room for one line, so it points you at settings, where both choices live.
**Panel keys don't end a recording.** Space, backspace and enter on the voice panel's right-hand rail edit the text while the microphone stays open, so you can add a space or fix a character without losing what you've said. A keystroke that lands while a clip is being transcribed behaves the same way. The audio is already captured, so the words still arrive. They just land after whatever you typed. Typing on the keyboard itself during a system-recognizer dictation still ends that utterance, because its live partial text sits in the editor and a keystroke inside it would corrupt it.
**Privacy and downloads are separate concerns.** Transcription itself makes no network calls. The only network activity Whisper causes is fetching a model file over HTTPS from its public repository when you tap download (see [Network policy](https://wmkeyboard.pages.dev/privacy/network/) for the full picture of what WM Keyboard can send and when).
**Attribution.** The whisper.cpp models run on whisper.cpp and ggml (MIT, the ggml authors), and are the whisper.cpp project's quantized conversions of OpenAI's weights. The LiteRT models run on TensorFlow Lite, branded LiteRT (Apache-2.0, Google). The model architecture and weights are OpenAI's Whisper (MIT). The `.tflite` graphs themselves are third-party conversions hosted on Hugging Face, not built by this project. Full credits are in the app's About screen.
Related: [Voice typing](https://wmkeyboard.pages.dev/tools/voice/) covers starting dictation, strip vs. panel mode, continuous mode, and spoken punctuation, all shared behavior between the system recognizer and Whisper.
---
# Offline voice (Whistle)
URL: https://wmkeyboard.pages.dev/tools/whistle/
Section: Tools
Summary: Fast dictation on your phone in seven languages, with one small Cactus Whistle model.
Offline Whistle is a dictation engine that runs entirely on your phone, like [offline Whisper](https://wmkeyboard.pages.dev/tools/whisper/), but with one small model instead of a catalog. [Cactus Whistle](https://huggingface.co/Cactus-Compute/whistle) is a 16.9 MB speech recognition model by Cactus Compute, run by their Needle engine. Once the model is downloaded, no audio leaves the phone.
It was contributed by [@Guillermode20](https://github.com/Guillermode20).
## Whistle or Whisper
| | Whistle | Whisper |
|---|---|---|
| Languages | English, German, French, Spanish, Italian, Dutch, Polish | close to a hundred, depending on the model |
| Download | one model, 16.9 MB | a catalog of 29, from about 40 MB to 1.5 GB |
| Words to listen for | yes, as words to favour | no |
| Translate to English | no | on most multilingual models |
| Devices | 64-bit ARM phones (arm64) only | every phone, in every build but F-Droid's |
Cactus's published figures put Whistle well ahead of Whisper Base on speed and ahead on several English and European test sets, though not all of them. Those are the model makers' own measurements on a desktop processor, not this app's on a phone.
Pick Whistle when you dictate in its languages and want a small download and a quick answer. Pick Whisper when you need any other language, or want to choose a bigger model for accuracy.
## Setting it up
Voice typing / Recognition engine
Add **Whistle** to the **Recognition engine** list and move it to the top. Press its row to open its screen, where the **Whistle model** group has one row: **Download** fetches the model, a cross pauses it, **Resume** carries on from where it stopped, and the bin deletes it. On a metered connection the download asks first, as every model download does (see [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/)).
The option is only offered where the engine can run: a 64-bit ARM phone, in any build but F-Droid's. Cactus publishes no engine for x86 phones, and the one they publish for older 32-bit ARM phones cannot be built into an Android app, so those phones do not see Whistle at all.
Dictate without the model downloaded and the voice panel says there is no model yet, with the same two ways forward Whisper offers: open settings to download it, or switch to the system recognizer.
## Languages
Dictation follows the language of the layout you are typing on, or the language you set under **Voice typing language**. When that is one of Whistle's seven, Whistle hears it. When it is any other language, dictation uses the system recognizer instead, without a prompt, because there is no Whistle model that could help.
Set to listen for [several languages at once](https://wmkeyboard.pages.dev/tools/voice/#language-and-engine), Whistle hears them only when all of them are among its seven, and it works out which one you spoke. Otherwise the system recognizer takes the whole session.
## Words to listen for
Unlike Whisper, Whistle can be told words to favour. The **Words to listen for** group stays on the screen with Whistle selected, and the same list the system recognizer gets, your own words and the ones you type into **More words to listen for**, goes to Whistle with each phrase. See [Words to listen for](https://wmkeyboard.pages.dev/tools/voice/#words-to-listen-for).
## How a phrase is heard
Whistle works the way Whisper does in the keyboard: it records the phrase and transcribes it once you stop, when **Stop after a pause** ends it, or at 30 seconds. A clip with no speech in it is never shown to the model, a microphone that drops out keeps what it had, and the panel's own keys type without ending the recording. All of that is described under [offline Whisper's details](https://wmkeyboard.pages.dev/tools/whisper/#details--edge-cases) and applies here unchanged.
The model loads while you are speaking your first phrase, so that phrase does not wait for it. It then stays loaded until the keyboard's process ends: the engine has no way to unload a model, and at 17 MB it is not worth reloading.
## Without a connection to your server
With the [transcription server](https://wmkeyboard.pages.dev/tools/transcription-server/) as your engine and **Use on-device models offline** on under [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/), a phrase that cannot reach the server is transcribed on the phone instead. Whistle does that job when its model is downloaded and it knows the language; otherwise a downloaded Whisper model does.
## Storage and importing
The model is kept in the keyboard's private storage and listed under **Storage > Voice models** beside any Whisper models, where it can be deleted too.
A phone that cannot download it can import it: fetch [`whistle.cact`](https://huggingface.co/Cactus-Compute/whistle/resolve/b358ddadd89b7a713b5aa131f23032d3cca1b251/whistle.cact) on any device and import it like any other file (see [Using it without internet](https://wmkeyboard.pages.dev/privacy/offline/#voice-models)). The download and the import both check the file's SHA-256 before it is used, so only that exact file is accepted.
## Privacy and credits
Transcription makes no network calls. The one network request Whistle causes is the model download from Hugging Face, made when you press **Download**. See [Network policy](https://wmkeyboard.pages.dev/privacy/network/).
The Needle engine and the Whistle model are Cactus Compute's, under the Apache 2.0 licence. The engine ships in the app as Cactus's own published build, behind a small bridge of the keyboard's own; F-Droid's build carries neither.
---
# WM Pass
URL: https://wmkeyboard.pages.dev/tools/wm-pass/
Section: Tools
Summary: A password book in the keyboard, built on the separate WM Pass password manager: unlock it with your fingerprint or master password without leaving the app, fill a login in two taps, browse and search the vault, type, copy or show a field, add a login, make or change a password, and get WM Pass's own buttons on the strip. The vault stays in that app.
The **WM Pass** tool is a password book in the keyboard, built on [WM Pass](https://github.com/wasi-master/wm-pass), a separate password manager from the same author. The keyboard does not hold a vault of its own. It shows what WM Pass lists, asks for one value when you tap it, types it into the field, and keeps nothing.
## Setting it up
1. Install WM Pass and make a vault there. Until it is installed, the tool and its page in the Tools list have a **Get WM Pass** button that opens its page on Google Play.
2. In WM Pass, open **Settings**, then **Security**, and turn on **Let WM Keyboard fill**. It is off until you do this.
3. In WM Keyboard, add the **WM Pass** tool to your toolbar from the Tools list.
## Unlocking without leaving the app
When the vault is locked, the tool shows a lock and the ways in. Neither takes you out of the app you are typing in.
- **Fingerprint** brings up WM Pass's own fingerprint prompt over that app. WM Pass checks the fingerprint; the keyboard only starts the prompt. Once the vault opens, the keyboard comes back with the tool open on your vault. Close the prompt to go back as you were, or tap **Use password** on it to get WM Pass's unlock screen instead. Without fingerprint unlock set up in WM Pass, the button reads **Unlock in WM Pass** and opens that screen straight away, still over your app.
- **Password** (**Type password** when there is no fingerprint) types your master password into the tool's own box, shown as dots. **Unlock** or Enter sends it to WM Pass, which checks it. The box empties as soon as the password is sent. A wrong one shakes the lock and the box is ready for another go; after too many, the tool shows how long WM Pass makes you wait. A duress password does what it does at WM Pass's own door. A vault that also needs a key file or a security key has to be unlocked in WM Pass.
A vault opened from the keyboard locks after the time set under **Lock after leaving the app** in WM Pass, counted from when it opened, and never sooner than a minute. **Lock** at the top of the tool locks it at once.
## Using it
Open the tool while the field you want is focused. It lists everything in the vault, logins, cards, notes, Wi-Fi networks and every other kind, except what is in WM Pass's trash or on its hidden shelf. Each item wears a coloured letter, the same colour every time, with a small badge for its kind, and a star when it is a favourite. Tap the search box at the top to look for one; it uses WM Pass's own search.
Logins saved for the app in front sit above the list as cards, under **Saved for** and the app's name. A browser does not tell a keyboard which site it is showing, so in a browser there are none: search instead. The chips over the list narrow it to **This app**, **Favourites**, **Logins**, **Codes**, **Cards**, **Notes** or **Other**, each with how many it holds; tap the chip again to go back to **All**. A long list is cut into letters. At the bottom, the vault's name and how many items it has.
Beside a login, the round buttons type its username, its password and its code straight away. Tap the item itself to open it.
## Filling a sign-in form
**Fill**, on the cards above the list and at the top of an open login, fills a sign-in form in one tap. It types the username into the box you are in, moves on to the next box the way the form's Next key or a Tab would, and types the password there once that box is a password box. A form that asks for the password on a second page works too: the password goes in when its box comes up, as long as that is within 15 seconds and in the same app. If the next box is not a password box, nothing more is typed. Tapped while you are already in a password box, **Fill** types only the password.
## An item, opened
An open item lists its fields. A plain one, such as a username, a website or a card's expiry date, shows as it is. A secret, such as a password, a card number or a hidden field you added, shows as dots: the keyboard has only its name.
- **Type**, the keyboard button, puts that one field into the box you are typing in. A secret comes over from WM Pass only on that tap, and goes straight to the field.
- **Show**, the eye beside a secret, shows it for ten seconds, with a ring counting them down, then hides it again. Digits and symbols are coloured so a password is easier to read out or copy by hand. Tap the eye again to hide it sooner.
- **Copy** asks WM Pass to copy it. WM Pass puts it on the clipboard itself, marked sensitive and cleared after the time set in WM Pass, so a copied secret never passes through the keyboard.
- **Open in WM Pass** opens the item there, once the vault is open. It is left off while you are typing in WM Pass itself.
- **Change password**, on an item that has one, opens a box for the new password. **Save** hands it to WM Pass, which keeps the old one in the item's history.
## Making a password
The key button at the top of the list opens WM Pass's generator: a new password, made the way you set the generator up in WM Pass, large, with its strength under it. **Type it** types it into your field, **Another** makes a new one, and **Save as a login** starts a new login with it as the password.
## Adding a login
The plus button at the top of the list, **Add a login**, opens four boxes: name, username, password and website. The keys type into one box at a time; **Next** moves on, and a tap on any box in the list goes back to it. **New password** fills the password box from WM Pass's generator, set up the way you set it there, and **Show** shows what is in it. A bar under the password rates how hard it is to guess, from **Very weak** to **Very strong**, with a rough count of bits. **Save** hands the login to WM Pass.
The name can be left empty: WM Pass names the login after the website, or after the app. A login added while an app is in front is tied to that app by its signing certificate, so it comes first there next time. In a browser it is not, since one browser shows every site; give it the website instead.
What you type into these boxes stays in the keyboard's memory until you save or close the tool. It is not learned, is not offered back as a suggestion, and is not kept for undo.
## Two taps, and never in private fields
Typing anything from WM Pass takes two taps: one to open the tool, one on the field you want. The tool types and copies nothing into a field that asks the keyboard not to learn from it, such as a private browser tab, and says so instead.
## Buttons in WM Pass's own fields
While you type in some of WM Pass's own fields, the strip offers the buttons that sit beside that field there:
- In a card's number, **Scan the card**. In the name on the card and the expiry date as well, when WM Pass's card scanner is set to Google's, since only that one reads them.
- In a Wi-Fi network's name and password, **Fill in from a QR code** and **Pick a network saved on this phone**.
A tap on a chip has WM Pass press that button, so it does exactly what the button does. The field tells the keyboard which buttons it has and nothing about what it holds, and the chips wear the keyboard's own words, so they follow its language. Each WM Pass screen names its fields afresh, so a tap meant for a screen you have left does nothing. The chips work whether or not **Let WM Keyboard fill** is on, since nothing from the vault comes to the keyboard. They give way to word suggestions once those appear.
## The code chip
With one-time code chips turned on, focusing a code box in an app that has a login saved in WM Pass offers its current code on the suggestion strip. It uses the same chip as codes read from notifications, and it disappears when the code stops being valid. Tap it to type the code.
## API keys in settings
The key fields in WM Keyboard's settings get a WM Pass button once WM Pass has a vault and **Let WM Keyboard fill** is on. They include the key and secret of every service under Providers (translation, AI, search, GIFs and the rest), smart home tokens, the Hugging Face and Telegram bot tokens, the GitHub token for imports, and a voice server's key. On a phone without WM Pass the fields look as they always did.
- **Fill from WM Pass** lists the API key items in your vault, with the ones named for that service first. Pick one and its key goes into the field.
- **Save in WM Pass** keeps what the field holds as an API key item named for the service, such as "DeepL", or "DeepL (Work)" for a second DeepL key you gave a name. If an item of that name is already there, WM Pass changes its key and keeps the old one in the item's history. A service that has a key and a secret ends up as one item that holds both.
If the vault is locked, the button opens WM Pass. Unlock it there, come back, and press the button again.
## Places for copies
Both apps can send backups to the same kinds of place: a folder, a server, or a cloud account. A place you set up in one can be picked in the other, so a server's address and password only have to be typed once.
- In WM Keyboard, open [Backup & restore](https://wmkeyboard.pages.dev/reference/settings/backup/#locations), press **Add a location**, then **Use a place from WM Pass**. WM Pass opens, asks you to unlock it, and lists its places. Tap one.
- In WM Pass, the place chooser has **Use a place from WM Keyboard**. WM Keyboard opens its list of locations, behind its app lock if you set one for Backup & restore. Tap one.
The app that receives a place opens it as a new place it hasn't saved yet. Look it over and save it. Each app keeps its own copy from then on: changing or removing the place in one leaves the other alone.
A login the server simply checks comes across whole: a WebDAV, FTP, SFTP, SMB, IMAP or NAS password, an SFTP key, an S3 key pair, a Git token. A sign-in does not. Google Drive, Dropbox, OneDrive and Box are signed in to separately by each app, and the token one app holds would not work in the other. Box and Proton Drive also replace their tokens every time they're used, so two apps sharing one would keep signing each other out. pCloud, MEGA and iCloud Drive keep a session too. For all of those, the receiving app asks you to sign in. A folder on the phone comes without the folder, because Android lets each app into a folder only when you pick it there. A folder inside an account that still has its app's default name, such as "WM Keyboard backups", becomes the other app's default.
Nothing crosses unless you tap a place on the other app's screen. Each app answers only the other one, checked by its signing certificate, and the place goes straight from one app's memory to the other's. It never passes through a file or the clipboard. A nearby computer paired over KDE Connect belongs to WM Keyboard alone and isn't offered.
## What this means for privacy
A password you type this way passes through the keyboard's process on its way to the field, as anything you type does. It goes through the same path as a generated password, so it is not learned, predicted from or added to your typing statistics. So does a password you type into **Add a login** or **Change password** on its way to WM Pass, and the master password you type to unlock: the keyboard hands it to WM Pass and keeps nothing. The keyboard sees every key of your master password when you type it at WM Pass's own door too, so typing it here shows the keyboard nothing more. Nothing is written to disk, and no network is involved.
The fingerprint never reaches the keyboard at all. The prompt is WM Pass's own, and the keyboard can start it but cannot answer it.
A password you copy in WM Pass instead goes through the clipboard, so the keyboard's [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/#passwords-are-handled-deliberately) sees it like any other copy. When WM Pass wipes the clipboard after the time set there, the history drops that clip at the same moment.
Each side checks the other. WM Pass answers only a WM Keyboard signed by a known certificate, and WM Keyboard asks only a WM Pass signed by a known certificate. An app that copied the name gets nothing and is told nothing.
If you would rather keep passwords out of the keyboard's process altogether, leave this off and use WM Pass as your device's autofill service instead.
---
# Theming overview
URL: https://wmkeyboard.pages.dev/themes/overview/
Section: Themes & appearance
Summary: Material You, light/dark/AMOLED, and automatic theme switching.
WM Keyboard's look is driven by a theme: a full set of colors, gradients, key shapes and corner radii bundled together. Pick a built-in theme, let it follow your wallpaper, switch it automatically between day and night, or [build your own](https://wmkeyboard.pages.dev/themes/editor/) from scratch.
A sample of built-in themes. Gradients, translucent keys, pill and cut key shapes, and a hue-cycling animation.
## Choosing a theme
Appearance / Keyboard themes
themes/gallery-grid — The theme gallery: custom themes on top, built-in themes below.
The theme gallery is a two-column grid: the **Default (system)** card first, then your saved custom themes (if you have any), then every built-in theme below them. Tapping a card applies it to the keyboard immediately.
A layout, one of its pages or a panel can keep a [theme of its own](https://wmkeyboard.pages.dev/languages/custom-layouts/#a-theme-of-its-own), and that grid then ignores the card you tap here. When one of the layouts you type with does, a notice above the gallery names each place and the theme it uses, for example "the Numpad tab of QWERTY uses Default (system)". **Clear** takes those themes off, so the whole keyboard follows your pick again.
The toolbox also has an **Appearance** tool that draws live mini-previews right over the keys. It carries a shortlist rather than the whole gallery. Your custom and downloaded themes always show up in it, along with whichever built-ins you pin: each built-in card in the settings gallery carries a pin that adds it to the tool or takes it out, and a fresh install starts with a spread of six. That makes the tool a quick mood switch, with a handful of favourites to flip between mid-typing and the full collection one tap away in Settings (the tool's caption says so too). The tool runs full-bleed. The toolbar row hides while it's open and its space becomes a header, with a back button on the left and the **Themes** / **Icons** / **Emoji** / **Fonts** tabs beside it, so the cards get the reclaimed rows. On the Themes tab, pressing a card while Auto theme is on sets whichever half of the pair, light or dark, is due right now. The grid goes read-only only when that half is set to Random, or when the active keyboard mode carries a theme of its own. A caption at the top names which of those two is holding it.
There are 32 built-in themes. Twenty-two are original WM Keyboard designs: plain colours like Ocean and Forest, gradient themes like Nebula and the animated Sunset drift and Aurora, two shape examples (Bubble for pill keys, Facet for cut corners), the three [frosted glass](https://wmkeyboard.pages.dev/themes/glass/) looks of the Frost family, and Cyberpunk, which puts the whole feature set on screen at once. The other ten (Dracula, Alucard, Nord, Solarized Dark, Solarized Light, Catppuccin Mocha, Catppuccin Latte, Catppuccin Frappé, Catppuccin Macchiato and Tokyo Night) are ports of well-known editor colour schemes. Alucard is Dracula's own light half, spelled backwards, so it draws as a second look on the Dracula card rather than a theme of its own. Cyberpunk is filed beside them in the source because it is written in that style, but the palette is ours. The MIT notices for the ones with upstream licences ship in the app's About screen.
They don't all get a card of their own, though. Twenty-seven of the 32 belong to one of seven families (Nightfall, Pastel, Gradients, Frost, Dracula, Solarized and Catppuccin), and a family shows as a single card with one dot per look, so the grouped built-in grid is twelve cards rather than 32. [Theme families and looks](https://wmkeyboard.pages.dev/themes/families/) covers the dots, the Grouped/Separate choice, and building a family of your own.
None of the built-in themes are editable in place. Tapping the pencil on one makes a copy in Your themes and opens the editor on that copy, before you have changed anything, so the originals stay put as a fallback you can always get back to. See [the theme editor](https://wmkeyboard.pages.dev/themes/editor/) for building or duplicating a theme, [image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) for putting a photo behind your keys, and [sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for exporting, importing, and addon repositories.
## Matching your wallpaper
Turn on **Material You colors** and the **Default (system)** theme pulls its palette from your phone's wallpaper, the same way Android's own system UI does. It's off by default, because the app and the Default theme ship with a palette of their own (the deep indigo and violet the launcher icon is built from), and wallpaper color replaces it. It only takes effect on Android 12 and up. The switch still shows on older versions, where it has nothing to do. This switch only changes the Default theme, and it recolors the settings app as well as the keyboard.
Any other theme can follow the wallpaper too, one theme at a time. Open it in the [theme editor](https://wmkeyboard.pages.dev/themes/editor/#following-your-wallpaper) and turn on **Follow wallpaper colors**. That works for your own themes, copies of the built-in ones, and themes imported from FlorisBoard, Gboard, Rboard or HeliBoard. A FlorisBoard theme written in Material You colors, such as FloriStyle, has it on from the moment you import it.
## Matching the app you type in
Appearance / Keyboard themes / Match the app colors
Turn on **Match the app colors** and the keyboard dresses itself in the colors of whatever app you are typing in: green in WhatsApp, red in YouTube, blue in Telegram. Nothing is written into your theme. Switch to another app and the colors change with it; turn the switch off and your theme is back exactly as it was.
The colors come from two places. One is the colors an app sets for its own screens, such as its toolbar. That is how AnySoftKeyboard does it, and it matches the app most closely when the app sets them. Many newer apps set none, or leave the template's purple in place, so the keyboard also looks at the app's launcher icon, which every app has. **Find colors in** picks between **Auto** (the app's screen colors first, then its icon), **App icon** and **App screens**. Grays, black, white and the stock template colors are skipped, because a palette made from them looks the same in every app.
The app's color is not used as is. It is grown into a full palette (light and dark tones of a primary, a secondary, a tertiary and two neutrals), the same way Android grows one from your wallpaper. **Palette style** decides how, with the same styles Android offers for wallpaper colors:
| Style | Looks like |
|---|---|
| Tonal spot *(default)* | Calm colors. Android's own default |
| Vibrant | The strongest version of the color |
| Expressive | The colors turned well away from the app's, for a playful look |
| Fidelity | As near to the exact app color as a palette can stay |
| Neutral | Nearly gray, with a hint of the color |
| Monochrome | Gray only |
| Rainbow | Colored accents over plain grays |
| Fruit salad | Colored accents turned away from the app's color |
**Themes that change** decides which themes take the app's colors. **Default and wallpaper themes** (the default) covers the Default theme and any theme with **Follow wallpaper colors** on, the themes that already take their colors from outside. Every other theme keeps its own colors. **All themes** recolors every theme. Each color keeps its lightness, so a dark theme stays dark, a light one stays light, and the contrast between keys and labels does not change.
### Another color, or none, for one app
While the setting is on, the keyboard's **Appearance** tool shows a **This app** row above the theme cards. It holds the colors the keyboard found in the app you are in (up to four), then a struck-through circle that keeps your plain theme in this app. Press one and the keyboard remembers it for that app only, so an app whose icon has two brand colors can wear the second one, and an app you would rather keep plain can stay plain. After them come the palette styles, each drawn in this app's color, so you can compare them before you pick. A style applies to every app.
The settings screen counts the apps you changed under **Color for each app**, with a **Reset** button that puts every app back on its first color.
The first time the keyboard opens in an app it reads that app's colors, which takes a moment, and then fades into them. After that it remembers them and opens in them straight away. The keyboard's own settings screens are never recolored, so a theme you are editing always shows as it is.
## Dark mode and AMOLED
**Mode** is a four-way switch: System, Light, Dark, or AMOLED. Like Material You colors, it controls both the settings app and the Default theme. AMOLED always renders dark, and unlike System it doesn't wait on the phone's own light/dark setting. It forces the Default theme's board to pure black, and blacks out the settings app the same way: page, rows, bars, dialogs and sheets, not just the page behind them. Keys and other surfaces stay near-black blended tones rather than literal black, so key edges and the edges of settings rows are still visible against the black behind them. Built-in and custom themes ignore Mode entirely. If you want an AMOLED look on a fixed palette, the built-in **Pitch black** theme gets you there with its own true-black board.
themes/amoled-default-theme — Mode set to AMOLED: a true-black board under the Default theme.
## Switching automatically
themes/auto-theme-sun — Auto theme on, with Sunrise & sunset chosen as the trigger.
**Auto (light and dark)** picks two themes (one for day, one for night) and swaps between them on its own, using one of three triggers:
- **System light or dark** follows the phone's own light/dark setting.
- **Time of day** switches at two clock times you set. It wraps across midnight correctly, so a schedule that starts light in the evening still works.
- **Sunrise and sunset** is computed on the device from the location saved in the weather tool, with no network round trip. Without a saved location, or on a polar day when the sun never rises or sets, this falls back to following the system setting.
While Auto is on, pressing a card in the keyboard's own Appearance tool updates whichever half of the pair is currently due, light or dark. That only holds while the half isn't set to Random; a random half stays read-only there instead. Pressing a card in the settings gallery instead stores a manual pick for later, since Auto ignores it outright while it's on. A caption on each surface says which theme is live. Turn Auto off to pick themes manually again.
### One theme, or a set
Each half of the pair holds either one theme or a whole set to select from. Open the **Light theme** or **Dark theme** row and the picker opens on a two-way control: **One theme**, which is the original behaviour and still the default, or **Random**, which turns the list into checkboxes so you can tick as many as you like. Press a family's name to tick every look in it at once. The two halves are independent, so a fixed light theme and five rotating dark ones is a perfectly ordinary setup.
Switching to Random ticks the theme that half already showed, and the last ticked theme cannot be unticked, so a random half always has something to show. Switching back to One theme keeps the set, ready for when you turn it on again.
A **Change the theme** row appears once either half is random, and it decides how often a new theme comes up: every time the keyboard opens, every hour, every 6 hours, every day, every week, or only when you press Change. The theme never changes while you are typing. A new one is selected as the keyboard comes up for a new field, and the same theme stays for the whole session, so nothing moves under your hands mid-sentence. The next theme is never the one already showing, which is what makes the change worth having on a set of two or three.
themes/random-picker-light — The light theme picker in Random mode, with four themes ticked.
## Options
Appearance / Keyboard themes
- **Match the app colors** (default off). Recolors the keyboard to the app you are typing in. Once on: **Themes that change** (default Default and wallpaper themes), **Find colors in** (default Auto) and **Palette style** (default Tonal spot). See [Matching the app you type in](#matching-the-app-you-type-in).
- **Material You colors** (default off). Wallpaper-based dynamic color for the Default theme and the settings app, in place of the app's own indigo palette. It does nothing below Android 12, and nothing to built-in or custom themes. Those have their own **Follow wallpaper colors** switch in the theme editor.
- **Mode** (default System). System, Light, Dark, or AMOLED, applied to the settings app and the Default theme.
- **Auto theme** (default off). Once on, pick a light theme, a dark theme, and a trigger (System light or dark, Time of day, or Sunrise and sunset). The time-of-day picker starts at 7:00 AM for the light theme and 7:00 PM for the dark theme.
- **Light theme** and **Dark theme** (default One theme, Default (system) in both). Each row opens a picker that holds one theme or a set to select from at random.
- **Change the theme** (default Every time the keyboard opens). Only shows while a half is random. Every time the keyboard opens, every hour, every 6 hours, every day, every week, or only when you press Change.
- **Change the theme now**. Selects a new theme for every random half straight away. This is the only way a new theme comes up under the Change-only setting.
Switching themes crossfades over about a third of a second, by hand or under Auto. Turning on **Reduce motion** (under accessibility) skips the crossfade and snaps instead.
## Details & edge cases
- **Contrast is worked out, not guessed.** A theme only has to specify seven colors: board, key background, key text, modifier key background, enter key background, enter key icon, and accent. Everything else (popup colors, panel chip colors, tool-circle tints, the pressed face, the gesture trail) is derived from those, using WCAG-style contrast math. That's why a custom theme built from one seed colour still has readable popups and legible active-tool highlights. The Default theme's dark mode goes further and steers clear of Material's own dark surface roles, which tend to flatten to near-identical grays. It blends the theme's text color over the darkened board at a few different strengths instead, which keeps panels and keys distinct.
- **That's separate from the accessibility contrast tools.** High contrast keys, key outlines, and color vision filters are a user-facing layer that overrides theme colors outright rather than filling in gaps. They live under Settings → Accessibility, not here. See [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) for those.
- **Key shape** is a per-theme setting with thirteen options, picked from a list that draws each shape beside its name: None (no shape at all, so only the labels show), Rounded (the default, and the one that follows the corner-radius value), Square, Pill, Cut (clamped to a 2–14dp radius regardless of the slider), Squircle, Arch, Leaf, Slanted, Hexagon, Scalloped, Ticket, and Circle (a typewriter-style round cap centred in the key). Apart from None, everything except Rounded and Cut sizes itself off the key instead of the radius slider. Full table on [the theme editor page](https://wmkeyboard.pages.dev/themes/editor/#key-shapes).
- **Corner radius has a global slider and an optional per-theme override.** The global **Key corner radius** slider (Appearance → Keys, default 8dp, range 0–28dp) applies unless a theme defines its own. The theme editor's "Corner radii for this theme" toggle exposes separate sliders for key, popup, and tool-circle radius when you want a theme to ignore the global value.
- **Borders are opt-in and off by default.** A theme has no key border unless it sets one explicitly. The built-in Solarized Dark and Solarized Light themes show when a border earns its keep: their two background tones sit close enough in contrast that a thin outline keeps key edges readable. The toolbar tools have their own outline pair in the theme editor, off in the same way. The accessibility "Key outlines" toggle adds a border of its own to the keys and the tools, but only where the active theme hasn't already defined one. A theme's own border always wins.
- **No build differences.** Nothing on this page is gated: every built-in theme, gradient, animation, background image, and Material You itself works the same way in every build.
---
# Image backgrounds
URL: https://wmkeyboard.pages.dev/themes/backgrounds/
Section: Themes & appearance
Summary: Put a photo behind your keys, with crop, opacity, blur and a separate landscape image.
Any keyboard theme can carry a photo behind its keys instead of a flat color or gradient. You crop it to the keyboard's shape, dial in opacity and blur so keys stay readable, and optionally swap in a different image for landscape.
This page covers a picture from your own device. A photo from Unsplash or Pexels, the collection they go into, and the background that changes on a schedule are all in [Online photos and rotation](https://wmkeyboard.pages.dev/themes/online-photos/).

## Setting an image
Appearance / Keyboard themes / (edit a theme) / Board

Open a theme for editing. Scroll to the **Board** section. Tap **Background image** to open the **Select a background** dialog. **Add from this device** opens the system photo picker, which offers image files only. The app copies the picture you choose into its own storage, and it never reads the original file again.
Picking an image changes two things about the theme so the photo is actually visible, both of which stay editable afterward:
- **The board color's opacity drops to zero**, so the photo shows at full strength rather than being hidden under an opaque background. Raise it again if you want to tint or dim the photo.
- **Fully opaque key and modifier colors drop to 62% opacity**, or to whatever [How solid the keys stay](https://wmkeyboard.pages.dev/themes/online-photos/#how-a-photo-changes-the-keyboard) is set to, anywhere from 20% to 100%. A keyboard is mostly keys, so leaving them solid means the photo is only visible around the edges. Keys you had already made see-through are left alone.
See [Editing a theme](https://wmkeyboard.pages.dev/themes/editor/) for both color controls.
The photo also joins your [photo collection](https://wmkeyboard.pages.dev/themes/online-photos/#the-photo-collection), so you can put it on another theme, or into the rotation, without finding it in your gallery again.
### Cropping to fit
Once an image is set, a **Crop image** row appears. In the crop dialog, pinch to zoom and drag to reposition. The image always scales to cover the frame, so there's never a gap at the edges. Four crop ratios are available:
- **Keyboard**: the shape your keyboard actually has on screen, in portrait for the portrait image and in landscape for the landscape one, so the frame shows exactly what ends up behind the keys. The keyboard measures itself each time it opens, so this follows your key height, number row, toolbar and width settings. Selected by default. Until the keyboard has been opened once in that orientation, it uses 2.4:1.
- **Wide** (1.7:1)
- **Square** (1:1)
- **Free**: keeps the picture's own shape. The other three are all wider-than-tall keyboard shapes, which crop a split or floating keyboard down.
Confirm the crop and the app saves the cropped picture, and keeps the uncropped one beside it. Opening **Crop image** again starts from the whole picture, with the frame where you left it, so you can move to a part an earlier crop cut away, or zoom back out. Cropping is optional, and an uncropped image still renders correctly, scaled to cover the board. Crop when you want to control which part of the photo lines up with the keys.
The photo grid in [Online photos](https://wmkeyboard.pages.dev/themes/online-photos/) dims the parts of each photo outside the keyboard's shape, using the same measured portrait shape.
## Keeping keys readable
A photo behind your keys can easily fight with legibility, so two sliders and a switch sit right below the image row:
- **Image opacity** fades the image itself, from fully transparent to fully opaque (the default). Below 100%, with the board color still see-through, whatever is behind the keyboard shows through the photo. An image with transparent areas of its own does the same.
- **Image blur** softens the image from sharp (the default) up to a heavy blur.
- **Play animated image** plays the background when it's a GIF or an animated WebP, and turns itself on when the picture you picked is one. Turning it on zeroes the blur, and raising the blur turns it off: blurring a running animation would re-render the effect on every frame. The animation also stays still under reduce motion or power saving, and on app versions from before this feature the same file shows its first frame.
There's no separate "dim" control. The board's own background color draws as a layer on top of the image, so raising that color's opacity tints or dims the photo. It's the same **Background** color row you'd use for a solid-color theme. A soft blur plus a translucent board color is usually enough to keep key labels legible over a busy photo.
Turning on **High contrast keys** in accessibility settings strips background images (and gradients and animation) from every theme while it's active, since a photo behind the keys works against the contrast that setting is trying to guarantee.
## Separate landscape image
Below the portrait image controls, **Landscape background image** lets you pick a second photo (with its own crop dialog) used only when the device is in landscape orientation. If you don't set one, the portrait image is used in landscape too. There's no picture that only shows in portrait while landscape falls back to something else.
Opacity and blur are shared between both images rather than set per orientation, so whichever photo is showing uses the same two slider values.

## Image sizes and templates
Appearance / Keyboard themes / (edit a theme) / Board / Image sizes and templates
If you draw a theme yourself, this screen tells you how big each picture should be before you start. It lists the background, the letter, function and Enter keys, the space bar, the pressed key, the key popup, stickers and press effect images, each with two sizes:
- **On screen** is what the picture covers on the keyboard.
- **Draw at** is the same shape, made as large as the keyboard keeps. A key image is kept at up to 256 pixels on its long side and a space bar image at up to 1024 × 320, so a larger drawing only gets made smaller again.
The sizes are measured from the keyboard itself, so they are right for your layout, number row, key height and spacing. **Screen** picks what they are measured for:
- **This phone** (the default): your own layout and sizes on this screen.
- **Phone**: a 1080 pixel wide phone with the default sizes. Use this for a theme you want to share, since other people see the defaults.
- **Phone, landscape**: the same phone on its side, for the [landscape image](#separate-landscape-image).
- **Tablet**: a 1600 pixel wide tablet or an open foldable, with the default sizes.
The picture under the selector is the board template: every key's outline over the background area, the toolbar band at the top, and the band under the navigation bar at the bottom. The background runs on under the navigation bar on Android 15 and later only, so keep that band plain.
**Save board image** saves that template as a PNG. **Save all templates** saves a ZIP with the board template, one template each for the letter key, function key, Enter key, space bar and popup (with a dashed box where the label sits), and a `README.txt` with every size. The templates are transparent with white lines. Put one on the top layer of your drawing, and hide it before you export.
## Details and edge cases
- **No image by default.** A freshly created theme, every built-in gallery theme, and the Default (system) theme all start with no background image, so the board is a flat color or gradient until you add one.
- **Switching themes crossfades the rest of the theme, but not the image.** Colors and gradients blend smoothly between themes. The background image switches over abruptly partway through the transition rather than cross-dissolving, since blending two unrelated photos frame-by-frame wouldn't look right anyway. Blur switches at the same moment for the same reason.
- **Changing the seed color keeps your photo visible.** Picking a seed swatch, or flipping the light and dark switch, rebuilds every color in the theme. When the theme has a background image, how see-through the board and keys are is carried across, so the new palette does not bury the photo under a fresh opaque color.
- **Images are decoded at the size they are drawn.** A photo straight off a camera is far larger than the strip behind your keys, so the app samples it down first rather than holding it at full size.
- **The gallery preview card only shows the portrait image.** The small theme thumbnail in the theme list never shows the landscape variant, even if one is set.
- **Background images travel with exports.** Both a single-theme export and a full settings backup embed the image data directly in the file, so restoring the export or importing the theme on another device brings the photo with it; you don't need to re-pick the image after importing. See [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for what else a theme export contains.
- **This page is about a single theme's own image.** Switching between two different themes automatically by time of day or by system light/dark mode (where each half of the pair might have its own background image) is covered in [Themes overview](https://wmkeyboard.pages.dev/themes/overview/). A single theme whose *photo* changes on a schedule is [Online photos and rotation](https://wmkeyboard.pages.dev/themes/online-photos/).
---
# Theme editor
URL: https://wmkeyboard.pages.dev/themes/editor/
Section: Themes & appearance
Summary: Every color, key shape, gradient, corner and animation knob for a single custom theme.
The theme editor is where a single custom theme gets built: every color, the key shape, optional gradients, corner-radius overrides, and a live-animated background if you want one. Every field here applies and saves the instant you touch it. There's no separate save step.

## Creating a theme
Appearance / Keyboard themes
A custom theme's card opens the editor from its pencil, and the card's miniature grows into the editor's own preview on the way. The editor is headed with the theme's name rather than with the words "Edit theme", so the card you tapped and the page it opened read as one thing. Rename the theme and the heading follows.
Every change saves as you make it. Once you have changed something, the undo arrow under the preview lights up, and after an undo the redo arrow beside it does too. They cover everything on the page, the theme's looks included: adding or deleting a variant is a step too. Changes made within about a second of each other undo together, so a slider's drag or a name typed out is one step rather than dozens. The history holds 30 steps and lasts until you leave the editor. A photo is the one thing an undo can't always bring back, because picking a new background deletes the old picture's file.
**Create theme** on the gallery screen makes a new custom theme called "My theme". It's seeded from a blue swatch and generated light or dark to match whatever the phone is set to right now. The new theme becomes the active keyboard theme, then the editor opens on it.
Built-in themes can't be edited in place. Their card shows a pencil instead, which copies the built-in into a new custom theme named "*(theme) copy*", makes the copy active, and opens the editor on the copy. The original built-in is untouched. The **Default (system)** card has no pencil at all. It's generated live from your device's Material You color scheme rather than stored anywhere, so there's nothing for this editor to open. See [Theming overview](https://wmkeyboard.pages.dev/themes/overview/) for the gallery itself and how Default, Material You, and the built-in set fit together.
Importing a `.wmtheme.json` file (from the gallery's Import button or by opening the file itself) adds it to Your themes and switches the keyboard to it immediately, without opening this editor. See [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for both ways to import.
## The Looks row
Straight under the preview, before the name field, sits a **Looks** row: one chip per look this theme carries, an **Add look** chip, and (while you have a look other than the first one open) a bin icon that deletes the one you're editing. Everything below that row edits the open look and nothing else, so a theme with four looks is four full colour sets, switched between with a chip.
A theme starts as one look, and the row is just the one chip until you press Add look. Add a second and a **Group name** field appears below the chips: that's the label the gallery card shows over the dots. Add look copies the look you have open, so the new one starts where the old one left off, and it greys out after twelve extras (thirteen looks in all).
[Theme families and looks](https://wmkeyboard.pages.dev/themes/families/) is the full story: what a family card looks like in the gallery, the Grouped/Separate choice, and what happens to a family when you copy, export or delete it.
## Starting from a color
At the top of the editor, a **Dark theme** switch and a row of swatches let you regenerate every color at once from a single seed:

Tapping a swatch, or flipping Dark theme, recomputes the board, key, accent, and popup colors from that seed and shade. It preserves everything that isn't seed-derived, so a reseed doesn't erase work you've done elsewhere in the editor. Your background image and its opacity/blur, both gradients, the key shape, the key border color and width, the gesture-trail color, all three corner-radius overrides, and the animation type and speed all carry over untouched. Anything you hand-picked that *is* seed-derived (a manually recolored key-text or popup color, say) gets overwritten, since there's no way to tell "kept the default" apart from "picked to match."
## Following your wallpaper
Under the seed swatches, **Follow wallpaper colors** makes the theme change color with your wallpaper, the way the Default theme does with Material You on. It's off for a theme you make, and it needs Android 12 or later, because older phones have no wallpaper palette to read.
The theme keeps its own colors. The keyboard moves them onto your wallpaper's palette as it draws them, so turning the switch off gives you the stored colors back, and the color rows in the editor always show the stored ones. How the colors move depends on where the theme came from:
- **Most themes** (your own, copies of built-ins, Gboard, Rboard and HeliBoard imports) keep how light or dark each color is and take the wallpaper's hue. Greys go to the wallpaper's neutral tones, colors go to its main accent, and a color on the far side of the color wheel from the theme's accent goes to the wallpaper's third accent, so a two-color theme stays two colors. Because the lightness stays put, text that was easy to read stays easy to read. Pure black and pure white stay as they are.
- **FlorisBoard themes written in Material You colors**, such as FloriStyle and Gboardish, remember which Material You color each part used. They follow the wallpaper exactly as they would in FlorisBoard, and the switch is already on when you import one. A color you change in the editor stays the color you picked.
Tapping a seed swatch or flipping **Dark theme** keeps the switch as it is.
## Options
Appearance / Keyboard themes / (edit a theme)
The rest of the editor is grouped into sections, each opening the color picker when you tap a row.
### The color picker
Every color row in the app opens the same dialog, here and over in the tool screens:
- A **wheel** for hue and saturation. Hue runs around it, saturation runs outward from the middle, and the ring follows your finger from the moment you touch it.
- A **Brightness** bar, plus an **Opacity** bar on the fields that support translucency. Each bar is painted in the colors it's about to hand you, and the opacity one sits on a checkerboard so you can see how much is showing through.
- A **preview strip** across the top, split down the middle: the color you opened with on the left, the color you have now on the right. Tap the left half to throw away the change and start again.
- A **Hex** field taking three, six or eight digits, with or without the `#`. Six digits keeps whatever opacity you already set rather than resetting it to solid.
- A row of **presets**, the same seeds as the swatch row above. Tapping one changes the color and leaves your opacity alone, so a scrim you've already made translucent stays that way.
The button in the dialog's title swaps the wheel for **Hue** and **Saturation** bars, for when you'd rather read exact numbers or move one channel at a time. Those bars are also the ones TalkBack can set. In landscape the wheel sits beside the bars instead of above them.
From Android 12 the dialog has two tabs. **Custom** is everything above. **Palette** offers your wallpaper's Material You colors: five swatches for its Primary, Secondary, Tertiary, Neutral and Neutral variant palettes, and a **Tone** bar from 0 (black) to 100 (white) that sets how light all five are. A theme that follows the wallpaper opens on this tab. The shade you pick is saved as a plain color and keeps your opacity.
### What's what
Every row in the editor paints one part of the keyboard, and the names are only obvious once you know which part is which. The two drawings below put a number on each part. The tables under them say which row colours it.

| | Part | Rows that paint it |
|---|---|---|
| 1 | **Board**: everything behind the keys, including the gaps between them | Board › Background, Gradient background, Background image |
| 2 | **Toolbar row**: the tools, each on its own circle | Toolbar, strip and popups › Toolbar: Toolbar background, Tool circles, Tool icons, Active tool circle, Active tool icon, Tool outline; shape and size under Tool background shape and Corners |
| 3 | **Suggestion strip**: the word candidates. The middle one is the primary, the others the runner-ups | Toolbar, strip and popups › Suggestion strip: Suggestion strip (the fill), Suggestion text (the primary), Other suggestions and Other suggestions size (the rest), Suggestion pills and Main suggestion pill (the boxes behind the words, with Coloured suggestions on); Panels & suggestions › Dividers (the hairlines between them) |
| 4 | **Letter key** and its label | Keys › Letter keys, Key text; the sheen under Key gradient; the picture under Key textures › Letter keys |
| 5 | **Key hint**: the small alternate in the corner | Keys › Key hint text; its size under Layout › Key hint font size |
| 6 | **Pressed key**: the flash while a key is held | Keys › Pressed key; the picture under Key textures › Pressed keys |
| 7 | **Key border** | Keys › Key border and Border width |
| 8 | **Modifier key**: shift, delete, `?123`, the globe | Keys › Modifier keys, Modifier key text |
| 9 | **Enter key** | Keys › Enter key, Enter key icon |
| 10 | **Space bar** | Takes the letter-key colour and texture; give it its own under Single-key styles › Space |
| 11 | **Navigation bar**: the band the phone's gesture bar sits on | Board › Navigation bar |
| 12 | **Key shape and corners** | Keys › Key shape; Corners › Key corner radius; the lift under Key shadow |

| | Part | Rows that paint it |
|---|---|---|
| 1 | **Popup fill**: the bubble that rises out of a pressed key, and the sheet the press-and-hold alternates sit on | Popups › Popup background; the picture under Key textures › Key preview popup |
| 2 | **Popup text**: the character in the bubble, and every alternate that isn't under your finger | Popups › Popup text |
| 3 | **Popup outline** | Popups › Popup outline and its width row |
| 4 | **Popup shape and corners** | Popups › Popup shape; Corners › Popup corner radius |
| 5 | **Popup height**: how tall the bubble is | Layout › Popup height; where it sits is Popup placement |
| 6 | **Selected highlight**: the alternate your finger is on, and the selected row of a menu | Panels & suggestions › Selected highlight |
| 7 | **Selected text**: the character or row on that highlight | Panels & suggestions › Selected text |
| 8 | **Spacing between alternates**: the room around each character, which is also the gap between the highlight and the popup's edge | Not a theme field. It's Key press / Alternate keys / Alternate key spacing, and the characters' size is **Alternate key size** beside it |
| 9 | **Popup shadow** | Panels & suggestions › Popup shadow |
| 10 | **Menu shape**: the list menus' own outline | Popups › Menu shape |
The popup rows do nothing to a bubble you have coloured by hand under Key press / Key popup: **Popup color** and **Popup text color** there win over the theme, and their reset controls hand the bubble back to it. A [single-key style](#single-key-styles) wins over both, for its one key.
### Board
- **Background** is the board's base color. It supports alpha, which starts to matter once a background image is set. See [Image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) for the image picker, crop, opacity, and blur controls that live in this same section.
- **Gradient background** is covered under [Gradients](#gradients) below.
- **Navigation bar** fills the band below the bottom key row, where the phone's own gesture bar or navigation buttons sit. Auto leaves it on the board, the same as the [suggestion strip](#toolbar-strip-and-popups). Only the system's own band is painted. The extra space that **Bottom padding** (under Layout & size / Size and position) adds above it belongs to the keyboard and stays on the board. On a phone with hardware navigation keys there is no band at all, so the color has nothing to paint and nothing changes.
On Android 15 and later the keyboard draws that band itself, so a gradient, image or animation carries through it. On Android 14 and earlier the keyboard window stops short of the bar and can only hand the system one flat color, which it does: the **Navigation bar** color if you set one, otherwise the far end of the gradient, otherwise the board color. Either way the bar follows the theme instead of staying whatever color the phone picked, and the system's hide-keyboard and language-switch glyphs flip between dark and light to stay readable on it.
### Glass
Appearance / Keyboard themes / (edit a theme) / Glass
**Frosted glass** turns the theme's surfaces into see-through panes over a blurred backdrop, with a rim of light and a little grain. The section opens with a **Presets** row (Frosted, Clear, Mica, Smoked, Liquid), **Kind of glass** (Frosted, or Liquid with its **Refraction** and **Color split** sliders), then **Blur behind the keyboard** (Android 12 and later; the app under the keyboard shows through the board blurred), **Blur strength**, **Board opacity without blur**, a row of **Glass surfaces** chips, **Behind the glass** (Auto, Image, Wallpaper or Nothing) with **Part of the wallpaper** and, on Android 12 and below, a **Use your real wallpaper** permission row, the three backdrop sliders (**Backdrop blur**, **Backdrop saturation**, **Backdrop brightness**), **Rim**, **Rim color** and **Grain**. [Frosted glass](https://wmkeyboard.pages.dev/themes/glass/) explains what each does, what the phone has to support, and how the built-in Frost family uses them.
### Keys
- **Key shape**: a row that opens a picker with **None** and all twelve shapes listed by name, each drawn beside its name at key proportions and at this theme's own corner radius, so you can see what you're choosing before you choose it. The full list, and which shapes react to the radius slider, is under [Key shapes](#key-shapes) below.
- **Letter keys** sets their color, and carries an optional **Key gradient**, a subtle sheen painted over letter keys only (see [Gradients](#gradients)).
- **Key text**, **Modifier keys**, **Modifier key text** (falls back to Key text when left on Auto), **Key hint text** (the small long-press alternate in a key's corner; Auto draws the key's own text color at 55% opacity, and this one keeps alpha so you can pick a translucent hint in another hue), **Enter key**, **Enter key icon**.
- **Make every key match the letter keys** gives the modifier keys (Shift, delete, `?123`, the globe, the emoji and tool keys) and Enter the letter keys' background and text colours in one step, for a board where every key looks the same. It is one undo step, and each colour stays editable afterwards.
- **Pressed key** is the color a key flashes while held. Auto derives it from Letter keys.
- **Key border** stays off (Auto, no visible border) until you pick a color. A **Border width** slider (0–3dp) then appears, starting at 0dp. Picking a border color on its own shows nothing until you raise the width too.
#### See-through keys
Keys don't need a separate opacity slider: the colour itself carries it. **Letter keys**, **Modifier keys** and **Enter key** all open the picker with the **Opacity** bar, and the hex field takes eight digits, so `#80303338` is the stock key at half strength. Pull the opacity down and the board shows through the key, gradient, photo or animation included, and the gap between keys stops being a gap. Setting a background image does the first step for you: it drops opaque key and modifier colours to about 62%. A key you can see through casts no [shadow](#key-shadow), whatever Key shadow says.
That is a translucent face. For no face at all, pick **None** under [Key shape](#no-key-shape): the labels then sit straight on the board, and only a pressed or latched key draws anything behind its label.
### Key shapes
The picker lists **None** first, then the twelve shapes in this order. The five simple ones come first, then the seven decorative ones:
| Shape | What it looks like | Reads the radius slider? |
|---|---|---|
| None | No shape at all, just the labels on the board. See [No key shape](#no-key-shape) below | Only for the flash while a key is pressed |
| Rounded *(default)* | The ordinary rounded rectangle | Yes, 0–28dp |
| Square | Four sharp corners, no rounding at all | No |
| Pill | A full stadium, rounded to half the key's height | No |
| Cut | Corners chamfered off at 45° | Yes, but clamped to 2–14dp |
| Squircle | A superellipse, the same curve family as an iOS app icon | No |
| Arch | Round across the top, nearly square at the bottom | No |
| Leaf | Top-left and bottom-right fully round, the other two nearly square | No |
| Slanted | A parallelogram, both vertical sides leaning right | No |
| Hexagon | Points at the left and right, flat top and bottom | No |
| Scalloped | A rim of even bumps, like the edge of a biscuit | No |
| Ticket | Corners bitten inwards by a circle, like a cinema ticket | No |
| Circle | A round cap centred in the key, like a typewriter's; wide keys (space, enter) keep a stadium | No |
The shapes that ignore the slider aren't being lazy about it. Their whole look is a proportion of the key rather than a corner size, so they measure themselves off the key's own width and height. The arch is always half the key's height. The hexagon's points are always a sixth of its width. The scallops count out at roughly the same size whether it's a letter key or a spacebar, up to 64 along a side; a box only a few pixels tall, such as a panel sliding open, is drawn as a plain rectangle until it is big enough for bumps. Wire them to a 0–28dp slider instead and one end of it flattens them into plain rectangles.
Only the drawing changes. Touch targets stay rectangular for every shape, so the gap between two hexagons still belongs to one key or the other, and nothing gets harder to hit.
Slanted is the one shape that draws slightly outside its key. A parallelogram kept inside the key would lose about a third of the width at the top and the bottom, which on a phone-sized key reads as a row of thin straps. So it leans half its slant into the gap on each side instead. Every key stays exactly as wide as it would be with any other shape, and the gap between two keys keeps its width. The gap just runs on a diagonal. Where there is no gap to lean into, the lean is trimmed to fit.
#### Key shadow
**Key shadow** lifts every key off the board, 0 to 16 dp. It's drawn by the platform from the key's own outline, so it follows whatever shape you picked.
**Key shadow colour** shows up once there is a shadow. Unset, it's the usual black; pick any colour, and lower its opacity for a soft glow rather than a hard edge. Themes imported from Gboard and FlorisBoard bring their own. Android 8 and older draw every shadow black whatever this says.
Two shapes stay flat however far you push it. Scallop and Ticket are concave, and Android tessellates a concave shadow through a path that pins the render thread hard enough for the system to kill the keyboard, so they keep the shape and lose the shadow. A key you can see through at all stays flat too, even at 90% opacity: the shadow fills the key's whole outline, and through a translucent face it reads as a lighter box inset in every key. That's what keeps a borderless theme looking borderless.
#### No key shape
**None** takes the shape away completely, which isn't quite the same as a see-through key color. A key at rest draws no fill, no border and no texture, so every label sits straight on the board, Enter and the space bar included. The Enter icon switches to your **Modifier key text** color while it has no fill of its own, since **Enter key icon** is picked to read on the Enter key's fill.
The keys still answer your finger. A pressed key flashes a rounded shape at your key corner radius, and an armed or locked modifier still lights up. A key you give its own face color under [Single-key styles](#single-key-styles) keeps that face, so a colored Enter key on an otherwise bare board is one style away. The keypads in the tool panels, like the calculator and the voice and handwriting rails, follow the same rule as the main keys.
**High contrast keys** and **Key outlines** in [Accessibility](https://wmkeyboard.pages.dev/accessibility/overview/) both bring rounded keys back while they're on. Keys without visible edges are exactly what those two settings are there to fix.
Only Rounded, Pill, Cut and Squircle shipped with the first version of the format. The other eight, Square among them, came later, and so did None. A theme exported with one of them won't import into a version of the app that predates it: the importer drops a theme whose shape name it doesn't recognise rather than guessing. Stick to Rounded, Pill, Cut, or Squircle in a theme you plan to share widely.
### Key textures
Keys can wear an image instead of a flat color. One picture for the letter keys is enough to theme the whole board. The modifier keys, the enter key, the space bar, the pressed state and the key-preview popup each take an optional image of their own, and each falls back sensibly when you don't set one: space reuses the letter texture, pressed draws the press tint over it, and the popup draws color-only rather than borrowing a key's image.
- Each texture row picks an image from your gallery. It's stored downscaled to 512px, so a camera photo won't bloat the theme file. Rows that have an image show it as a small thumbnail, the same way the background and press-effect image rows do.
- **Crop / Stretch / Tile** decides how the image fits a key. Crop keeps its proportions, stretch fills the key exactly, and tile repeats it at its natural size. Tile is the one you want for pixel-art.
- **Texture opacity** blends the image over the key color underneath. That color stays the base coat, and it's what high-contrast accessibility falls back to, since textures don't draw there at all.
Textures travel inside the theme file when you export it. An older app version imports the same file as a plain color theme and loses nothing else.
To find out how big to draw each texture, open **Image sizes and templates** in the **Board** section. It measures your keyboard and saves templates to draw on. See [Image sizes and templates](https://wmkeyboard.pages.dev/themes/backgrounds/#image-sizes-and-templates).
### Single-key styles
One key can carry a style of its own. The quickest way to choose it is **Pick on keyboard**: the preview stops typing, and pressing any key on it opens that key's style. A row of chips above the preview switches it between the letters, the two symbol pages, the Fn page when your layout has one, and every panel that is a grid of keys (text editing, numpad, clipboard, emoji, GIF, sticker, trackpad and handwriting), so a key on any of them can be picked the same way. **Done** or Back returns the preview to typing. **Add a key** still takes a letter typed by hand, or one of the special keys (Enter, Space, Shift, Delete, Symbols…), for a key the preview isn't showing.
A letter's style follows that letter onto every layout that has it. A key that does one particular thing gets a style for that thing alone: each arrow, Home and End on the text-editing pad, each tool key, each page key. That is how the arrows of the text-editing pad go red while Home and End stay dark. A style an older theme or an imported one gave the whole family (every text-editing key at once, say) keeps applying to every key that has no style of its own.
The key's style opens on the page straight under the preview, which stays pinned above it, so every change shows on the key as you make it. Back or the × beside its title closes it. Set any of these. Everything you leave alone keeps following the theme, so a key that only recolors its hint carries exactly that.
- **Colors**: its face, its label, its hint, its border, and the two colors of the popup bubble it shows.
- **Key image**: a picture for this one key, drawn with the same Crop/Stretch/Tile fit and opacity the board's [key textures](#key-textures) use, so a dressed key still reads as part of the set. It beats the class texture it would otherwise wear, and a key image works on a board with no class textures at all.
- **Press effect**: the burst this key throws: Stars, Hearts, Sparkle, Confetti, or Emoji with its own field for the emoji. It overrides the board's [key press effect](#key-press-effect) for this key alone, and a board whose only effect is one key's still bursts on that key. The board's Intensity, Colour and the physics sliders shape every burst; a single-key style picks what is thrown, not how. Images aren't offered per key.
- **Key shape**: this one key's outline. Automatic follows the board's shape; pick one to make a single key round, pill, cut or anything else in the list, including None. The board's corner radius still decides how round a rounded key is, and the shapes that measure themselves off the key (circle, pill, hexagon and the rest) ignore it as they always do. This is how a round Enter key on a grid of soft rectangles is made, and it is what carries over from an imported FlorisBoard theme built that way.
- **Bold label** and **Label size**: how the label is drawn. Bold is a three-way choice: Automatic follows the board, On and Off answer for this key. Label size replaces the automatic rule rather than scaling it, which is how you get a `?123` drawn at full letter size. A size the *layout* gave the key in the [layout editor](https://wmkeyboard.pages.dev/languages/custom-layouts/#editing-a-key) wins over the theme's, since the layout is the more specific answer.
An armed or locked modifier still shows the accent or pressed face whatever color you gave it, because state has to stay visible. A key image and a press effect travel inside the exported theme the same way the board's do. High-contrast accessibility drops single-key styles with the rest of the decoration, and a press effect never plays under reduce motion or power saving.
### Stickers
Up to six images laid over the keys, purely for decoration: typing goes straight through them. Transparent PNGs work best. Each sticker has position, size, rotation and opacity sliders, adjusted over a live miniature preview so what you drag is what the keyboard shows. Stickers travel with the exported theme the same way textures do.
### Key press effect
A small particle burst under every key press. Pick **Stars**, **Hearts**, **Sparkle**, **Confetti**, **Emoji** with a field for the emoji to throw (each one becomes its own particle kind), or **Images**, which takes up to six of your own pictures from the gallery, each a particle kind the same way. Transparent PNGs look best. They're stored small and keep their proportions in flight. An **Intensity** slider scales how many particles a press throws.
Six more controls shape every particle at the moment it spawns: a **Colour** picker (Natural, Key text, Accent, Trail, Custom, or Random) and five sliders, **Size**, **Speed**, **Spread**, **Gravity** (how fast particles fall; zero leaves them drifting, and below zero they float upward), and **Duration** (200-2500 ms). The effect never plays under reduce motion or power saving, and high-contrast accessibility drops it with the rest of the decoration.
### Key material
What the keys look like they're made of. Pick **Key cap** (a raised cap on a darker skirt that sinks when pressed), **Metal**, **Wood**, **Stone**, **Stitched cloth**, **Leather**, **Carbon fibre**, **Paper cut-out**, **Letterpress**, **Neon**, **Holographic**, **Terminal**, **Cartoon**, **Art glass**, **Candy**, **Ceramic**, **Resin**, **Arcade button**, **Typewriter**, **Instrument panel** or **Molten**. **Custom** starts from a flat key.
Each material is a preset of the same few parts, and every part has its own control under it: **Key cap height**, **Top face inset** and **Cap side shade** for the raised cap; **Gloss** and **Light direction** for the highlight; **Surface texture** (grain, linen, dots, scan lines, brushed, wood grain, stone, paper, carbon weave or leather) and its strength; **Edge** (stitching, a glow, an ink outline, a glazed rim, a pressed-in top edge or a double ring) with its colour and width; **Label effect** (glow, pressed in, raised or a shadow); and **Rainbow sheen**. Picking another material starts its preset afresh. The textures are drawn in greys, so they take each key's own colour.
### Moving background
Something moving on the board behind the keys: **Snow**, **Rain**, **Code rain**, **Bokeh**, **Stars**, **Warp speed**, **Fireflies**, **Dust**, **Embers**, **Bubbles**, **Confetti**, **Fireworks**, **Aurora**, **Nebula**, **Lava lamp**, **Waves**, **Smoke**, **Dot grid**, **Contour lines**, **Equaliser**, **Scanner**, **Sheen** or **Ripples**. **Amount**, **Speed**, **Brightness** and **Size** shape it, and two colour pickers set its colours (the accent and the label colour when left on Auto).
**Reacts to typing** makes each key press push the effect: it speeds up and glows brighter, then settles back as the typing stops. **Ripples** and **Dot grid** also send a ring out from each key you press. Under reduce motion or power saving the background stays as a still picture. While Reduce motion is on, or the phone is set to remove animations, the editor says so above **Moving background**, with a button that turns Reduce motion off.
### Key lighting
Light on the keys, the way a backlit keyboard has it. **Steady**, **Breathe**, **Wave**, **Sweep**, **Twinkle** and **Heartbeat** run on their own. **Pressed key**, **Ripple**, **Echo**, **Splash**, **Cross** and **Rain** start from the key you press and go dark again when you stop. **Light on** puts it on the key faces, round the key edges, or behind the keys, where it shows in the gaps. **Rainbow spread** turns the colour across the keyboard (360° shows the whole rainbow), and **Fade time** sets how long a press stays lit. Lighting is off under reduce motion and power saving.
### Touch bloom
The keyboard answers your finger itself. Turn on **Touch bloom** and a soft light spreads out from where you touch, across the background behind the keys, while the keys near your finger sink a little and spring back when you lift, with a small wobble. Keys further away stay still. When you glide, the light and the dip travel with your finger, and the bubble over a pressed key lights up in the same colour.
**Light colour** is the theme's accent unless you pick one. **Light strength** sets how bright the bloom is (0% leaves only the dip), **Reach** how far from your finger the light spreads and the keys move, **Key dip** how deep they sink (0% leaves only the light), and **Wobble** how much they bounce on the way back. Touch bloom is off under reduce motion and power saving.
### Key art
Hand-drawn keys from a file. **Load key art from a file** reads a skin: each kind of key (letters, special keys, enter, space, delete, shift, the mode key, punctuation, emoji, the globe) drawn as layers of vector shapes, plus an optional picture for the board. The colours in it are names of the theme's own colours, such as the key's face or the accent, made lighter, darker or more see-through, so the art takes whatever palette the theme has. Layers can move, fade, grow or turn while a key is held. A key with no drawing of its own uses its group's, then the letter key's. Key art replaces the key colours, material and border on the keys it draws. A file can also draw the popups: the bubble over a pressed key, the alternates and the language list then wear that drawing.
The file layout is the one the Kyng keyboard uses for its skins, so a skin document in that layout loads as it is. High contrast drops key art, materials, the moving background, lighting and touch bloom with the rest of the decoration.
### Font and sound
The theme can bring its own key font and key sound. You pick both from what's on the device: the styles the app ships, plus anything installed from the font and sound libraries. The sound list holds your installed sounds *and* your installed sound packs, each as its own row, so picking one is picking the sound itself rather than a category. They're referenced by name and never copied into the theme file. If someone imports your theme without that font or sound installed, their keyboard quietly uses their own global choice instead. Someone who has picked a key font of their own under [Keyboard fonts](https://wmkeyboard.pages.dev/themes/fonts/) keeps it too: the theme's font only applies while that setting is left on System default. Publish the font and sound as their own add-ons and list them under the theme's `requires` in a [repository manifest](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/), so the install screen offers to fetch them together.
**Key font** covers Latin, Cyrillic and Greek. Every other script keeps its own face, because a Latin-only display font draws a Bengali board as empty boxes.
**Add a font for a script** is how a theme keeps its look on those boards anyway. Pick a script, pick a face that actually carries its glyphs, and that script uses it instead of its automatic Noto face. Add as many as you have fonts for; the scripts you skip are unaffected. A pixel theme usually needs one file per script, since a single pixel font almost never covers Latin and Bengali and Arabic at once. Each of these is another add-on to publish and list under `requires`.
Your own pick in Appearance → Fonts still beats the theme's, the English one included. You chose that font on purpose, and a theme doesn't get to overrule it.
For a theme you plan to share, use the forms that travel. Google Font ids (`google:Caveat`) resolve the same on every device, and an installed sound or font can be named by its catalogue name: "Typewriter" for a sound, `installed:Bloxat` for a font. The ids the libraries mint locally differ from phone to phone.
A theme's sound is only ever a sound it can actually make. A theme whose sound is missing on this device (never installed, or since deleted) falls back to the global **Key press → Sound style** pick, and so does a theme file whose `soundStyle` is `CUSTOM` or `PACK` with no `soundCustomId` beside it. Those two styles name something installed rather than a waveform of their own, so without a name there is nothing to play, and overriding your own pick with silence-turned-system-click would leave the whole keyboard clicking with nothing on screen to say why.
### Accent
- **Accent** paints the shift-on tint, the patch behind a locked Shift, the active-tool color, the buttons across the panels, and the [smart chips](https://wmkeyboard.pages.dev/smart/chips/) on the suggestion strip, the contact chip among them. Those chips stay in Accent rather than the suggestion text color, so they read as answers rather than words.
- **Gesture trail** is the color of the swipe-typing trail. Auto falls back to Accent, and the field supports alpha.
### Popups
The popups have a section of their own: open it from the **Popups** row under [Toolbar, strip and popups](#toolbar-strip-and-popups), or from the **Popups** chip while picking on the keyboard. At its top is a drawing of the two popups in the theme you are editing, the bubble over a pressed key and the press-and-hold alternates with one lit, on a patch of the board's own fill, so a colour, the shape, the outline and the shadow can be judged without holding a key down. The bubble is left out while key popups are off under Key press, since the keyboard never draws it then.
- **Popup background** and **Popup text** color the bubble that appears above a key on press, and the press-and-hold alternates with it: the sheet the alternates sit on is Popup background, and every alternate not under your finger is Popup text. Both support Auto fallbacks, deriving from the board and key colors and from Key text respectively. Popup background supports alpha. The highlight that follows your finger across the alternates is **Selected highlight** under [Panels & suggestions](#panels--suggestions), and the character on it is **Selected text**, so the gap between that highlight and the sheet's edge is the alternates' spacing, which is Key press / Alternate keys / Alternate key spacing rather than a theme field. See [What's what](#whats-what) for a drawing of each part.
- **Popup shape** sets the outline of every popup surface the keyboard draws: the character bubble, the long-press alternates, the language list, the panel menus. It's the same picker and the same twelve shapes as Key shape, drawn at this theme's popup radius. Until you pick one, the theme follows Key press / Key popup / Popup shape.
- **Popup placement** decides whether the character bubble grows out of the pressed key (the stock style) or floats detached above it. **Default** leaves the choice to Key press. The other two make it part of the theme, so a bubble-popup theme looks right on every phone it lands on.
- **Popup outline** and its width row draw a border around the bubble, off until you pick a color, exactly like the key border. It draws on every popup surface the keyboard opens: the character bubble, the long-press alternates, the spacebar language picker, the clipboard and emoji menus, and the small pickers inside the tool panels.
- The popup can also wear an image: the **Key preview popup** row under [Key textures](#key-textures) paints one inside the bubble, clipped to the popup shape, using the same Crop/Stretch/Tile mode and opacity as the key textures.
- A theme with a [key material](#key-material) draws its popups in the same material: the bubble, the alternates and the language list of a wooden board are wood too. [Key art](#key-art) with a drawing for popups uses that drawing instead.
- **Menu shape** exists because the list menus (the spacebar language picker, the clipboard and emoji menus) don't blindly follow the popup shape. On **Automatic** they take a safe derivative: the plain outlines (Rounded, Square, Cut, Squircle) pass through, and the decorative ones fall back to Rounded. A slanted character bubble is fine. A slanted menu clips its own rows. Pick a shape here only to overrule that judgement.
### Toolbar, strip and popups
The bar above the keys is either the toolbar or the suggestion strip, and each has a section of its own rather than a long group on this page, as do the [popups](#popups). Open them from the **Toolbar**, **Suggestion strip** and **Popups** rows in this group, or pick the part itself: with **Tap a part to style it** (the button under the preview), pressing a tool opens the toolbar's section and pressing a suggestion opens the strip's, and the chip row above the preview carries all three as well. The section opens on the page straight under the preview, which stays pinned above it however far you scroll, and while the strip's section is open the preview puts a few words on the strip so there is something to look at. Only one part is open at a time; Back or the × beside its title closes it.
The **Toolbar** section:
- **Toolbar background** fills the bar behind the tools: their own row, when Appearance / Toolbar gives them one, and the suggestion strip while it is showing the tools instead of words. On Auto it matches **Suggestion strip** in the strip's section, which in turn matches the board, so the bars stay one colour until you pull this one apart. Alpha works here, so a half-transparent fill tints the strip's own colour rather than covering it. The strip takes its own colour back the moment words arrive, so a bar that flips between words and tools flips colour with them; if that reads as a flicker, leave the two the same and use the tools' own row instead.
- **Tool background shape** is the outline of the background behind each toolbar tool, from the same twelve shapes. It follows Appearance / Toolbar / Tool background shape until you pick one here. Nothing draws at all while the tool radius is 0, which is the setting for bare icons.
- **Tool icons** sets the toolbar's icon color, Auto by default.
- **Tool circles** and **Active tool circle** paint the background behind a toolbar icon, at rest and while pressed or active. Both support alpha.
- **Tool shadow** lifts that background off the bar.
- **Active tool icon** colors the glyph sitting on an active tool. Auto picks whichever of black or white reads on the active circle, which is what this was before the row existed. Set it when the automatic pick is technically legible but not the colour you want.
- **Tool outline** and **Tool outline width** put a border around that background, off until you pick a color, exactly like the key border in [Keys](#keys). The width row appears once a color is set, and nothing draws until it's above 0. A theme that sets one keeps it even with the accessibility **Key outlines** toggle on.
The **Suggestion strip** section:
- **Suggestion strip** fills the bar the word candidates sit on, and every other bar around the keys with it: the toolbar row, the emoji row, the symbol row, the dictionary bar, the fancy text bar and the selection macros. Those rows stack straight against each other, so one color across all of them is the only thing that reads as deliberate. It sits on Auto until you pick a color, and Auto means the bars carry the board straight through, gradient, image, animation and all. Alpha works here, so a half-transparent fill tints the board rather than covering it. One thing to watch: the words on the strip take their color from the board, not from this fill, so if you paint the bars a long way from the board color, set **Suggestion text** just below to match.
- **Suggestion text** colors the suggestion strip and the panels' body text. Auto falls back to Key text, contrast-guarded against the board, so a theme whose keys invert against its board (dark ink on cream keys over a near-black board) still gets a legible strip. An explicit choice here is honoured as written.
- **Other suggestions** colors the strip's suggestions other than the primary one: the runner-up on its left and the rest on its right, when the primary sits in the middle, or everything after the first word when it doesn't. Auto draws them in Suggestion text, which is what they always were. The primary keeps Suggestion text (and its bold), and the word autocorrect has settled on keeps the colour **Autocorrect word color** under Appearance / Toolbar gives it, so a strip that colours its runner-ups quieter never hides the one word a space is about to type. A word no list knows yet keeps **New word color** from the same screen in the same way.
- **Other suggestions size** draws those words at a share of the primary's size, 50% to 120% in 5% steps, 100% being the same size. It's a ratio, not a size, so it rides on the suggestion-size slider under Typing the way the primary does, and a word squeezed to fit its slot shrinks from this size rather than from the primary's. The conversion candidates of the CJK layouts and Avro's candidate list are one row of equals and ignore it.
- **Suggestion pills** colors the rounded box behind each word, which the strip draws only while **Coloured suggestions** is on under Typing / Suggestions. Auto is the theme's accent made faint (16% on a dark theme, 10% on a light one), so the pills follow the accent the way they always did; pick a color here to set them apart from it. Alpha counts, so a half-clear pill lets the strip show through.
- **Main suggestion pill** colors the pill behind the primary word, the one a space puts in. Auto lays the Suggestion pills color over itself, so the main word still reads a step deeper without a second pick; with both on Auto it's the accent at 34% on a dark theme and 24% on a light one.
### Panels & suggestions
- **Cards & search bar** is the `chipBackground` field. It covers suggestion chips and the search-bar surface across the tool panels. Auto falls back to Modifier keys, and the field supports alpha.
- **Popup shadow** lifts the key preview and the long-press alternates off the board.
- **Selected highlight** and **Selected text** color the letter your finger is on while you hold a key, and the selected row of the language list. Auto uses the accent, which is what these were before they had rows of their own.
- **One-handed rail** and **One-handed rail icons** color the strip beside the keyboard in one-handed mode. It used to draw in the settings app's colors and ignore your keyboard theme entirely.
- **Secondary text** colors the quieter text beside the main text: the second word of a suggestion, and the kind and time on a clipboard entry. Auto draws Suggestion text at 65% alpha, which is what it was before the row existed. Alpha works here.
- **Dividers** colors the hairlines between the parts of a panel and between suggestions. Auto draws Suggestion text at 25% alpha. Alpha works here too, so you can keep the rule faint in a hue of its own.
- **Card shadow** lifts the panel cards off the panel.
### Chips & buttons
Chips are the small tappable surfaces inside the tool panels: style pickers, tab strips, converter controls, dictionary suggestions, search bars, the cards in the clipboard, snippet, weather, calendar, grammar and plugin lists, and the buttons and fields a [plugin](https://wmkeyboard.pages.dev/plugins/overview/) draws. They take the card color above as their background, and this group styles the rest. The panels' own keypads (calculator, numpad, the converters, the text-editing pad, the handwriting and voice rails) count as keys rather than chips. They follow the theme's key shape and key border, so a circle-key theme gets circular calculator keys for free.
- **Chip text** and **Selected chip** / **Selected chip text** cover the resting label, plus the fill and label of the chip that's currently active. Auto derives all three from the theme, and the selected fill follows the active tool circle.
- **Chip outline** and its width row draw a border around every chip, off until you pick a color.
- **Chip shape** and **Chip corner radius** use the same twelve-shape picker as everywhere else. The default is the 12dp soft rectangle chips have always drawn.
- **Card shape** governs the bigger surfaces: clipboard and snippet cards, plugin rows, search bars, the converters' displays. They follow the chip shape through the same safety rule as the menu shape, so plain outlines pass through, decorative ones fall back to Rounded, and an explicit pick here overrules both.
Any row left on Auto shows an "Automatic" label under its name instead of a fixed value. Once you've overridden it, that row's color picker gains an **Automatic** button (next to Apply) that clears the override and puts the row back on Auto. On a row that is still on Auto, the picker opens on the colour Auto stands for, and pressing Apply without changing it leaves the row on Auto. A strip or toolbar on Auto paints nothing over the board, so writing the board's own colour over it would draw a lighter band.
### Gradients
Both **Board** and **Keys** can carry an optional gradient, painted over their solid color and toggled on with a switch next to each. Turning one on seeds it with a sensible default that you can then reshape. The board gradient starts as a 135° linear blend from Background to Accent, and the key gradient as a subtle 90° white sheen.

- **Type**: Linear, Radial, or Sweep.
- **Angle**: 0–360°, shown for Linear and Sweep only (Radial ignores it).
- **Color stops**: 2 to 4, each with its own color and alpha. **Add color** and **Remove color** grow and shrink the list.
### Corners
Off by default, so keys everywhere follow the global sliders (two under Appearance, one under Key press). Turning on **Corner radii for this theme** seeds three independent sliders for this theme alone, each starting at whatever its global counterpart is set to:

- **Key corner radius**: 0–28dp, seeded from the global Key corner radius slider (default 8dp) when you turn the switch on.
- **Popup corner radius**: 0–40dp, seeded from the global **Popup corner radius** slider under Key press (default 12dp).
- **Tool circle radius**: 0–20dp, where 0 draws no background behind the icon at all (the slider reads "Off" there), seeded from the global Tool circle radius slider (default 20dp).
### Layout
A second opt-in group, **Layout for this theme**, works the same way: one switch seeds a set of size overrides from your current global settings, and turning it off clears them all again. Tool background width, toolbar height, popup height, key height, key gap, left and right padding, key label size, bold labels, corner hint size, and the gesture trail's width and opacity all live here. Screen-specific sizes from Layout settings (folded, unfolded, landscape) still win over a theme's own.
Popup height is the height of the key-preview bubble. The global slider under Key press keeps one value for the on-key bubble and another for the floating one, and the theme keeps the same pair. The slider here shows and edits the height of the style the theme is drawing, its own placement if it names one and otherwise the global one, and the other style's height stays as it was. An on-key bubble is also never drawn shorter than the key plus its label, whatever height it is given, so it cannot vanish under your finger.
### Animation
Adds continuous motion to the board while the keyboard is on screen:
- **None** (default), **Flow**, or **Hue cycle**, as a segmented picker.
- Flow drifts the board gradient along its own axis (or orbits its center, for a radial gradient). Hue cycle slowly rotates the hue of the board gradient, or the solid board color if no gradient is set.
- A **Speed** slider (0.25×–3×, default 1×) appears once animation is on.
Only three built-in themes ship with animation turned on: Sunset drift and Cyberpunk use Flow, Aurora uses Hue cycle. Every other built-in, and every fresh custom theme, starts on None.
## Live preview while you edit
The keyboard itself is pinned at the top of the editor, drawn small enough to leave room for the controls but at the proportions it has on screen. It is the real keyboard, not a picture of one: press a key and it flashes, the popup rises, the key effect fires, the strip shows the word, and a Flow or Hue cycle animation moves. Every change you make below redraws it at once, and switching looks or themes crossfades the way the keyboard does. The preview always draws the theme you are editing, panels included: a layout or panel that names a theme of its own keeps that pairing on the keyboard, but here the Numpad, the emoji panel and the rest show your draft. When one does, a notice above the editor says which and what theme it uses, and **Clear** lets it follow the theme you picked again.
Typing goes into the preview alone. Nothing you type there reaches an app, is learned, or is saved. The keys work the way they do when you type: shift and caps lock, the `?123` and `ABC` keys, the globe key cycling your enabled languages, and the emoji key and the toolbar tools opening their panels in the theme. Tools that need something the editor does not have, a microphone or your clipboard history for example, open their panel and stop there.
The keyboard runs edge to edge across the screen. **Tap a part to style it**, the button under the preview, or **Pick on the keyboard** under [Toolbar, strip and popups](#toolbar-strip-and-popups), turns the preview from something you type on into something you pick from. Press a key and its [single-key style](#single-key-styles) opens; press a tool and the toolbar's section opens; press a suggestion and the strip's does. The chips above the preview switch it between the typing pages and the panels, since the keys that would normally get you there are being picked too, and swipe typing is off for as long as you are picking so a finger sliding to a key isn't read as a word. **Done** or Back puts the preview back to typing, and opening the docked keyboard does too.
When the preview takes too much of a small screen, drag either of the two corner marks under it inward or up. The whole keyboard shrinks at its own proportions, text and icons included, down to 35% of its full size, and stays centred. Drag outward or down to grow it again, or double-tap a mark to go back to full size. The editor remembers the size on this phone, and the docked keyboard opened by the button below is always full size.
The floating keyboard button at the bottom right opens the same keyboard at full size, docked along the bottom of the screen with the sections scrolling above it, the way an app sits over the keyboard. A line above the keys shows what you type, with a button to clear it. Tap the button again, press Back, or use the keyboard's own hide tool to put it away.
## Duplicating and deleting
There's no Save or Apply button anywhere in this screen: every field write goes straight to storage, so closing the editor (or the app) never loses anything or asks you to confirm.
**Duplicating** an existing custom theme isn't available from inside the editor itself; the copy-then-edit flow described in [Creating a theme](#creating-a-theme) only applies to built-ins, from the gallery.
**Deleting** a custom theme is a gallery action too: tap the trash icon on its card. A confirmation dialog asks "Delete this theme?" with a note that it will be removed permanently. The theme's image files (background, textures, stickers, particles) are not deleted with it. A housekeeping pass clears them once nothing in any theme still points at them, and only after they have been unreferenced for a day, because looks in the same family share their files:
themes/editor-delete-confirm — Deleting a custom theme asks for confirmation before removing it.
After a delete, an **Undo** bar shows at the bottom of the screen for a few seconds. It puts the theme back as it was, with all its looks and pictures, and switches back to it if it was the theme in use.
To delete several at once, long-press a card in Your themes. The cards there turn into checkboxes: tap more of them to tick them, or use **Select all** in the bar at the bottom, then the trash icon in that bar. It asks once for all of them, and the same Undo bar brings back every theme it deleted. While picking, a tap ticks a card instead of switching the keyboard to it; the close button or Back stops picking. The upload icon in the same bar saves every ticked card in one `.zip` of `.wmtheme.json` files, one per theme, the way each card's own export writes it: a family card as the whole family, a card in the flat gallery as its one look. **Import** reads that zip straight back. See [Several files at once](https://wmkeyboard.pages.dev/themes/sharing/#several-files-at-once).
The same bar regroups themes. With two or more cards ticked, **Group into one theme** makes them looks of a single theme, named after the first card, with the look dots on its card to switch between them; rename it with the **Group name** field in the editor. **Ungroup** shows up when a ticked theme has more than one look, and turns every look into a theme of its own. A look that only had a short name of its own, such as "Dark", takes the theme's name with it and becomes "Nord (Dark)". In the flat gallery a card is one look, so Group and Ungroup move just the looks you ticked and leave the rest of their theme together. Neither changes the theme in use, and both offer Undo.
Delete isn't offered for built-in themes or for the Default theme, only for themes in Your themes. Export is offered on built-in cards as well, so you can hand someone a copy of one as a file; only the Default card has no export button, since it's generated live rather than stored.
## Details and edge cases
- **Editing an existing custom theme doesn't switch to it.** Tapping the pencil on a theme already in Your Themes opens the editor without changing your active keyboard theme. Only Create, Duplicate (from a built-in), and Import set the edited theme as active, as a side effect of those actions rather than of editing.
- **Typography is part of a theme, but only once you set it.** The key font, the per-script fonts, key label size, bold key labels and corner hint size are all theme fields. Each one is empty until you touch it, and an empty field follows your global setting instead. So a theme you never gave a font to picks up whatever [Keyboard fonts](https://wmkeyboard.pages.dev/themes/fonts/) is set to, and changing that global font changes how the theme looks. Fonts are the one exception to "the theme's value wins once set": the theme's font shows only while your own font for that script is on its default, because a font you picked is a deliberate answer and a theme's is a suggestion. The sizes work the same way through the **Layout for this theme** switch.
- **The key-preview bubble has no band to rise into here.** On the real keyboard, the bubble for a top-row key is drawn in an empty strip of window kept above the board, over whatever app you are typing into. The preview has no window of its own, so it holds no such strip. Keeping one would be dead space taken out of the board's own room, and on the miniature it cost the board most of the width it had. The docked keyboard draws that bubble over the sections above it instead, the way the real one draws it over the app; the miniature, which is clipped to its own rounded corners, sits it on the board's top edge rather than letting it be cut in half.
- **Selected highlight used to miss the alternates.** Before version 0.5.14 the press-and-hold popup drew the highlight under your finger in the **Pressed key** colour and ignored Selected highlight and Selected text, so changing either row did nothing visible there. Both now reach the alternates as well as the menus. A theme that relied on the old behaviour and wants the pressed colour back sets Selected highlight to it.
- **Two "Auto" swatches don't match the real keyboard.** Pressed key and Active tool circle, when left on Auto, are worked out by a different formula in the editor's swatch than on the board itself. The swatch blends the key colour 40% toward the accent; the keyboard blends it 25% toward the key text. On a theme with a strong accent the two are visibly different colours, so trust the live preview over the swatch. Every other Auto fallback (popup background, tool circles at rest, and so on) matches exactly.
- **A theme's background image lives in this same editor**, under Board, but the picker, crop dialog, and opacity/blur sliders are covered in full on [Image backgrounds](https://wmkeyboard.pages.dev/themes/backgrounds/) rather than here.
- **Corner-radius overrides here are theme-specific**; the sliders they inherit from and the global key-shape basics are covered in [Theming overview](https://wmkeyboard.pages.dev/themes/overview/).
- **Exporting, importing, and installing a theme from a repository** are covered in [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/), including what happens to a theme's colors and images when it travels as a file.
---
# Theme families and looks
URL: https://wmkeyboard.pages.dev/themes/families/
Section: Themes & appearance
Summary: One theme, several looks: family cards with swatch dots, the Grouped and Separate gallery styles, and building a family of your own.
A theme can carry more than one **look**. The dark colourways are really one design in seven colours. Catppuccin is four flavours of the same palette. Solarized is a light half and a dark half, and so is Dracula, whose light half is Alucard. Rather than spilling those across the gallery as unrelated cards, they group into one **family**: one card, one preview, and a row of dots under it, one per look.
Each look is still a whole theme underneath. It has its own complete set of colours and its own id, so anything that names a theme can name one specific look.
## Family cards in the gallery
Appearance / Keyboard themes
A family card shows the family's name as its title, the name of the look it's currently showing underneath that, and a dot per look beneath the preview. Each dot is painted with that look's board (its gradient if it has one, its plain board colour otherwise) with the accent as a small core, so two colourways with the same board still tell apart.
Tapping a dot does two things at once: the card turns its preview to that look, and the keyboard switches to it. There's no second screen to open, which is the point of putting the dots on the card.
The rest of the card behaves the way a single-theme card does, with one wrinkle each:
- **The pencil** on a built-in family asks **Make a copy** first, offering **Only this look** or **All looks**. On a family of your own it just opens the editor, on the look the card is showing.
- **Export** saves the whole family as one file while the gallery is grouped, and the single look the card shows while it's separate.
- **Delete** goes by the card, not by the look showing on it. A grouped family card deletes the whole family. To drop one look, either switch the gallery to Separate and delete that look's own card, or use the bin beside the Looks chips in the editor. The one card that still takes the whole family with it is the first look's, since that's the theme the others hang off.
- **The pin** (built-ins only) is per look, not per family, because the keyboard's Appearance tool lists looks.
## Grouped or separate
Appearance / Keyboard themes / Theme gallery style
The dots aren't compulsory. A three-way control above the grid decides how a multi-look theme is drawn:
| Choice | What the grid does |
|---|---|
| **Auto** *(default)* | Follows the answer you gave the setup wizard |
| **Grouped** | A theme with more than one look is one card with a dot per look |
| **Separate** | Every look of every theme is its own card |
**Auto** reads the "What kind of keyboard do you want?" question from the setup wizard: **Keep it simple** gets the separate list, and every other answer, including never having answered at all, gets the grouped cards. So an install from before families existed picks a side on its own, and an explicit Grouped or Separate always wins over the guess. A caption above the control tells you which way it's currently resolving.
It makes a real difference to how far the built-in section scrolls. Twenty-four of the 29 built-in themes belong to a family, so grouped they fit into eleven cards.
## The built-in families
The card title is the family's name, which isn't always the name of the look heading it: the dark colourways card reads **Nightfall**, not Ocean.
| Family | Looks |
|---|---|
| Nightfall | Ocean, Forest, Sunset, Berry, Crimson, Slate, Pitch black |
| Pastel | Snow, Mint, Rose, Sand, Glacier |
| Gradients | Deep sea, Sunset drift, Aurora, Nebula |
| Frost | Frost, Frost wallpaper, Frost light, Frost liquid |
| Dracula | Dracula, Alucard |
| Solarized | Solarized Dark, Solarized Light |
| Catppuccin | Catppuccin Mocha, Catppuccin Latte, Catppuccin Frappé, Catppuccin Macchiato |
Bubble, Facet, Nord, Tokyo Night and Cyberpunk stand alone, so they draw as ordinary single cards either way.
Switch the gallery to Separate and those family names disappear with the cards: every look goes back to its own name, which is the name it has always had and the one an export writes.
## Building a family of your own
Appearance / Keyboard themes / (edit a theme) / Looks
Every custom theme starts as one look, and the editor's **Looks** row (right under the live preview) is where you add more:
- **Add look** copies the look you have open into a new one and switches the editor to it, so the second look starts from the first rather than from nothing. It stops at twelve extras, thirteen looks in all.
- **The chips** switch which look the whole editor below is editing. The colours, the shape, the background, all of it belongs to the open look.
- **Group name** appears once there are two looks. That's the title the gallery card shows over the dots. Leave it empty and the card falls back to the first look's own name.
- **The bin icon** beside the chips deletes the look you have open, and only shows while that isn't the first one.
Looks don't nest. A look can't have looks of its own, and anything that would create one (a hand-edited file, an import) has the nesting stripped on the way in.
## What a look's id buys you
Because every look keeps its own id rather than being an index into a family, a look is addressable everywhere a theme is:
- **Auto theme's** light and dark slots name looks, one each or a whole set. The picker lists them flat with the family's name as a heading above its members, so you can put Solarized Light in one slot and Solarized Dark in the other. In Random mode the family's name carries a checkbox of its own that ticks every look under it, which is the quick way to say "any of the four Catppuccins". See [Theming overview](https://wmkeyboard.pages.dev/themes/overview/#one-theme-or-a-set).
- **The Appearance tool's** pin list holds look ids, so you can pin Aurora without pinning the rest of the gradient family.
- **Per-mode themes** name a look the same way.
The ids didn't change when families landed, either. Every built-in id that ever shipped still resolves, so a selection, a pin or an auto-theme slot set before the grouping still points at the same colours.
## Details & edge cases
- **A built-in family's label is translated; yours is typed.** Built-in family names come from string resources so they localise, which is why there's no Group name field on a built-in. Your own families carry the text you typed in that field, exported verbatim.
- **A separate card exports a look on its own.** The export strips the variants and the group name, so what the other person imports is one theme, not the family minus a few looks.
- **Importing a family mints fresh ids for every look.** The ids inside the file are discarded (the head gets a new one and each look is reminted off it), so importing the same family twice gives you two independent families rather than one overwritten one.
- **Older builds see the head look only.** The looks live in a key an older importer doesn't know, so a family imported into a build from before this shipped comes in as the first look alone rather than failing.
- **A FlorisBoard theme with a day and a night version arrives as one theme with two looks.** See [Importing other keyboards](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/).
- **Image files are shared between looks, not copied.** Add look points the new look at the same background file, and a file is only deleted once nothing in the theme still points at it.
## Related pages
- [Theming overview](https://wmkeyboard.pages.dev/themes/overview/) for the gallery, Mode, Material You and Auto theme
- [Theme editor](https://wmkeyboard.pages.dev/themes/editor/) for everything below the Looks row
- [Sharing themes](https://wmkeyboard.pages.dev/themes/sharing/) for exporting and importing
- [Keyboard themes reference](https://wmkeyboard.pages.dev/reference/settings/themes/) for the field-by-field tables
---
# Keyboard fonts
URL: https://wmkeyboard.pages.dev/themes/fonts/
Section: Themes & appearance
Summary: Change the key-label typeface: Google Fonts, your own files, or addon fonts.
The keyboard doesn't have to draw its key labels in the system font. Pick from a curated set of Google Fonts (20 for Latin, a handful for each other script), import your own `.ttf`/`.otf` file, or install one from an addon repository, and do it separately for Latin keys, Bengali keys, and each other script your enabled languages use.
themes/font-picker-english — The Keyboard font screen: curated Google Fonts, each row previewed in its own face.
## Choosing a Google Font
Every picker is a scrollable list of `SettingsGroup` rows, and every row renders its own label live in the face it names: nothing to imagine from a font name alone. Fonts are fetched once through the Google Play Services downloadable-fonts provider and cached on-device after that, so the second time you pick one there's no download at all.
On a device with no Play Services (a de-Googled ROM, most commonly), the Google Font rows don't appear at all. Picking one there would resolve to the system face and change nothing on screen. Rather than a list of rows that all look identical, you get one paragraph at the top of the screen: Play Services supplies those fonts, and the lists below hold the system font, your imports, and anything you installed from the store. The theme editor's own font picker drops its Google list on the same devices, for the same reason.
- **English** gets 20 curated faces: Roboto, Inter, Open Sans, Lato, Montserrat, Poppins, Nunito, Rubik, Ubuntu, Source Sans 3, Merriweather, Lora, Playfair Display, Roboto Slab, Roboto Mono, JetBrains Mono, Comfortaa, Pacifico, Caveat, and Orbitron. This same picker also draws Cyrillic and Greek keys, since neither script has one of its own.
- **Bengali** gets an **Automatic (Noto)** row (which draws Noto Sans Bengali) plus 8 alternatives: Hind Siliguri, Noto Serif Bengali, Anek Bangla, Baloo Da 2, Tiro Bangla, Atma, Mina, and Galada, shown only while a Bengali-script language is enabled.
- **21 other scripts** (Arabic, Hebrew, Armenian, Georgian, Devanagari, Gurmukhi, Gujarati, Odia, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Lao, Khmer, Myanmar, Ethiopic, Korean, Japanese, and Chinese) each get their own short curated list (from a single alternative for Armenian or Georgian, up to nine for Thai, Chinese, Korean, or Japanese), plus an **Automatic (Noto)** default row. A script's section only appears while a language using that script is enabled, so the settings screen never shows pickers for scripts you don't type in. Myanmar's automatic face is fetched from the font provider like the others, unless Burmese's [Unicode key font](https://wmkeyboard.pages.dev/languages/burmese/#unicode-key-font) is on: then it's the keyboard's own downloaded copy of Noto Sans Myanmar, for phones whose font is Zawgyi.
- Dozens of other scripts draw with an automatic Noto face too, with no picker at all, since none has a second well-supported face to offer. That covers real shipped-language scripts such as Thaana, Syriac, Tifinagh, Cherokee, N'Ko, Canadian Aboriginal Syllabics, Tibetan, Ol Chiki, Meetei Mayek, and Tai Le, plus notations like music and Braille.
Selecting a font applies it immediately. The English face governs English mode, the Bengali face governs Avro, প্রভাত, and জাতীয় modes. Each script face governs typing in that script, across key labels, the suggestion strip, and the keyboard's own panels.
A font you pick here beats one that comes with a theme or a layout. A theme's key font and a layout's label font only fill in while that script's row is left on its default, so leave English on **System default** if you want a theme or layout to bring its own type.
## Importing your own font file
Every script section has an **Import a font file (.ttf or .otf)** button, and its own installed-fonts list once there's a font that fits it. The button adds the file to one shared library rather than overwriting a single slot, so importing a second font doesn't evict the first. Each list is filtered to the fonts that declare that script, plus any that declare no coverage at all. Import a Devanagari or Thai font from any section, or install one from an addon repository, and it turns up in that script's own picker. English and Bengali also keep a legacy single-file slot from older builds.
A picked file goes through two checks before it's accepted:
1. A byte-signature sniff that gives a specific rejection message for common near-misses: WOFF/WOFF2 web fonts ("use the .ttf or .otf version"), zip archives, or HTML/XML files saved with the wrong extension.
2. An actual font-load attempt, so a file with a plausible header but a broken internal table is still caught before it's registered.
You can pick several files at once in the file picker, and each one joins the library. Two limits apply: 50 fonts in the library at once, and 64 MB per file. Hitting either shows a plain-language dialog instead of a silent failure. Imported fonts live in the app's private storage under a generated id, never the file's original name, and if two fonts share a display name the newer one becomes "Name (2)".
Older builds stored one custom English file and one custom Bengali file directly, before the multi-font library existed. Those two legacy files still work, and they still show up as selectable rows for anyone who set one before upgrading. New imports always go into the library instead.
## Installing fonts from an addon repository
Fonts are one of the addon types repositories can ship: a text font (`font`) and an emoji font (`emoji_font`) are distinct types, each capped at 64 MB, matching the manual-import limit. Neither type is previewable in the catalogue: a font can only really be judged by looking at the keyboard wearing it, so there's no static preview image, just an install-and-try workflow.
Installing a text font addon adds it straight to the library with no follow-up question. The catalogue's **Use** button takes you to the Keyboard font screen and scrolls to the font's own row, in whichever section lists it, so a Devanagari face lands in the Devanagari section rather than at the top. The **Installed fonts** heading under English is only the fallback, for when no row on the screen carries that font. An emoji font addon works differently, and asks whether to start drawing emoji with it right away (see below). Uninstalling a text font removes it from the library outright, and clears it from every slot that named it: the English face, the Bengali face, and any per-script override all go back to their automatic default in the same step.
See [Installing addons](https://wmkeyboard.pages.dev/addons/installing/) for the general install flow, and [Addon repository format](https://wmkeyboard.pages.dev/development/addon-repos/repo-format/) if you're building a repository of your own.
## Why a font only shows up where it fits
A font can declare which languages it covers. That claim is what keeps a Latin-only display face out of the Bengali picker's installed list, where selecting it would draw every key as an empty box. A section lists a font when one of its declared languages matches that section's script, or when the font declares no coverage at all. A font you import by hand makes no claim, so every section offers it. An addon font's claim comes from the repository's own manifest, which is how a Devanagari face lands in the Devanagari section and stays out of the others.
The filter only curates which fonts a picker *offers*. It isn't what keeps keys from going blank. Rendering falls back per glyph to the system font automatically, whatever a font claims, so an exotic display face never leaves a key empty.
themes/font-picker-script — A script-specific picker (Devanagari): the automatic Noto face, a short curated list, and any installed fonts that declare coverage for the script.
## Emoji fonts are separate
The font you pick here never touches emoji: key labels and emoji are two independent settings, with their own pickers, their own addon type, and their own defaults. See [Emoji customization](https://wmkeyboard.pages.dev/emoji/customization/) for choosing between your phone's system emoji set, Google's Noto Color Emoji, an installed addon font, or a custom file.
## Options
Appearance / Keyboard font
- **English font** takes the system default, one of 20 Google Fonts, or an imported or installed file. It also governs Cyrillic and Greek. Default: system default.
- **One section per enabled script**: the script's Automatic (Noto) face, one of its curated Google Fonts, or anything in the library that covers it. Default: Automatic (Noto). English and Bengali also keep a legacy single-file slot.
- **Installed fonts** is the shared library. A section lists the fonts that declare its script or declare no coverage at all, and the heading stays hidden when none of them fit. Delete a font from any section that lists it.
A related but separate control: Accessibility / Readable font is a one-tap shortcut that sets the English font to Atkinson Hyperlegible, the Braille Institute's low-vision face where lookalike characters (capital I, lowercase l, digit 1) are drawn distinctly. It's the same setting as the English font picker above, just reached from Accessibility instead.
Font face and font size are different settings. The size of the labels on the keys (a 0.7×–2.0× scale, independent of typeface) lives on the main Appearance screen, not here.
## Details & edge cases
- Fonts apply to key labels, the suggestion strip, and the keyboard's own panels, but not to the text you type, which always commits as plain Unicode and renders with whatever font the receiving app and device use.
- The language switcher (the spacebar's preview, the picker list and the carousel) draws each language's name in the face of the script it's written in, not the active layout's: বাংলা in your Bengali face, English and names like Avro phonetic in your English one. So switching language never changes how the names look or how wide they are.
- On a board in another script, Latin text keeps your English font: an English suggestion, a copied word in the clipboard chip, the digits on the number row. Every letter your English font has comes from it, and the rest come from the script's face. This needs Android 10 or later; older versions draw the whole board in the script's face, Latin included. On **System default** the Latin comes from your phone's own font, including one your ROM or a font app put in place of Roboto, so a phonetic board like Avro shows its letter keys the way the rest of the phone does. That part needs Android 12; on 10 and 11 the script's face draws a System default board's Latin.
- No Play Services means no Google Font rows: they're removed from every picker rather than left there resolving to the system face, and a short paragraph at the top of the screen says why. The system font, your imported files and the font library are all the app's own, so those stay.
- With the provider present but the device offline the first time a Google Font is needed, that row quietly renders in the system font instead of erroring; it downloads normally as soon as the provider is reachable.
- The 50-font and 64 MB limits are shared across the whole library: text and emoji fonts, manually imported and addon-installed, all count against the same caps.
- Deleting a font from its row on the Keyboard font screen resets that setting to the automatic default first if it was currently selected, so the keyboard is never left pointing at a file that's about to disappear. Uninstalling the same font from the Addons screen does the same thing, and clears all three kinds of slot (English, Bengali, per-script) rather than only the one you were looking at.
- A script's picker section only appears while a language using that script is enabled; disabling the last language of a script hides the section, though the underlying setting isn't deleted.
---
# Frosted glass
URL: https://wmkeyboard.pages.dev/themes/glass/
Section: Themes & appearance
Summary: See-through keys, bars and popups over a blurred backdrop: how glass themes work, what they need from the phone, and every knob in the editor.
A glass theme makes the keyboard's surfaces see-through, blurs what is behind them, and dresses each one with a rim of light and a little grain so a translucent key reads as a pane of frosted glass rather than as a colour you can see through. It is a per-theme setting: every custom theme carries its own **Glass** section in the [theme editor](https://wmkeyboard.pages.dev/themes/editor/#glass), and the built-in **Frost** family shows the ways it can be used.
Appearance / Keyboard themes / (edit a theme) / Glass
## What is behind the glass
Glass blurs whatever is behind it, and a theme has three things it can put there.
**The app behind the keyboard.** With **Blur behind the keyboard** on, the keyboard asks Android to blur whatever app is under its window, and the board's own colour becomes a tint over that blur. This is the system's own window blur, the one the notification shade uses, so it costs the keyboard nothing per frame. It needs Android 12 or later, and it only runs while the phone allows window blur at all. The keyboard never turns it off because of the phone's battery saver; Android does that itself, switching window blur off for every app while battery saver is on, as do the developer option *Allow window-level blurs* and some manufacturers on their cheaper models. The keyboard watches that state live and switches over the moment it changes. When the phone isn't blurring windows, the theme editor says so in red under **Blur behind the keyboard**: on such a phone the glass shows your wallpaper, which stays the same in every app, so a theme that changes with the app on someone else's phone looks fixed on yours.
Whenever the blur cannot run, the board stands on your wallpaper instead (see below), or, with nothing behind the glass, is drawn at least as opaque as the theme's **Board opacity without blur** slider says (92% by default). The keys of a glass theme were designed to sit on a blur, and over a sharp app at 40% they would be unreadable; the fallback keeps them readable and the theme still looks like itself. The floating keyboard never blurs behind itself either, because its window spans the whole screen and the blur would cover the whole app, so it uses the fallback too. WM Keyboard's own [power saving](https://wmkeyboard.pages.dev/typing/power-saving/) switches the blur off too, through its **Glass blur** switch, and only while that switch is on.
**The theme's background image.** A theme with a [background image](https://wmkeyboard.pages.dev/themes/backgrounds/) keeps that image sharp on the board, and the glass surfaces show it *blurred* where they cover it. The keyboard bakes one blurred, re-saturated copy of the image when the theme is applied, and every glass surface draws the slice of that copy that lies under it, then its own translucent colour on top. Nothing is blurred while you type: a key's face is one image draw with the right offset. This works on every Android version. The bake follows three sliders: **Backdrop blur**, **Backdrop saturation** and **Backdrop brightness**. **Blur the background too**, on by default, draws the board's own picture through the same bake, so the board between the keys is frosted like the keys and the three sliders visibly reach the picture. Turn it off to keep the board as sharp as its own **Image blur** leaves it, with only the panes frosted.
**Your wallpaper.** With no image of its own, a glass theme stands on a band of your wallpaper: the bottom of it by default, where the keyboard sits, or the middle or the top under **Part of the wallpaper**. The band is drawn as the board's own picture, under the board colour, and baked for the keys the same way an image is. That is what a window-blur theme falls back to where the app cannot be blurred (an older Android, battery saver, the floating keyboard, the editor's preview), so instead of going opaque the board shows your wallpaper blurred, which is close to what the blurred app would have looked like. The built-in **Frost wallpaper** look asks for the wallpaper outright and never blurs the app, so its backdrop is steady whatever you are typing into.
Whether that band is the wallpaper's real picture depends on the phone. Android 13 and later keep the wallpaper's pixels private from every app, so there the keyboard paints soft fields of light in the wallpaper's own three colours instead, which the system does publish; under the glass's blur the two read much the same. On Android 12 and below the picture itself can be read once the theme editor's **Use your real wallpaper** row has been allowed storage access; until then the colour fields stand in there too. A phone with no wallpaper colours at all (Android 8.0) gets the fields in the theme's own colours.
**Behind the glass** in the editor picks between these: **Auto** (the image if the theme has one, otherwise the wallpaper whenever the app is not being blurred), **Image**, **Wallpaper** (always, even where the app could be blurred instead) and **Nothing**.
A board with nothing behind its glass is its own see-through colour, with the rim and the grain. Over a gradient that already looks right, since blurring a smooth gradient changes nothing. An animated background image (a GIF or animated WebP) that is playing cannot be baked either; its glass surfaces are tint-only until the animation is stopped or reduce motion stills it.
## Which surfaces are glass
**Glass surfaces** is a row of chips, one per part of the keyboard: Letter keys, Modifier keys, Enter key, Space bar, Bars (the suggestion strip and every row stacked with it), Tool buttons, Chips, Cards, Popups (the press-and-hold alternates and the key-preview bubble) and Menus (the language picker and the clipboard, emoji and symbol menus). A fresh glass theme has every surface on except the Enter key, whose solid accent is what makes it easy to find. Inside the tool panels the same split holds: every chip there (the GIF, sticker and theme tabs, filter and action chips, search fields) is a Chip, every card (snippets, clipboard items, notices like "this field does not accept GIFs") is a Card, and a panel's own keys (the calculator, the number pad, the handwriting and voice rails) follow the Letter keys and Modifier keys chips.
Popups and menus sample the same backdrop as the keys wherever there is one, a theme image or the wallpaper band. Where there isn't, because the glass is the live blur of the app, Android offers no way to blur only what lies under a popup's own window, so on Android 12 and later the keyboard softens *itself* while a glass popup or menu is open, and the popup reads as glass over softened keys. Nothing outside the keyboard is touched. On older phones, or with reduce motion on, those popups are drawn at the fallback opacity instead, like the board.
## Rim and grain
Every glass surface gets a one-pixel **Rim**: a hairline of light along its top-left edge fading into a hairline of shadow along its bottom-right, the way light catches a pane. The slider is its strength; at zero there is none. **Rim color** is the colour of the lit edge, Automatic being a soft white on a dark theme and a brighter white on a light one, where the panes are pale and need the edge more. The shadowed edge is always a translucent black.
**Grain** lays a faint, fixed noise over each surface. A few percent is enough; it is what stops a large pane of glass from looking like flat plastic. The grain is the same tile on every surface and every launch, so a theme never twinkles.
## Liquid glass
Every pane, popups and menus included, shows the backdrop under the board's own colour or gradient, laid out as the board lays it out, so a gradient runs on unbroken across the keys and the toolbar. Popups and menus find their place on the board through the screen, so the press-and-hold alternates, the key bubble and the spacebar's language bar are panes of the same glass as the keys, with the letter keys' fill.
**Kind of glass** has two settings. **Frosted** is everything above: the blurred slice under the surface's colour. **Liquid** is the look Apple calls liquid glass, and it changes what happens at the edge of every pane. Each pane is flat glass in the middle with a bevel about 12 dp wide at its rim, the same on every key so the space bar and a letter look like one material. Almost all of the bending happens in the last few pixels of that bevel: what lies just inside the edge is folded back into a thin, squeezed mirror ring, while the middle shows the backdrop straight through. Red, green and blue bend by slightly different amounts there, so the ring is fringed with colour like the edge of a prism. The light is a hairline on the edges facing the upper left and an equal one on the edges facing the lower right, where the inside of the glass reflects it, fading out along the sides, with a short glow inward on the lit side; there is no dark edge. Pressing a key bends its rim harder and swells the middle a little. Full-width bars stay frosted, since a lens bent a wedge into their ends. **Refraction** sets how far the edge bends the backdrop, **Color split** how far the colours part, and **Rim** how bright the hairlines are. The Liquid preset also drops the blur to almost nothing, turns the grain off and brightens the rim, since a lens is clear.
A lens needs two things. It needs a picture to bend, so it works over a background image or the wallpaper band and not over the blurred app, whose pixels belong to the system and cannot be read; a window-blur theme set to Liquid is a lens on a phone that cannot blur and frosted on one that can, unless **Behind the glass** says Wallpaper. And it needs Android 13 or later, where a pane can run a small pixel program; older phones draw a liquid theme as very clear frosted glass, with the same two hairlines of light and no dark edge. The program runs on the graphics chip for the keys' pixels only, and it is recorded once per key, not re-run on every keystroke.
The optics follow the open-source [Liquid-Glass-Android](https://github.com/QWEA0/Liquid-Glass-Android) lens, whose rim profile and light were measured off iOS 26. What is not here: Apple's panes also merge and stretch into each other as they move, and their highlight follows the phone's tilt. Neither is part of a theme yet.
## Presets and dressing a theme
The **Presets** row sets the glass sliders in one tap and makes the theme's fills see-through where they are not already:
| Preset | What it does |
|---|---|
| Frosted | Milky glass: a soft blur, colours lifted a little, a quiet rim. The default. |
| Clear | Nearly transparent panes with a bright rim, the way polished glass catches light. |
| Mica | Heavy blur and muted colour, like a wall of frosted acrylic. |
| Smoked | The backdrop dimmed, a low rim, a touch more grain. |
| Liquid | Thick clear lenses: the backdrop bent and colour-split at the edge, a bright rim, no grain. Android 13 and later draw the lens. |
A preset lowers the opacity of the letter-key, modifier-key, chip, tool and popup colours to what it needs, but only where a fill is still more opaque than that: a colour you have already made more see-through is left alone. It also gives the letter keys a [key gradient](https://wmkeyboard.pages.dev/themes/editor/#gradients) sheen if the theme has none, so each pane catches the light across its top. Whether the app behind is blurred is left as you set it. Afterwards nothing remembers which preset was picked, and every slider still moves.
## The Frost family
Three built-in looks on one card, all glass:
- **Frost** is styled after FrostKeys' frosted glass: it blurs the app behind the keyboard and lays only a thin tint over it, about a third, in your wallpaper's colours, with the letters as faint panes in the same hues, the action keys (shift, delete, ?123 and Enter) a solid accent blue, and no rim or grain. **Accent-tinted board**, in the Glass section of a copy of Frost, turns the board's accent off: the board keeps a darker, quieter tint in the wallpaper's hue while the keys stay as they are. That thin tint is the look, and its cost is that labels get faint over a white app, as they do in FrostKeys. On a phone that cannot blur, the board stands on your wallpaper band under the same thin tint instead.
- **Frost wallpaper** stands on your wallpaper outright and never blurs the app, so its backdrop is steady whatever you type into, and it is the look every phone and the gallery can show in full.
- **Frost light** is the same glass as Frost for a light phone.
- **Frost liquid** sets the glass to Liquid: a board barely tinted over the blurred app, clear panes with a bright rim and no grain, the action keys a quieter pane than the letters and Enter a pale grey. Over a live blur its keys draw as very clear frosted glass; where the app cannot be blurred, the board turns nearly opaque. Like Frost, its thin board gets faint over a white app.
## Details and edge cases
- **Contrast.** Glass is see-through by design, so what is behind it changes how readable the labels are. The Frost looks are checked against a white app and a black app, blurred and not; a theme of your own is yours to judge in the editor's preview, which has no app behind it and so shows the theme the way a phone that cannot blur draws it, on your wallpaper band.
- **The wallpaper band refreshes** when the wallpaper changes, when the theme is applied, and when the keyboard's size changes. It is one small picture in the same cache as the theme's background image.
- **High contrast** (under Accessibility) drops the glass with the rest of the decoration: the board goes solid and the keys with it.
- **Theme crossfade.** Switching themes eases the rim, the grain and the fallback opacity. The blur itself, its radius and the surface set switch over at the midpoint, since each would otherwise ask for a new bake or a new window layout on every frame of the fade.
- **The key-preview band.** The keyboard normally keeps an empty strip of window above the top row, where a top-row key's bubble pops up over the app. A window blur covers all of the window, that strip included, and it sits over the field you are typing in, so while the app behind is blurred the strip is not kept and a top-row bubble stays on the board's top edge instead.
- **The bars' held band.** The keyboard normally keeps a strip of empty window above the keys while a bar row such as the tools row is folded away, so toggling it never resizes the window. With the window blurred that strip would be a band of blurred app above the keys, so a glass theme gives the band up and takes the one resize instead.
- **Export.** A theme's glass settings travel in its `.wmtheme.json` file, the wallpaper choice included; the wallpaper itself is the other phone's own. An app version from before glass existed imports the same file as a plain translucent theme and loses nothing else.
---
# Icon packs
URL: https://wmkeyboard.pages.dev/themes/icon-packs/
Section: Themes & appearance
Summary: Replace toolbar and key icons one at a time, or all at once by installing an icon pack.
Every glyph the keyboard draws around your keys (toolbar tools, the Shift and Enter keys, the emoji tab strip) is a named icon slot you can replace. Change one icon at a time, or install a pack that redraws the whole set.
The built-in icons, in the keyboard and in the settings app alike, are Google's Material Symbols in their Rounded style, the same family Android itself draws with.
themes/icons-screen-overview — The Icons screen: the active pack, and every replaceable slot grouped by where it appears.
## What you can replace
There are 113 replaceable slots, split into four groups:
| Group | Covers | Count |
|---|---|---|
| Tools | Every toolbar tool's own icon (clipboard, GIF, voice, and so on) | 72 |
| Keys | Shift (off/on/locked), the Caps Lock key, backspace, forward delete, globe, switch keyboard, emoji key, the four arrow-row keys, the Tab key, and Enter in each of its wordings (search, send, go, next, previous, done) | 21 |
| Toolbar chrome | Toolbox, back-to-keys, show-tools, emoji shortcut, close search, incognito badge, power saving badge | 7 |
| Emoji tabs | Search, recent, most used, and one per emoji category | 13 |
The tools group grows on its own as new tools ship, since it's generated from the app's tool list rather than hand-maintained. A pack doesn't have to cover every slot: anything it leaves out keeps the app's built-in icon, so a two-icon pack that only reskins Shift and Enter is a completely normal thing to install.
One thing icon packs deliberately don't touch: the per-key glyphs a language layout author drew into that layout. Those belong to the layout, not to the app's chrome, so swapping icon packs never changes how a layout's own keys look.
**Colour.** An icon that sets no colour of its own (`none`, `currentColor`, or nothing at all) is drawn in the current theme's colour, and picks up that tool's own accent colour if **Colorful tool icons** (under Tools) is on. An icon that sets real colours draws exactly as authored and stops following the theme. Most packs should lean on the first behaviour; only go colourful on purpose.
**Vector or picture.** An icon can be an SVG or a picture: PNG, WebP, JPEG or GIF (issue #504). A vector is the better of the two where you have one: it scales to any key height, recolours cleanly and costs a few hundred bytes. A picture is what you have when you drew the icon in anything but a vector editor, and it is the only form a [Gboard theme's own key glyphs](https://wmkeyboard.pages.dev/reference/importing-other-keyboards/#theme-glyphs-and-icon-packs) ever come in. The two behave the same everywhere else in this page, including which slot they fill and whether they follow the theme's colour; a picture counts as "sets no colour of its own" when every visible pixel in it is the same colour, which is what a glyph drawn on a transparent background looks like. A picture may be up to 512 KB and 2048 px on its longest edge; an SVG may be up to 256 KB.
A pack decides which of the two a file is by reading its first bytes, not by its name, so an SVG in a file called `.png` is stored and drawn as the SVG it is.
SVGs are read by a small built-in parser rather than a full SVG engine. It supports paths, basic shapes, and simple color and transform attributes. Gradients, filters, embedded images, and `