Skip to main content

CbacPicker

Components for managing classification-based access control (CBAC) markings. CBAC markings control who can access data — each piece of data can be tagged with markings from different categories, and the combination determines its access restrictions.

The picker lets users select markings grouped by category. Some categories are disjunctive (pick exactly one, like a sensitivity level) and others are conjunctive (pick any combination, like access groups). The server enforces which combinations are valid, which markings are implied by others, and which are disallowed.

Prerequisites​

Before using CBAC components, make sure you have completed the library setup described in the README, including:

  • Installing the required dependencies
  • Wrapping your app with OsdkProvider
  • Adding the CSS imports

Table of Contents​

Import​

import {
BaseCbacBanner,
BaseCbacPicker,
BaseCbacPickerDialog,
CbacPicker,
CbacPickerDialog,
} from "@osdk/react-components/experimental/cbac-picker";

Basic Usage​

Inline Picker​

The simplest way to use the CBAC picker is inline with CbacPicker:

import { CbacPicker } from "@osdk/react-components/experimental/cbac-picker";
import { useState } from "react";

function ClassificationForm() {
const [markingIds, setMarkingIds] = useState<string[]>([]);

return (
<CbacPicker
initialMarkingIds={markingIds}
onChange={setMarkingIds}
/>
);
}

This renders a marking picker that fetches categories and markings via the OSDK, displays them grouped by category, and shows a classification banner. The server enforces marking restrictions automatically — the picker reflects which markings are implied, disallowed, or required based on the current selection.

Picker in a Dialog​

For modal workflows, use CbacPickerDialog:

import { CbacPickerDialog } from "@osdk/react-components/experimental/cbac-picker";
import { useState } from "react";

function ClassificationDialog() {
const [isOpen, setIsOpen] = useState(false);
const [markingIds, setMarkingIds] = useState<string[]>([]);

const handleConfirm = (newMarkingIds: string[]) => {
setMarkingIds(newMarkingIds);
setIsOpen(false);
};

return (
<>
<button onClick={() => setIsOpen(true)}>
Set Classification
</button>
<CbacPickerDialog
isOpen={isOpen}
onOpenChange={setIsOpen}
onConfirm={handleConfirm}
initialMarkingIds={markingIds}
/>
</>
);
}

Props Reference​

CbacPicker​

NameTypeDescription
initialMarkingIdsstring[]Initial set of selected marking IDs. Defaults to [].
onChange(markingIds: string[]) => voidRequired. Called when the selection changes.
maxClassificationConstraintMaxClassificationConstraintOptional constraint capping the maximum classification a user may select. When set, the picker surfaces a callout if the selection exceeds it.
readOnlybooleanDisables marking toggle interactions. Defaults to false.
classNamestringCSS class for the picker container.

CbacPickerDialog​

PropTypeRequiredDefaultDescription
isOpenbooleanYes-Controls dialog visibility
onOpenChange(open: boolean) => voidYes-Called when dialog open state changes
onConfirm(markingIds: string[]) => voidYes-Called with the selected marking IDs on confirm
initialMarkingIdsstring[]No[]Initial set of selected marking IDs

The dialog title automatically adjusts: "Add classification" when no initial markings are provided, "Edit classification" when editing existing markings. The confirm button is disabled with a tooltip when the selection is invalid (e.g., missing required markings).

CbacBanner​

Fetches banner data for the given marking IDs and renders the resolved classification banner.

PropTypeRequiredDefaultDescription
markingIdsstring[]Yes-Marking IDs to resolve into a classification banner
onClick() => voidNo-Makes the banner clickable
onDismiss() => voidNo-Shows a dismiss button
isLoadingbooleanNofalseForces the loading skeleton. OR'd with the query's own loading state, so the skeleton also shows automatically while banner data is being fetched.
classNamestringNo-CSS class for the banner

Base Components​

Most users should use CbacPicker or CbacPickerDialog above — they handle all data fetching automatically.

The base components below are for advanced use cases where you need to supply your own marking data (e.g., you already have the data from a different source, or you're building a custom integration). They accept primitive props and have no OSDK dependency.

BaseCbacPicker​

PropTypeRequiredDefaultDescription
categoriesCategoryMarkingGroup[]Yes-Marking categories with their markings
markingStatesMap<string, MarkingSelectionState>Yes-State for each marking (SELECTED, IMPLIED, etc.)
bannerCbacBannerDataNo-Banner data (classification string, colors)
onMarkingToggle(markingId: string) => voidYes-Called when a marking button is clicked
onDismissBanner() => voidNo-Called when the banner dismiss button is clicked
showInfoBannerbooleanNo-Show the "implied markings" info banner
requiredMarkingGroupsReadonlyArray<RequiredMarkingGroup>No-Groups of markings that must be selected together
isValidbooleanNo-Whether the current selection is valid
readOnlybooleanNo-Disable marking interactions
isLoadingbooleanNo-Show loading skeleton
errorErrorNo-Show error message
classNamestringNo-CSS class for the container

BaseCbacBanner​

PropTypeRequiredDefaultDescription
classificationStringstringYes-Text displayed in the banner
textColorstringYes-Banner text color
backgroundColorsstring[]Yes-Background colors (rendered as gradient if multiple)
onClick() => voidNo-Makes banner clickable
onDismiss() => voidNo-Shows a dismiss button
isLoadingbooleanNofalseRenders a same-size loading skeleton instead of text
classNamestringNo-CSS class for the banner

BaseCbacPickerDialog​

Extends all BaseCbacPickerProps plus:

PropTypeRequiredDefaultDescription
isOpenbooleanYes-Controls dialog visibility
onOpenChange(open: boolean) => voidYes-Called when dialog open state changes
onConfirm() => voidYes-Called when confirm button is clicked
onCancel() => voidYes-Called when cancel button is clicked
titlestringNo"Select classification"Dialog title
submitDisabledReasonstringNo-Tooltip on disabled submit button

Types Reference​

MarkingSelectionState​

type MarkingSelectionState =
| "NONE"
| "SELECTED"
| "IMPLIED"
| "DISALLOWED"
| "IMPLIED_DISALLOWED";
StateDescription
"NONE"Default state — marking is available but not selected
"SELECTED"Marking is explicitly selected by the user
"IMPLIED"Marking is automatically included due to other selected markings
"DISALLOWED"Marking cannot be selected due to restrictions
"IMPLIED_DISALLOWED"Marking is both implied and disallowed by the current selection

CbacBannerData​

interface CbacBannerData {
classificationString: string;
textColor: string;
backgroundColors: string[];
markingIds: string[];
}

Returned by the useCbacBanner hook. Contains the resolved classification string and the display colors for the banner.

PickerMarkingCategory​

interface PickerMarkingCategory {
id: string;
name: string;
description: string;
categoryType: "CONJUNCTIVE" | "DISJUNCTIVE";
markingType: "MANDATORY" | "CBAC";
}
FieldDescription
categoryType"CONJUNCTIVE" = multiple markings can be selected (checkbox-style). "DISJUNCTIVE" = only one marking per category (radio-style).
markingType"MANDATORY" = always present on all objects. "CBAC" = user-selectable.

PickerMarking​

interface PickerMarking {
id: string;
categoryId: string;
name: string;
description?: string;
}

CategoryMarkingGroup​

interface CategoryMarkingGroup {
category: PickerMarkingCategory;
markings: PickerMarking[];
}

A category paired with all markings that belong to it.

RequiredMarkingGroup​

interface RequiredMarkingGroup {
markingNames: string[];
}

A group of marking names that must be selected together for the classification to be valid.

Selection Logic Utilities​

These pure functions handle marking selection logic without any React or OSDK dependencies. Use them when building custom picker implementations.

toggleMarking​

function toggleMarking(
markingId: string,
currentSelection: string[],
categories: CategoryMarkingGroup[]
): string[];

Toggles a marking in the selection, respecting category type:

  • Conjunctive categories: adds or removes the marking (checkbox behavior)
  • Disjunctive categories: replaces the existing selection in that category (radio behavior)

computeMarkingStates​

function computeMarkingStates(
selectedIds: string[],
impliedIds: string[],
disallowedIds: string[]
): Map<string, MarkingSelectionState>;

Computes the display state for each marking based on the current selection, implied markings, and disallowed markings.

groupMarkingsByCategory​

function groupMarkingsByCategory(
markings: PickerMarking[],
categories: PickerMarkingCategory[]
): CategoryMarkingGroup[];

Groups a flat list of markings into CategoryMarkingGroup arrays, ordered by category.

Examples​

Example 1: Basic Inline Picker​

import { CbacPicker } from "@osdk/react-components/experimental/cbac-picker";
import { useState } from "react";

function ClassificationForm() {
const [markingIds, setMarkingIds] = useState<string[]>([]);

return (
<div>
<h3>Classification</h3>
<CbacPicker
initialMarkingIds={markingIds}
onChange={setMarkingIds}
/>
<p>Selected markings: {markingIds.join(", ") || "None"}</p>
</div>
);
}

Example 2: Picker in a Dialog with Confirmation​

import { CbacPickerDialog } from "@osdk/react-components/experimental/cbac-picker";
import { useState } from "react";

function DocumentClassification() {
const [isOpen, setIsOpen] = useState(false);
const [markingIds, setMarkingIds] = useState<string[]>([]);

const handleConfirm = (newMarkingIds: string[]) => {
setMarkingIds(newMarkingIds);
setIsOpen(false);
// Save to your backend
};

return (
<div>
<button onClick={() => setIsOpen(true)}>
{markingIds.length > 0 ? "Edit Classification" : "Add Classification"}
</button>
<CbacPickerDialog
isOpen={isOpen}
onOpenChange={setIsOpen}
onConfirm={handleConfirm}
initialMarkingIds={markingIds}
/>
</div>
);
}

Example 3: Read-Only Classification Display​

import { CbacPicker } from "@osdk/react-components/experimental/cbac-picker";

interface ClassificationViewerProps {
markingIds: string[];
}

const NOOP = () => {};

function ClassificationViewer({ markingIds }: ClassificationViewerProps) {
return (
<CbacPicker
initialMarkingIds={markingIds}
onChange={NOOP}
readOnly={true}
/>
);
}

Example 4: Custom Data Fetching with BaseCbacPicker​

Use BaseCbacPicker when you want to supply your own marking data instead of fetching from the OSDK:

import {
BaseCbacPicker,
type CategoryMarkingGroup,
computeMarkingStates,
toggleMarking,
} from "@osdk/react-components/experimental/cbac-picker";
import { useCallback, useMemo, useState } from "react";

const CATEGORIES: CategoryMarkingGroup[] = [
{
category: {
id: "cat-1",
name: "Classification Level",
description: "Overall classification",
categoryType: "DISJUNCTIVE",
markingType: "CBAC",
},
markings: [
{ id: "m-low", categoryId: "cat-1", name: "LOW" },
{ id: "m-medium", categoryId: "cat-1", name: "MEDIUM" },
{ id: "m-high", categoryId: "cat-1", name: "HIGH" },
],
},
{
category: {
id: "cat-2",
name: "Access Groups",
description: "Who can see this",
categoryType: "CONJUNCTIVE",
markingType: "CBAC",
},
markings: [
{ id: "m-internal", categoryId: "cat-2", name: "INTERNAL" },
{ id: "m-external", categoryId: "cat-2", name: "EXTERNAL" },
],
},
];

function CustomClassificationPicker() {
const [selectedIds, setSelectedIds] = useState<string[]>([]);

const markingStates = useMemo(
() => computeMarkingStates(selectedIds, [], []),
[selectedIds],
);

const handleToggle = useCallback(
(markingId: string) => {
setSelectedIds((prev) => toggleMarking(markingId, prev, CATEGORIES));
},
[],
);

return (
<BaseCbacPicker
categories={CATEGORIES}
markingStates={markingStates}
onMarkingToggle={handleToggle}
/>
);
}

Architecture​

What the server controls​

The following data is computed server-side and cannot be changed by the frontend:

  • Implied markings — which markings are automatically included based on the current selection
  • Disallowed markings — which markings are blocked based on the current selection
  • Required marking groups — which additional markings must be selected for the classification to be valid
  • Validation (isValid) — whether the current selection satisfies all constraints
  • Banner data — the classification string, text color, and background colors

The frontend handles only UI concerns: toggling selections (respecting conjunctive/disjunctive category types), grouping markings by category for display, and computing visual states (SELECTED, IMPLIED, DISALLOWED) from the server-provided data.

Two-layer architecture​

@osdk/react-components follows the same pattern as @osdk/react-components:

  • OSDK layer (CbacPicker, CbacPickerDialog) — fetches data using @osdk/react hooks, converts it to primitive props, passes to the base layer
  • Base layer (BaseCbacPicker, BaseCbacBanner, BaseCbacPickerDialog) — pure UI with no OSDK dependency, accepts primitive data directly

Troubleshooting​

Picker shows "Loading..." indefinitely​

  • Ensure your app is wrapped with OsdkProvider with a properly configured OSDK client
  • Check that the OSDK client has access to the marking categories and markings APIs
  • Verify network requests in browser DevTools for errors

No markings appear​

  • Confirm that marking categories and markings are configured in your environment
  • Check that the OSDK client's authentication token has the necessary permissions
  • The banner resolves colors from the OSDK. If no marking IDs are provided, or the banner API returns no data, fallback colors are used
  • Verify that the useCbacBanner hook is receiving the correct marking IDs

Validation errors: "Selected markings do not include all required markings"​

  • Some marking combinations require additional markings. The requiredMarkingGroups in the picker state show which markings need to be added
  • The confirm button in CbacPickerDialog is disabled with a tooltip when the selection is invalid

CSS not applied​

  • Ensure you've imported @osdk/react-components/styles.css in your CSS entry file
  • If using CSS layers, verify the layer order includes the cbac styles

Type errors with imports​

  • All components and types should be imported from @osdk/react-components/experimental/cbac-picker
  • Ensure your @osdk/react and @osdk/react-components peer dependencies are installed

Additional Resources​