Base 3:16
Tools

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/sdk

Wrap 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

AreaComponents
Signing inSignIn · SignUp · ForgotPassword · ResetPassword · VerifyEmail · DeviceApproval
The accountUserProfile · PasskeyManager · TwoFactorManager · ActiveSessions · PrivacyControls
TeamsTeamMembers · AcceptInvitation
BillingBillingPortal · PricingTable · PlanChangeReview · SubscriptionManager · UpcomingInvoice · SeatManager · PaymentMethod · Invoices · PayInvoice · usePlans
Your dataDataTable · Kanban · Chart · RecordDetail · RecordForm · RelationPicker
BrandBranding
For your ownuseBase316 · 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 tenant when 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:

  1. The dashboard. Brand colour, background and logo arrive automatically — your environment's, or the tenant's when the provider is given one.
  2. appearance tokens on the provider: { colorPrimary, borderRadius, … }. These win over the dashboard's values.
  3. Your own CSS, against the b316-* class names or a className on 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>;
}

On this page