Visibility Picker

Makes widening who can see something an explicit step that shows who gains access; narrowing stays instant.

StopsMade a private doc public by accident

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/visibility-picker.json

The files land in your project. They're yours to edit.

Usage

visibility-picker-usage.tsx
import { VisibilityPicker, type VisibilityOption } from "@/components/ui/visibility-picker"

return <VisibilityPicker options={options} defaultValue="team" resourceName="Q3 roadmap" onValueChange={save} />

What it catches

  • Widening to a riskier level or a larger audience, held for confirmation
  • Who gains access, in plain words and counts
  • Levels blocked by policy, with the reason
  • Failed saves, reverted with a notice

Props

VisibilityPicker props
PropTypeDefault
options* (required)

Sharing levels. They are shown ordered by `risk`.

VisibilityOption[]—
value

The applied level (controlled).

string—
defaultValue

The starting level when uncontrolled. Defaults to the least risky option.

string—
onValueChange

Called once a level is applied: instantly when narrowing, after confirming when widening. Return a promise to show saving; a rejection puts the old level back and says so.

(value: string) => void | Promise<void>—
resourceName

Name of the thing being shared, used in the warning.

string"this"
label

Visible label of the group.

string"Who can access"
cancelLabel

Text of the button that cancels a widening. Defaults to "Keep <current level>".

string—
confirmLabel

Text of the button that confirms a widening. Defaults to "Yes, change to <level>".

string—
warningText

The widening warning. Override to translate. `more` is how many more people, if known.

(info: { resource: string; from: string; to: string; more?: number; unbounded: boolean }) => React.ReactNodedefaultWarning
savingText

Shown while an async `onValueChange` runs; `{level}` is filled in.

string"Saving {level}…"
failedText

Shown when it rejects; `{level}` is the level that was kept.

string"Couldn't change who can access this. It's still {level}."

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Levels are a roving-tabindex radiogroup; arrow keys move focus without applying a widening
  • Risk level stated as sr-only text; blocked levels use aria-disabled
  • Widening warning linked via aria-describedby in a polite live region; Escape cancels it

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Enforce sharing level and admin policy on the server for every access check.

  • Audience counts come from your options
  • The confirm is a UX step; access must be enforced server-side

What it stops

Made a private doc public by accident

Also known as

visibility · public · private · sharing · privacy settings · who can see · audience · repo visibility

Dependencies

Pairs well with