PrivateBlock
React component that marks a subtree as private. Everything inside is masked in the replay.
Import
import { PrivateBlock } from "@galacha/react";Basic usage
<PrivateBlock>
<CreditCardForm />
</PrivateBlock>All inputs, textareas, and text inside are masked. The recorder serializes the subtree with data-private="true" which the web SDK picks up at capture time.
Block mode
For a stronger guarantee — the subtree is dropped from the DOM snapshot entirely — pass block:
<PrivateBlock block>
<SSNInput />
</PrivateBlock>| Mode | What the dashboard sees |
|---|---|
| default | Input widget visible, value masked with asterisks, element path captured |
block | Empty placeholder, no inner DOM, click events have no target path |
Polymorphic as prop
Render as any HTML element:
<PrivateBlock as="section" className="checkout">
<PaymentForm />
</PrivateBlock>
<PrivateBlock as="td">
<CardLast4 />
</PrivateBlock>
<PrivateBlock as="article" className="sensitive-content">
<MedicalHistory />
</PrivateBlock>Default is div. Any HTML element tag works.
Full props
interface PrivateBlockProps {
as?: keyof JSX.IntrinsicElements;
block?: boolean;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}| Prop | Type | Default | Purpose |
|---|---|---|---|
as | HTML tag | "div" | Render as a different element |
block | boolean | false | Drop subtree instead of masking |
className | string | — | Passthrough |
style | CSSProperties | — | Passthrough |
children | ReactNode | — | Subtree to mask |
Examples
Credit card form
<PrivateBlock>
<label>
Card number
<input value={card} onChange={(e) => setCard(e.target.value)} />
</label>
<label>
CVV
<input type="password" value={cvv} onChange={(e) => setCvv(e.target.value)} />
</label>
<label>
Cardholder name
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
</PrivateBlock>Conditional based on user role
const isAdmin = user?.role === "admin";
{isAdmin ? (
<p>Customer email: {customer.email}</p>
) : (
<PrivateBlock>
<p>Customer email: {customer.email}</p>
</PrivateBlock>
)}Admins can see the email unmasked (because they're already trusted with the data). Regular users have it masked.
Table with mixed-sensitivity columns
<table>
<thead>
<tr>
<th>Order</th>
<th>Customer</th>
<th>Amount</th>
<th>Card</th>
</tr>
</thead>
<tbody>
{orders.map((o) => (
<tr key={o.id}>
<td>{o.id}</td>
<PrivateBlock as="td">{o.customer_email}</PrivateBlock>
<td>{o.amount}</td>
<PrivateBlock as="td" block>{o.card_last_four}</PrivateBlock>
</tr>
))}
</tbody>
</table>Column-level masking. The card column uses block to drop the DOM entirely.
Medical / healthcare
<section>
<h2>Patient summary</h2>
<p>Visit date: {visit.date}</p>
<PrivateBlock block>
<div>
<h3>Diagnosis</h3>
<p>{visit.diagnosis}</p>
<h3>Notes</h3>
<p>{visit.notes}</p>
</div>
</PrivateBlock>
</section>block mode ensures the diagnosis text never enters the DOM snapshot at all — stronger than default masking for HIPAA-adjacent cases.
Under the hood
PrivateBlock renders the element with data-private set. That's the whole mechanism:
// default
<div data-private="true">{children}</div>
// block mode
<div data-private="block">{children}</div>You can achieve the same effect manually:
<div data-private="true">
<CreditCardForm />
</div>The component is just cleaner ergonomics, plus consistent API with @galacha/react-native's <GalachaPrivate>.
React Native equivalent
import { GalachaPrivate } from "@galacha/react-native";
<GalachaPrivate>
<CreditCardForm />
</GalachaPrivate>Different mechanism: RN renders the view as a solid gray fill in captured frames. Full pixel masking.