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 OsdkProvider2
  • Adding the CSS imports

Table of Contents

Import

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

Basic Usage

Inline Picker

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

import { CbacPicker } from "@osdk/cbac-components/experimental";
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/cbac-components/experimental";
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

PropTypeRequiredDefaultDescription
initialMarkingIdsstring[]No[]Initial set of selected marking IDs
onChange(markingIds: string[]) => voidYes-Called when the selection changes
readOnlybooleanNofalseDisables marking toggle interactions
classNamestringNo-CSS 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).

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
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/cbac-components/experimental";
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/cbac-components/experimental";
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/cbac-components/experimental";

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/cbac-components/experimental";
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/cbac-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 OsdkProvider2 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/cbac-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/cbac-components/experimental
  • Ensure your @osdk/react and @osdk/react-components peer dependencies are installed

Additional Resources