PrivateBlock

Mark any subtree as private. Everything inside is masked in the session replay.

Web / React / Next.js

Import from @galacha/react:

import { PrivateBlock } from "@galacha/react";
 
<PrivateBlock>
  <CreditCardForm />
</PrivateBlock>

All <input>, <textarea>, <select>, and visible text inside are replaced with asterisks in the captured events. Click positions are captured (you see where the user clicked) but element paths are stripped of identifying text.

React Native

Import from @galacha/react-native:

import { GalachaPrivate } from "@galacha/react-native";
 
<GalachaPrivate>
  <CreditCardForm />
</GalachaPrivate>

The view is rendered as a solid gray block in the captured frame. Full pixel masking — even the widget shape is hidden.

Block mode (stronger)

Pass block to drop the subtree entirely from the DOM snapshot, not just mask its values:

<PrivateBlock block>
  <SSNInput />
</PrivateBlock>
ModeWhat the dashboard sees
defaultInput widget visible, value masked, element path captured
blockEmpty placeholder, no inner DOM, click events have no target

Use block for the most sensitive fields (SSN, bank routing numbers, medical identifiers). Use default mode for everything else.

Polymorphic as prop

Render as any HTML element:

<PrivateBlock as="section" className="checkout">
  <PaymentForm />
</PrivateBlock>
<PrivateBlock as="td">
  <CardLast4 />
</PrivateBlock>

Default is div. Any HTML element works.

Full props

interface PrivateBlockProps {
  as?: keyof JSX.IntrinsicElements;
  block?: boolean;
  className?: string;
  style?: React.CSSProperties;
  children?: React.ReactNode;
}
PropTypeDefaultPurpose
asElement tag"div"Render as a different element
blockbooleanfalseDrop the subtree, not just mask
classNamestringPassthrough
styleCSSPropertiesPassthrough
childrenReactNodeContent to mask

What it does under the hood

PrivateBlock renders a standard React element with data-private="true" (or data-private="block" in block mode). The recorder's selector picks these up and masks them during serialization. That's the whole mechanism.

// PrivateBlock output
<div data-private="true">{children}</div>
 
// In block mode
<div data-private="block">{children}</div>

You can achieve the same effect without the component by adding the attribute yourself:

<div data-private="true">
  <CreditCardForm />
</div>

The component is just cleaner ergonomics.

Examples

Credit card form

<PrivateBlock>
  <label>
    Card number
    <input value={cardNumber} onChange={(e) => setCardNumber(e.target.value)} />
  </label>
  <label>
    CVV
    <input type="password" value={cvv} onChange={(e) => setCvv(e.target.value)} />
  </label>
</PrivateBlock>

Table row with sensitive columns

<tr>
  <td>{order.id}</td>
  <PrivateBlock as="td">{order.customer_email}</PrivateBlock>
  <td>{order.amount}</td>
  <PrivateBlock as="td" block>{order.last_four}</PrivateBlock>
</tr>

Conditional masking

const isAdmin = user?.role === "admin";
 
{isAdmin ? (
  <p>Customer email: {customer.email}</p>
) : (
  <PrivateBlock>
    <p>Customer email: {customer.email}</p>
  </PrivateBlock>
)}

Related