Open-source project
cboard-org/cboard avatar
cboard-org/cboard

cboard ships an ad component, a payment component and a cloud speech SDK in the same bundle as the symbol board

Augmentative and Alternative Communication (AAC) system with text-to-speech for the browser

759 stars286 forksJavaScriptGPL-3.0

At a glance

What is it?
An augmentative and alternative communication board that runs in the browser, built from Create React App and served as a static bundle. The document claims two different language counts, the container listens on a port the run command does not map, and the dependency list is far larger than the feature set implies.
Who is it for?
cboard is a serious piece of work for the population it serves, and two of its numbers deserve checking before you deploy it for a child. First, the speech path: the document says the app speaks through the browser's Speech Synthesis API, while the dependency list carries a Microsoft Cognitive Services speech SDK, and nothing in the file says which one runs or whether audio can leave the device.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The document gives two different language counts

The introduction says Cboard is available in 40 languages, with support varying by platform between Android, iOS and Windows. The translations section, three paragraphs later, says it is available in more than 50. Both are in the same file, both are current, and nothing reconciles them.

The parenthetical about platform variation is the more useful half of that sentence. A language being listed does not mean it works on a phone, because the platform determines which speech voices exist in the operating system's synthesizer. A board whose labels are translated into a language the device cannot pronounce aloud is a board that displays text and stays silent, which for this audience is the failure that matters most.

The underlying capability is the browser's own. Speech is produced by the Speech Synthesis API when a symbol is clicked, so there is no text-to-speech server in the described path and nothing to configure for it. The content side is separate: thousands of symbols drawn from the most popular AAC symbol libraries are available when building a board, and a separate document in the repository covers how new symbol sets are integrated.

So the count that matters is not the total number of translations but the number of languages that are both translated and pronounceable on the platform you deploy to, and the file does not give a way to ask that question.

Almost every language is waiting for a proofreader

The translations section is unusually blunt about the state of its own localisation. Languages are available in bulk, but almost all of them were machine translated and have never been reviewed by a human speaker. There are no official translators on the team. For most languages, the first person to proofread them is a volunteer from the community or a specialist chosen for that language.

The reasoning given is worth repeating because it explains the design: machine translation gets the words roughly right and the meaning often wrong, and for a child using a board to communicate the difference between a board that makes sense and one that does not is the whole product.

The workflow that fixes this runs on Crowdin and needs no code. Join the project, claim the language on issue 89 so two people do not review the same strings, translate the empty strings and proofread the machine-translated ones, then approve each string as you go. Approval is the gate: only proofreaders can approve, and approved strings are the ones that ship. Work is pulled into the codebase periodically by a maintainer through `npm run translations:pull`, which updates each language file and the central `cboard.json`, and which needs the Crowdin API key available in the `.private` config.

The consequence for a user is that language availability is a moving target. A language can appear in the interface as an option before a single string in it has been approved.

The container serves on 80 and the run command maps 3000

The image is two stages: a Node build stage and an nginx stage that receives the compiled bundle.

dockerfile
FROM nginx:stable-alpine
COPY ./rootfs/ /
COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

The declared port is 80. The documented run command maps a different one:

bash
make run   # docker run -p 5000:3000 cboard/cboard:latest

Mapping host 5000 to container port 3000 only works if something in the image is listening on 3000, and the only candidate is an nginx configuration that the repository's `rootfs` directory could be supplying, since the Dockerfile copies that directory over the whole image root before the bundle. Whether it does is not settled by the files at hand. What is settled is that the exposed port and the mapped port disagree, so the first thing to check when `make run` produces nothing is which port nginx actually bound.

The image name disagrees as well. `make image` builds `cboard/cboard`, while the text says the image is tagged `cboard:latest`. `make run` uses the former, so the prose is wrong rather than the Makefile, but anyone following the text and then running the target by hand will fail to find the image.

The build asks for 7168 MB of heap, and there is a second build for Cordova

The build stage pins a Node version, disables the git hooks for the install, and then raises the heap ceiling for the actual build:

dockerfile
FROM node:22.23.2 AS build-deps
RUN HUSKY=0 npm ci
RUN NODE_OPTIONS="--max-old-space-size=7168" npm run build

Seven gigabytes of old-space is not a casual number. It is what the bundler needs for a dependency graph of this size, and it is the practical constraint on anyone building this on a small machine or a memory-capped CI runner. The `.nvmrc` file at the repository root pins the interpreter for local work, and the package manifest also names a package manager version, so the node version is specified in three places.

There is a second, deliberately different build for native packaging. `npm run build-cordova-debug` produces a non-minified build for debugging inside Cordova, and it goes through craco with its own configuration file so the webpack behaviour can be changed without ejecting the React build. Packaging that application is handled in a separate repository, cboard-org/ccboard, which keeps the mobile wrapper out of this codebase.

For day-to-day work the three scripts are the ordinary React ones: `npm start` serves the app in development mode at http://localhost:3000 with reload on edit and build errors and lint warnings in the console, `npm test` opens the test watcher which by default runs only tests related to files changed since the last commit, and `npm run build` emits a minified bundle with hashed filenames into a build folder, including a service worker so the app loads from cache on later visits.

Secrets are unlocked with a key one person holds, and are not needed to run the app

Some external services need API keys, and to keep them out of a public repository the project encrypts them into GPG files, `env/local-private.gpg` and `env/prod-private.gpg`. To read one you request the `ENCRYPTION_KEY` from a named maintainer and run `ENCRYPTION_KEY={key-goes-here} npm run decrypt:local`, or the prod equivalent, which produces `.private/local.js` with the secrets in plain text where the scripts can read them. Changes go back the other way with `npm run encrypt:local`.

Two properties are worth noting. The encryption key is not derivable from anything in the repository; it is held by one person, so that person's availability is a dependency for anyone changing a secret. And the decrypted file is plain text in the working tree, which is why the instruction that the `.private` directory must never be committed exists at all. That instruction is enforced by convention rather than by anything described here, and the repository does carry a pre-commit hook setup, so the guard is probably a hook rather than a server-side rule.

The reassuring sentence is the last one in that section: these keys and secrets are not required to run or develop Cboard. They are used by scripts some team members run. The Crowdin key needed by `npm run translations:pull` is one of them, which is why a contributor translating strings does not need the key at all.

A cloud speech SDK sits beside a promise about the browser's own speech

The described speech mechanism is local. The app uses the browser's Speech Synthesis API to generate speech when a symbol is clicked, and the privacy story that follows from that is that no text-to-speech service is involved.

The dependency list tells a different story. It includes `microsoft-cognitiveservices-speech-sdk` at a 1.x range, which is a client for a cloud speech service, alongside the browser-side libraries. Nothing in the document says where that SDK is used, whether it is behind a setting, whether it needs a key to function, or whether a family using this board can end up sending text to a hosted endpoint.

That gap is the kind worth closing before deployment rather than after. Speech is the entire point of a communication board for someone who cannot type fluently, and the difference between synthesising on the device and sending a phrase to a service is the difference between a local tool and one that transmits what a child said. It is entirely possible the SDK is used only for an optional voice, or only in the Cordova build, or only for a feature not reachable in the web bundle. The file does not say, and the honest reading is that the question is open rather than answered either way.

The dependency list also carries image resizing, DOM to image capture, zip handling, file saving and a MediaPipe vision package, which together describe the board export and camera-adjacent features without the document explaining them.

Advertising, payment and analytics packages ship alongside the symbol board

The dependency list is long, and four entries in it are not communication features. There is an ad component, a payment component, an application insights web package, and a beacon integration for Google analytics. All four are bundled into the same client build as the symbol board.

For a general web application that is a business decision. For an AAC tool aimed at children with autism and cerebral palsy it carries obligations the repository does not discuss. The people using it are often children, often cannot read the interface themselves, and often cannot tell an advertisement apart from a symbol. A board whose layout is chosen by the person setting it up is the mitigation, and nothing in the document says whether advertising surfaces are disabled by default, whether the payment component is used for donations, or whether either can be removed without a rebuild.

The three analytics-adjacent packages are separate from the ad and payment ones, and analytics in a children's application is a different question again: what is measured, where it goes, and whether a parent knows. None of that is answered in the file.

A community-funded public good is named at the top of the document, along with a Crowdin project for translations and a Discord for collaboration. The funding route and the commercial dependencies can coexist; what the file does not do is say which parts of the product they pay for, which is the information a deploying clinic would need.

Three test harnesses and a repository that documents its own symbol integration

The test story is plural. There is an interactive watcher wired to the Create React App test setup that runs only what changed since the last commit, a Playwright configuration at the repository root, a BrowserStack configuration beside it, and a mocks directory. BrowserStack is the one that implies a hosted device lab, which is the standard way an AAC board gets checked against the speech synthesizers of phones it will never be developed on, given that platform voice support is exactly what varies.

Several other artefacts sit at the root that describe how the project is run rather than what it does: a changelog, a code of conduct, a funding manifest, a release roadmap, a code quality configuration for a tool named in the scripts, a Prettier and ESLint pair, an editor configuration, and a husky directory for commit hooks. There is also an agents directory, a Claude configuration directory, and a skills lock file, so the repository follows an agent-assisted development convention that predates most of its audience.

One document stands out as the integration contract: CBOARD_SYMBOLS_INTEGRATION.md at the root, describing how external symbol sets are brought in. That is the file a maintainer of a symbol library needs, and its presence at the root rather than inside the wiki is a deliberate choice about who the file is for.

The symbols section of the document credits the sources it draws from, naming Mulberry and ARASAAC among them, with the list continuing past where the file ends.

Editorial conclusion

cboard is a serious piece of work for the population it serves, and two of its numbers deserve checking before you deploy it for a child. First, the speech path: the document says the app speaks through the browser's Speech Synthesis API, while the dependency list carries a Microsoft Cognitive Services speech SDK, and nothing in the file says which one runs or whether audio can leave the device. Second, what else is in the bundle: an ad component, a payment component and two analytics integrations sit in the same dependency list as the symbol board, and a family AAC tool has different obligations to its users than a game does. Beyond that, expect an ordinary React application: `npm start` for development, `npm run build` for a static site, a Makefile pair for a container, and a translation workflow that only reaches users after a volunteer proofreader approves each string.

Frequently asked questions

What is the CBoard AAC app?

An augmentative and alternative communication web application for people with speech and language impairments, including autism and cerebral palsy. It runs in the browser and generates speech with the browser's Speech Synthesis API when a symbol is clicked, with boards built from thousands of symbols drawn from popular AAC symbol libraries.

what is cboard

It is an open-source AAC communication board. The hosted app is at app.cboard.io and the code lives in this repository under GPL-3.0. A production build compiles to a static bundle served by any web server, and a container image is built from a Dockerfile that pairs a Node build stage with an nginx stage.

How do I run cboard locally?

`npm start` runs the app in development mode at http://localhost:3000, reloading on edit and showing build errors and lint warnings in the console. `npm test` opens the test watcher, which by default runs tests related to files changed since the last commit. `npm run build` writes a minified, hash-named bundle with a service worker into a build folder.

Does cboard need API keys to run or develop?

No. The document states that the encrypted keys and secrets are not required to run or develop Cboard and are used by scripts some team members run. They live in env/local-private.gpg and env/prod-private.gpg, are unlocked with an ENCRYPTION_KEY held by one maintainer, and decrypt into a plain text file under .private.

How does cboard handle translations, and can I help?

Translation happens on Crowdin with no installation and no code. Almost all existing languages are machine translated and unreviewed, so the useful work is proofreading and approving strings, which only proofreaders can do and only approved strings ship. Claim a language on issue 89 first, then ask there for proofreader rights.

How many languages does cboard support?

The document gives two figures. The introduction says 40 languages with support varying by platform between Android, iOS and Windows, while the translations section says more than 50. Either way, a language being listed does not guarantee the device can pronounce it, since voices come from the platform synthesiser.

Official sources

  1. cboard-org/cboard on GitHub
  2. License: GPL-3.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cboard-org-cboard.svg)](https://hysenlabs.com/projects/cboard-org-cboard)