Components
Sign-in, profile, teams, billing and data screens, rendered inside your own application.
@base316/react renders Base316's screens inside your own pages rather than on
a page your users are sent away to.
npm install @base316/react @base316/sdkWrap your app once
import { Base316Provider } from "@base316/react";
export default function App({ children }) {
return (
<Base316Provider publishableKey={process.env.NEXT_PUBLIC_BASE316_KEY}>
{children}
</Base316Provider>
);
}The provider holds one client and the signed-in person's session, and loads the branding you set in the dashboard — so the components look like your hosted pages without configuring your brand twice.
What there is
| Area | Components |
|---|---|
| Signing in | SignIn · SignUp · ForgotPassword · ResetPassword · VerifyEmail · DeviceApproval |
| The account | UserProfile · PasskeyManager · TwoFactorManager · ActiveSessions · PrivacyControls |
| Teams | TeamMembers · AcceptInvitation |
| Billing | BillingPortal · PricingTable · PlanChangeReview · SubscriptionManager · UpcomingInvoice · SeatManager · PaymentMethod · Invoices · PayInvoice · usePlans |
| Your data | DataTable · Kanban · Chart · RecordDetail · RecordForm · RelationPicker |
| Brand | Branding |
| For your own | useBase316 · useSession · useCollection |
Every component and prop is also described in machine-readable form —
MANIFEST, exported from the package — which is what an agent building your
screens reads to choose between them.
White-label for a tenant
When your product is run by one business on behalf of others — a firm and its client companies, an agency and its brands — the screens a client sees should carry the firm's brand, not yours. Give the provider the tenant and every component under it follows:
<Base316Provider publishableKey={key} tenant={{ organizationExternalId: currentFirm.id }}>
<Branding />
<SignIn />
<PricingTable />
</Base316Provider>- Name the tenant by Base316's id (
organizationId) or your own (organizationExternalId). - Its branding is resolved field by field: the tenant's own, then its parent's, then your environment's. So a firm's clients show the firm's brand with nothing set on each client — and a client that sets its own colour keeps the firm's name and logo.
- Change
tenantwhen the person switches team and the components restyle without a reload. - A tenant id that does not exist in the environment falls back to your own branding rather than failing, so the page never renders unbranded.
The same lookup is available without React: billing.tenantBranding({ organizationId }) on Base316Browser, or GET /v1/public/branding?organizationId=…
with a publishable key.
Branding is set per tenant in the dashboard (Branding on the tenant's row)
or with organizations.setBranding — see
Managed tenants.
A whole billing page
BillingPortal is the billing screen in one component: plan and status, the
next invoice, a scheduled change with its undo, a plan picker whose choice opens
PlanChangeReview (gained and lost features, what it costs now or when it
waits), seats on a per-seat plan, the card on file (PaymentMethod, which
replaces it on a Stripe-hosted page), invoices with PDF download and Pay, and
cancel and resume. Pass billingDetails to put your own address or tax-ID form
in its Billing details section. The same page is also hosted by Base316 for apps
that do not use React — see Billing portal.
A downgrade scheduled for the renewal
Picking a cheaper plan in PricingTable schedules it for the end of the period
rather than switching at once: the current plan stays marked current, and the
table says when the new one starts. usePlans().selectPlan returns
{ status: "scheduled", effectiveAt } for it. SubscriptionManager then shows
"Moves to Basic on 1 Nov" with a Keep Pro button that withdraws the change.
Billing screens for a firm's clients
When the firm pays for a client, SubscriptionManager shows the firm's plan
with "Provided by Hartley & Co" and no cancel button, and PricingTable names
the firm above the plans and disables every button — a plan bought there would
grant the client nothing. usePlans() exposes the same as managedBy.
Teams reached through a firm
A firm's staff reach its clients' teams without being members of them. In
TeamMembers, such a team says where the access comes from — "You have access
through Hartley & Co" — and offers nothing to leave, because there is no
membership to leave: the access ends when the firm's role mapping or the
client's link to the firm does.
What a manager can do to other roles
team.manage decides who may invite and remove people at all. What they may
do to somebody else's role is bounded by their own: a member can invite people
only to a role whose permissions they hold every one of, and can remove only a
member whose role is within theirs. An admin cannot invite an owner, withdraw
the owner's invitation, or remove the owner; an owner can do all three to
another owner. TeamMembers invites to the environment's default role, so if
that is a role holding *, only a member holding * can invite from it — call
teams.invite with a roleId for anybody else.
Leaving is always allowed, with one exception: a member holding team.manage
cannot leave while nobody remaining holds a role covering theirs. That is the
last owner — they give the role to somebody first. Your server, with a secret
key, is not bounded by either rule.
The member list carries addresses only for somebody holding team.manage;
everybody else sees names and roles, with email null.
Styling
Three ways, from least to most invasive:
- The dashboard. Brand colour, background and logo arrive automatically — your environment's, or the tenant's when the provider is given one.
appearancetokens on the provider:{ colorPrimary, borderRadius, … }. These win over the dashboard's values.- Your own CSS, against the
b316-*class names or aclassNameon any component. Every colour, radius and font is a--b316-*custom property.
Reading the session
import { useSession } from "@base316/react";
function Greeting() {
const { session, loading } = useSession();
if (loading) return null;
return session ? <p>Hello {session.user.email}</p> : <a href="/login">Sign in</a>;
}
