Fandry UI
sfdx

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 Requirements
  • A Salesforce DX project (it has an sfdx-project.json)

  • The Salesforce CLI (sf) and an authorized org

  • Node.js 18 or newer, to run the fandry CLI

Native shadow DOM, whatever your org uses

Fandry components render in native shadow DOM (they set static 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 CLI

Install 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.

Terminal
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. Initialize

Fandry 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.

Terminal
npx 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 components

Name the components you want. Their dependencies are followed for you: adding table also adds input, checkbox, pagination, skeleton, the shared base and more.

Terminal
npx 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 structure
my-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 component

On the platform your code lives in the default c namespace, so fandryButton is <c-fandry-button>. No imports are needed in the template.

force-app/main/default/lwc/myComponent/myComponent.html
<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. Deploy

Deploy Fandry first (or both directories in one command), then your app.

Terminal
sf 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 know

select, combobox, command and lookup use dynamic components

They use lwc: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 table installs it as the fandryTableCore bundle.

  • Upgrading: update fandryui, then run fandry add <name> again. Bundles you have not edited are updated to the new version, edited ones are skipped, and --overwrite replaces only the components you name. Commit fandry.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.