Getting started on Salesforce
The platform has no bundler and cannot import npm packages, so the CLI copies components into your project as source you own, with everything they depend on.
Requirements
Link to section RequirementsA Salesforce DX project (it has an
sfdx-project.json)The Salesforce CLI (
sf) and an authorized orgNode.js 18 or newer, to run the
fandryCLI
Native shadow DOM, whatever your org uses
Fandry components render in native shadow DOM (they setstatic shadowSupportMode = 'native'), even in orgs that run other components on the synthetic-shadow polyfill. That is what lets you restyle their internals with ::part() and keeps their behavior identical to LWR. Your own components don't need to change: they can stay synthetic and still use and style fandry components, and Lightning components you slot into fandry (a lightning-button in a card or dialog) keep working. The one place not to use Lightning base components is inside an option's component (select, combobox, command, lookup): fandry renders that component, so it runs in native shadow, where base components lose their SLDS styling. SLDS and other global CSS don't reach inside them; theme them with --fd-* tokens and parts.1. Install the CLI
Link to section 1. Install the CLIInstall fandryui as a dev dependency. It is only used to run the fandry CLI and to copy source out of; nothing from node_modules is deployed.
npm install --save-dev fandryui
Prefer not to install it? Every command below also works as npx fandryui <command>.
2. Initialize
Link to section 2. InitializeFandry gets its own package directory next to your app code, so the copied source is easy to tell apart and can be deployed on its own.
Terminalnpx fandry init sfdx-project.json (after)
{
"packageDirectories": [
{ "path": "force-app", "default": true },
{ "path": "fandryui" }
],
"namespace": "",
"sourceApiVersion": "67.0"
}
Use --dir <path> to install into a different package directory. Your existing indentation and default package directory are left untouched.
3. Add components
Link to section 3. Add componentsName the components you want. Their dependencies are followed for you: adding table also adds input, checkbox, pagination, skeleton, the shared base and more.
Terminalnpx fandry add button input table Output
Added 10 bundle(s) to fandryui/main/default/lwc (3 requested, 7 dependencies) + fandryBase + fandryButton + fandryLabel + fandryInput + fandryCheckbox + fandryPagination + fandrySkeleton + fandryTableCore + fandryTableState + fandryTable Other commands
npx fandry list # every component and what it depends on npx fandry add --all # everything npx fandry add table --dry-run # show what would be added npx fandry add table --overwrite # replace table even if you edited it
Each bundle gets a .js-meta.xml using your project's sourceApiVersion, and it is never rewritten after that, so you can expose a component or add targets safely. Bundles you have edited are never overwritten unless you name them with --overwrite.
What your project looks like
Link to section What your project looks like Directory structuremy-sfdx-project/
├── sfdx-project.json # "fandryui" package directory added by `fandry init`
├── fandry.json # written by `fandry init`
├── package.json
├── force-app/ # your app
│ └── main/default/lwc/
│ └── myComponent/
└── fandryui/ # Fandry: your source now, commit it
└── main/default/lwc/
├── fandryBase/ # shared class, base.css, tokens.css, motion.css
├── fandryButton/
├── fandryInput/
├── fandryLabel/
├── fandryCheckbox/
├── fandryPagination/
├── fandrySkeleton/
├── fandryTable/
├── fandryTableState/
└── fandryTableCore/ # @tanstack/table-core, bundled
The copied files are yours: commit them, edit them, deploy them. They are ordinary LWC source, not a managed package.
Make it yours: CSS, then slots, then your own copy
Style most things from your own CSS with--fd-* tokens and ::part(), down to a rainbow border. Add to a component through its slots: a loading spinner is just slotted content. For a change inside a component that neither CSS nor slots reach, like a different native element or different internal behavior, copy fandryButton to myButton, rename it, and change anything: it still extends fandryBase, so it keeps your tokens and native shadow DOM. fandry add does not overwrite files you changed unless you pass --overwrite. With lightning-* components the only equivalent is rebuilding from SLDS blueprints.4. Use a component
Link to section 4. Use a componentOn the platform your code lives in the default c namespace, so fandryButton is <c-fandry-button>. No imports are needed in the template.
<template>
<lightning-card title="Team">
<c-fandry-button onclick={handleSave}>Save</c-fandry-button>
<c-fandry-table
columns={columns}
data={rows}
caption="Team members"
enable-pagination
page-size="3"
enable-global-filter
></c-fandry-table>
</lightning-card>
</template>
force-app/main/default/lwc/myComponent/myComponent.js
import { LightningElement } from 'lwc';
export default class MyComponent extends LightningElement {
columns = [
{ id: 'name', accessorKey: 'name', header: 'Name' },
{ id: 'role', accessorKey: 'role', header: 'Role' }
];
rows = [
{ name: 'Ada Lovelace', role: 'Engineer' },
{ name: 'Alan Turing', role: 'Researcher' }
];
handleSave() {}
}
5. Deploy
Link to section 5. DeployDeploy Fandry first (or both directories in one command), then your app.
Terminalsf project deploy start --source-dir fandryui --target-org my-org sf project deploy start --source-dir force-app --target-org my-org # or both at once sf project deploy start --source-dir fandryui --source-dir force-app --target-org my-org
Tested on a real org
button, input, table (with its bundled table-core), pagination and the table search were deployed to an org and run on a Lightning page.Good to know
Link to section Good to knowselect, combobox, command and lookup use dynamic components
They uselwc:is, which Salesforce only accepts when the bundle's own .js-meta.xml declares the lightning__dynamicComponent capability (with API 55 or later and Lightning Web Security on); otherwise the deploy fails with LWC1188. fandry add writes the capability into the meta files it creates and tells you if an existing one lacks it. Nothing in jsconfig.json or your project settings does this.
Fandry is not a managed package. Components are plain
c-namespace source in your project.The table depends on
@tanstack/table-core. Because npm packages cannot be imported on the platform,fandry add tableinstalls it as thefandryTableCorebundle.Upgrading: update
fandryui, then runfandry add <name>again. Bundles you have not edited are updated to the new version, edited ones are skipped, and--overwritereplaces only the components you name. Commitfandry.json: it records what was installed so an upgrade can be told apart from an edit.
Ready to build? Browse every component, its props and examples under Components.