Every control,
under control.
Feature flags, access controls, audit logs, error logsand user analytics. Composable, and inside your running product.Anyone on your team can read it. An engineer installs it once.
Click a control — the panel answers for it.Adding controls to one button, in the order they run.
Orders
Click any control
2 of 3 controls are governed by something.
Counted for this screen. It moves as you walk the product.
Fulfillment link
- Address
- chrome.fulfillment-link
- Operation
- order.fulfill
You hold the fulfillment desk, so you may open it. Tomas Reyes and June Alcott do too.
Export CSV
- Address
- admin.orders.export
- Operation
- customer.export
Exporting customers is the store manager’s. You do not hold that seat.
Refund
- Address
- admin.orders.refund
Nothing governs this control — no access rule, no flag, no recorded use.
Anyone who reaches this screen can refund any order, and nothing will record that they did.
Restrict to
Written from here. No deploy.
Refund
- Address
- admin.orders.refund
- Operation
- order.refund
Now governed. Every refund from here on is recorded, refusals included.
Refund
order.refund
Controls
runs top to bottomInstant refunds
Fulfillment desk · written from this panel
Add one, or add all of them. Here, or to every control at once.
Refund
order.refund
A refusal tells somebody the feature exists and is not theirs. Off tells them nothing, because there is nothing to tell.
Refund
order.refund
Refused — and written down. Somebody who could not say who they were tried to refund an order is the most interesting line a log can hold.
The button on the left answers with the panel. Neither of them had to be told twice.
Audit
Usage · 7d
128
times · 14 people
Errors · 7d
1
gateway timeout
Nobody instrumented a button. Compliance, product and engineering are reading one act three ways.
Open the panel on a screen you already shipped. It counts what governs each control in front of you — two of these three.
Ask a control who may press it. Exporting customers is the store manager’s, and the answer is for whoever is actually looking.
Refund answers with nothing at all. No rule, no flag, no record — anyone who reaches this screen can refund any order.
So restrict it from where you found it. The padlock lands on the button. No deploy, and every refund from here is recorded — refusals included.
Now stack the next control on the same button. They run in an order, and the flag runs first — before may you there is whether this exists for you at all.
Turn it off and the button is simply not there. No 403 to explain, no dead control to file a ticket about. Off means absent — which is exactly why nothing below the flag needs to run.
Back on, and the rule you wrote decides. Ask the same screen as somebody who cannot say who they are. The panel answers for them, and so does the button.
And all three of those wrote themselves down. The flag going off, the attempt that was refused, the refund that went through. One act, three readers: audit, usage, errors.
Yours to poke at. Click any control and the panel answers for it — the story picks back up whenever you press play.
Two modes, one panel
Read the answer. Then change it.
Same install, same addresses, same panel. The second mode only works because the first one gave every control a name.
Five features, one control
Everything you already buy separately,answered in one place.
Access, feature flags, usage, audit and errors are five readings of the same act. The Overlay puts them on the control itself, rather than in five consoles that have never rendered your product.
It replaces the basics of a tool in each of these categories. Not the deep end of any one of them, and that is the trade: one answer per control, in the product, instead of five subscriptions and five tabs.
Who may press this?
Replaces the basics of Permit.io, Cerbos, or the roles table you wrote yourself.
Ask it standing on the button, and get an answer for whoever is actually looking. Change it here too — name the seats that may perform the act, or detach the rule entirely.
The subject is the act, not the button. Two controls that refund an order cannot disagree about who may refund one.
View as
Is this on, and for whom?
Replaces the basics of LaunchDarkly, Statsig, Unleash.
Flip a feature from inside the product, on the control it affects — no hunting forrefund_v2_enabled in a list and hoping it still means this button.
A new flag arrives proposed and governs nothing until somebody with the authority puts it in force. Writing a rule and making it decide are separate grants.
Policies · 2 attached
Governs nothing until somebody ratifies it.
Is anyone actually using it?
Replaces the basics of Amplitude, Mixpanel, PostHog.
How often this control has been used and by how many people, on a window the panel states rather than assumes. A control nothing has happened to has no Used row at all, rather than a row of zeroes.
And it is read, never collected — no event taxonomy to maintain, and no handlers attached to buttons you own.
128
times
14
people
The window is stated, not assumed.
Who did it, and who was stopped?
Replaces the basics of WorkOS Audit Logs, or the events table nobody reads.
Everything that has happened to this control, newest first, with the refusals drawn as refusals. Not a separate export you reconcile later — the same reading, on the same button.
Somebody who could not say who they were tried to refund an order is the most interesting line a log can hold, so an unidentified attempt is recorded like any other rather than dropped for want of a name.
Activity
And when it breaks, which control was it?
Replaces the basics of the Sentry breadcrumb that says a button was clicked.
A stack trace names a function. This names the act somebody was trying to perform, on the control they were standing on — which is the fact a support conversation actually starts from.
It is the outermost of the five, so it catches a failure in the checks themselves. A control whose access rule throws is a control that is broken, not a control that said no, and those two must never look alike.
order.refund · #4412 · Priya Raman
Refused is not failed. A refusal is in the audit log; this is the other list, and keeping them apart is the difference between a permissions bug and a working rule.
Five controls, one order
Take one control, or all five.On one button, or on all of them.
The panel that reads your controls is also where you change them. Add a flag to a single button, or wrap every control in your product in all five at once — and either way you write it from inside the running product rather than from a console that has never rendered it.
Nothing is all-or-nothing.
A provider at the root takes the controls you want everywhere. A wrapper on one control takes the ones you want just there. A team that only wants feature flags on one button installs feature flags on one button, and nothing else changes.
Which means the first install is never a migration. It is one control, one flag, and a coverage number that was zero and is now one.
// everywhere
<DnaProvider controls={['flags', 'access', 'audit']}>
// ...or just here
<Dna.Control op="order.refund" flag="instant-refunds">
<RefundButton />
</Dna.Control>
</DnaProvider>The same five names on the API and the agent, so a team moves between surfaces without relearning anything.
Ask the flag before you ask the rule.
A control that is off for you is absent, not refused — so the flag runs first, and nothing under it runs at all. That is a security property before it is a performance one: a 403 tells somebody the feature exists and is not theirs, and off tells them nothing, because there is nothing to tell.
The three recording controls wrap the two deciding ones, which is the only arrangement where a refusal is itself auditable and a failure inside the checks still gets caught.
Wraps everything, including the controls themselves
Opens on the attempt, closes on the outcome
Records the intent, sealed with what happened
Does this exist for you at all?
May you?
No deploy, and no second tab.
Restrict an act to a seat, flip a flag, detach a rule — standing on the control it affects, in staging or in production. Nobody has to find refund_v2_enabled in a list and hope it still means this button.
The subject is the act, not the button. Write the rule once and every control that refunds an order is holding it, including the ones you have not found yet.
Restrict to
Written from here. No deploy, and every refusal from here on is recorded with it.
Writing a rule and making it decide are two grants.
Anything written here arrives proposed and governs nothing until somebody holdingratify puts it in force. So a panel that anyone on the team can open is not a panel anyone on the team can change your product with.
Which is also the answer to the obvious objection about a governance tool that lives in production: reading is free and unlimited, and authority is the thing we charge for and the thing we gate.
June Alcott · 2 minutes ago · governs nothing yet
- Proposed · anybody who may read the panel
- In force · somebody holding ratify
Every proposal, and every ratification, is itself an audited act.
You do not have to replace what already works.
Start with the controls that have no governance at all, which is most of them, and leave your flag vendor and your roles table alone. Teams that later consolidate do it because they wanted one answer per control, not because we required it.
Reading is free and unlimited on every plan. Writing needs a seat — see theladder.
Partners
Working with design partners across three domains
We are looking for design partners we can be genuinely useful to.
Installing it
One call. That is the whole install.
No provider to mount, no components to adopt, no concepts to learn first.
// vite.config.ts — the whole installation
import { dna } from "@dna/react/vite";
export default defineConfig({
plugins: [dna({ host: "app-billing", key: "pk_live_…" }), react()]
});That one call stamps every element with an address, writes the overlay’s script tag into your document, publishes the build manifest if your pipeline holds a key, and prints coverage at the end of the build.
// app/layout.tsx — the whole installation
import { DnaOverlay } from "@dna/react";
<body>
{children}
<DnaOverlay dnaKey={process.env.NEXT_PUBLIC_DNA_KEY ?? ""} />
</body># after the build, in CI
- run: npx dna publish app
env: { DNA_HOST: app-billing, DNA_API_KEY: "${{ secrets.DNA_KEY }}" }No next.config entry, no loader, no SWC plugin, and no opinion about Turbopack. Declared addresses are literal props, and React forwards them to the DOM unaided — so they arrive with no build integration at all. One answer covers the App Router and the Pages Router, Turbopack and webpack, because none of them is involved.
On anything else, npx dna publish reads your source directly — nothing about it is bundler-specific.
What that gets you
Addressable
Every control carries a stable address. You can point at a button and be understood — in a rule, in a ticket, in a conversation — instead of guessing which flag key somebody meant.
Bindable
An address reaches an operation — the act the control performs. Now the button has a subject, and two buttons that do the same thing are known to do the same thing.
Governable
Rules attach to the act. Permissions — who may perform it. Release — whether it is on at all. Usage — how often, by how many people. Audit — everything that has happened, refusals included, and who was refused.
@dna/react is in private beta.It is not on npm yet. Early-access customers get the package and half an hour of setup with us.
Seven things it will never do
It arrives in a product nobody asked it about. Every one of these is enforced, not promised.
Your markup is unchanged
Rendered output is byte-identical apart from data-dna attributes, and removing the install restores every file byte-for-byte.
It never listens to your buttons
Usage is read from occurrences the platform already writes — never collected by attaching handlers to controls you own.
It cannot fail your build
Unreachable, slow, refusing, or answering a proxy’s HTML — publishing resolves and reports. Your key is never printed, including in transport errors.
The key in your markup is powerless
It identifies the application and nothing more. Which environment this is, whether the overlay may run here, and what the viewer may do are all the server’s answer.
Nothing publishes without a key
A developer’s local build, and a pull request from a fork where the secret is unavailable, send no request at all rather than one that would be refused.
Refusals are enforced twice
The panel hides what you may not do; the server checks again and names which gate stopped a request forged past it. A rule that only exists on the client is not a rule.
Removal is one call
Take the install out and the page is exactly what it was — container, watchers, outlines and both sessionStorage entries, gone.
The same install is what makes the other mode possible: once every control has an address, the panel can attach a rule to one of them, or all five controls to all of them, without another deploy.See Govern mode →
Also under Govern it
A button is not the only way into your business.
The same operation a control carries is the one an endpoint performs, and most of what callsPOST /payments is not a person at a screen. API Operations reads your OpenAPI document and answers the same seven questions at your API edge — and it can tell you where you stand before you install anything, because your spec is an adapter you already wrote.
Included in every paid plan.
UI Operations is a surface on your model, not a product beside it, so it is never sold separately. Reading the Overlay is free and unlimited on all plans; we charge for authority, not for looking.
FAQ
Do I have to take all five controls?
No — that is the point of them being composable. A provider at the root takes whichever controls you want everywhere; a wrapper takes the ones you want on a single control. Feature flags on one button is a complete, useful install.
Do I have to replace my feature flags?
No. Start with the controls that have no governance at all, which is most of them, and leave what works alone. Teams that later consolidate do it because they wanted one answer per control, not because we required it.
What does it do to my bundle?
Almost nothing. The overlay is fetched separately rather than bundled, and can be served only where you want it. What lands in your own output is attributes, and your markup is byte-identical apart from those.
Can anyone who loads my page see our rules?
No. The publishable key carries no capabilities at all. Until somebody identifies, the panel can inspect and nothing more, and names of people are only ever returned to a caller who may govern. A roster served to anyone who views source would be a staff directory.
Does it work if my app is not React?
Vite and Next.js have a one-step install today. Beyond those, npx dna publish reads your source directly. Nothing about it is bundler-specific, so you get addresses and governance without a framework integration at all. Tell us your stack on the form; which one we deepen next is decided by who is asking.
What happens when I turn it off?
The page is exactly what it was. stop() removes the container, the watchers, the outlines and both sessionStorage entries, and taking the install out restores every file byte-for-byte.
Is this ready for production?
It runs against a live stack and every screenshot on this page was taken walking a real script. It is early access rather than generally available: the package is private, we set you up by hand, and we are looking for design partners we can be genuinely useful to.
See it on your own controls.
Install it, open the panel, and read what governs each button in your product. The answer isnothing more often than most teams expect, and that is usually a more interesting conversation than the install was.
Got it, thank you.
We read every one of these by hand. Expect a reply from a person, usually within a day.