Skip to content

vincepg13/sn-react-shadcn

Repository files navigation

sn-shadcn-kit

A tree shakeable collection of react components built using ShadCN UI and Tailwind CSS. Built and tested using React 19.

Designed to give ServiceNow developers easy access to react components on the platform. Visit sn-shadkit-sdk for a demo app which can be installed onto ServiceNow instances.


📦 Installation

If you are installing from scratch then install the component and its peer dependencies:

npm install sn-shadcn-kit axios react react-dom sonner tailwindcss zod @tanstack/react-table

⚠️ Import Paths (V2+)

To keep performance optimal in the hosting application, only import what is being used by the relevant path:

  • sn-shadcn-kit – Root exports for core setup utilities like setAxiosInstance / getAxiosInstance.
  • sn-shadcn-kit/amb – Functionality for ServiceNow’s Asynchronous Message Bus (e.g., useRecordWatch).
  • sn-shadcn-kit/hooks – Utility React hooks (e.g., debouncing, shortcuts, responsive helpers).
  • sn-shadcn-kit/form – Form components (relies on heavier deps like CodeMirror and TipTap). ⚡ Consider lazy-loading these in your app.
  • sn-shadcn-kit/table – Components and utilities for working with ServiceNow tables.
  • sn-shadcn-kit/user – User-related components (avatars, user cards, group cards).
  • sn-shadcn-kit/skeleton – Placeholder skeletons and loaders.
  • sn-shadcn-kit/script – Script editor logic, including linting, formatting, and editor integrations.
  • sn-shadcn-kit/standalone – Lightweight components without heavy dependencies (e.g., attachments, record watchers).
  • sn-shadcn-kit/ui – Small UI/UX helpers and utility components.

🎨 CSS/Tailwind Setup (v4+)

To ensure Tailwind includes styles used in the component, add this to your global CSS file (e.g. index.css):

@import 'tailwindcss';
@source "./node_modules/sn-shadcn-kit/dist";

The HTML editor used in the form component needs to have its own stylesheets imported. The easiest way to do this is to grab the entire tiptap styles folder from the demo repo then import it into your global css file.

@import './styles/tiptap/index.css';

🔑 Axios Setup (IMPORTANT)

Components which fetch data from a ServiceNow instance (such as tables) use a shared Axios instance. In production, the authentication details will be automatically provided to axios via the ServiceNow session cookie. If you are using a devleopment server to test your app however, you must configure Axios to provide authentication to this package.

In development mode authentication can be provided via basic auth or via spoofing the ServiceNow session cookie of an active instance. For more details on how this works visit the Environment Variables section of the Shadcn Kit Template repository

Example Setup in Hosting Application:

import axios from 'axios'
import { setAxiosInstance } from 'sn-shadcn-kit'

if (import.meta.env.MODE === 'development') {
  axios.defaults.auth = {
    username: import.meta.env.VITE_REACT_APP_USER,
    password: import.meta.env.VITE_REACT_APP_PASSWORD,
  }
}

// Important: inject the configured instance into sn-shadcn-kit
setAxiosInstance(axios)

🏓 Tables, Tables and More Tables

When using either of the data tables, it will display each fields display value in the corresponding cell. If you want to take control of the UI, you can do this by passing in your own column definitions to the table. To find out more about column definitions in Tanstack visit the Column Definition Guide.

Below is an example of how you would use the table but make all the short description values have red text.

import { SnDataTable, SnRow } from 'sn-shadcn-kit/table'

const columns = [
  {
    accessorKey: 'short_description',
    header: 'Short Description',
    cell: ({ getValue }) => <div className="text-red-500">{getValue()?.display_value}</div>,
  },
]

<SnDataTable
  table="incident"
  fields={['short_description', 'priority', 'state']}
  columnDefinitions={columns}
/>

Below is an Example of what SnTable looks like with no UI modifications: SnTable Demo

🔧 Props

<SnDataTable />

Prop Type Description
table string ServiceNow table name (e.g. "incident")
fields string[] List of field names to include
query ? string Encoded query string
defaultPageSize ? number Default number of rows per page
onRowClick ? (row: SnRow) => void Callback when a row is clicked
columnDefinitions ? ColumnDef<SnRow, SnRowItem>[] Custom column definitions

<SnTable />

A wrapper to SnDataTable that inserts the fields array based on a SerivceNow List view.

This has more or less the same properties as SnDataTable with the exception that you no longer pass through fields. Instead the table will do the following:

  • Display the last used view of the logged in user with any customisations to it
  • If they have no list view pref for that table, it will display the default view
  • You can pass in the view name of a specific view via the view property

<SnTableHeader />

A simple table header component with a debounced search input and an optional advanced filter panel, designed to sit above one or the table components exported by this package and bring the whole UI together.

SnTableHeader props:

Prop Type Description
title string Header title text
tagline ? string Optional subtitle text under the title
table string ServiceNow table name for the condition builder
displayField string Field name used for the quick search input
query ? string Current encoded query string
uuid ? string Optional key to reset the condition builder instance
isFetching ? boolean Shows a loading spinner when true
actions ? ReactNode[] Additional action elements rendered on the right
onQueryChange (nextQuery: string) => void Callback when the query changes
onResetQuery () => void Callback when the query should be cleared

SnTableHeader Demo

<DataTable />

The final table component available is DataTable. Whilst this table takes a bit more setup to use, it gives you full control over the API layer and is a UI only component. This is especially useful when you want to integrate directly with your own asynchronous state managemnt, e.g. when using tanstack query. You can see an example of how to set this up in the TablePage route of the demo application.

<SnPersonaliseList />

You can use this component to personalise the list layout of a given table. The type SnListItem and is defined as {value: string, label: string}

Prop Type Description
unselected SnListItem[] array of the unselected fields
selected SnListItem[] array of the selected fields
isUserList boolean true when the current list view has not been modified by the user
onSave (selected?: SnListItem[]) => Promise<void> Callback on save of list view

This component manifests itself as a button, which when clicked will open a dialog that allows you to drag and drop fields to/from the current view. You can overwrite the style of the button by passing a child to the component which should itself also be a button.

<SnPersonalise />

A wrapper to the above component, SnPersonalise deals with fetching and saving the list layouts metadata so that you dont have to.

Prop Type Description
lmEndpoint string endpoint path for GET and POST
table string ServiceNow table name for list
view ? string Specific list view, if not provided default will be used
onChange ? () => void Callback when list view changes after save

The key to this component is to provide it with an endpoint path that can handle both the GET and POST requests, e.g. something like /api/<namespace>/react_form/list_mechanic.

This is the resource path I use for two scripted rest endpoints:

Although seperate scripted rest resources, they can share the same resource path since they belong to the same API definition and use different HTTP methods.

SnPersonalise Demo

Another component which assists you in interacting with tables is <SnFilter/>. a Condition Builder that can be used on forms or tables. You can read more in the Standalone Components section.


📝 ServiceNow Forms

If your'e familiar with widget development in the ServicePortal you'll know ServiceNow provide a method in the GlideScriptable API ($sp.getForm) that can return you the entire metadata for any given form. The below component set is a beta version of how you can consume that metadata and recreate a ServiceNow form with the same layout as well as UI policies, Client Scripts and UI actions.

My aim is to provide the same level of support for Client Side APIs that ServiceNow provide in the service portal.

Supported currently:

  • Fields: Most(but not every) field types are supported. If the form encounters an unmapped field type it will be hidden.
  • Form Layout: Form will render in the layout provided by ServiceNow which includes tabbed sections and their respective columns.
  • UI Policies: Scripted UI Policies as well as using the standard mandatory/visible/readonly options. The set and clear value options are currently unsupported.
  • Client Scripts: All will attempt to execute. Any unmapped g_form methods will send a warning to the console but still attempt to process the rest of the client script. Any failures will terminate the current client script and proceed to the next.

<SnFormWrapper /> && <SnForm />

Load a form using SnFormWrapper by giving it the api path to a scripted rest message above, the table name to load, and a guid (sysid)

SnFormWrapper props:

Prop Type Description
guid string sys_id of a record
table string Table name the record belongs to
apis SnFormApis Resource path to metadata apis
snInsert ? (guid: string) => void Optional callback triggered on record insert
snUpdate ? (guid: string) => void Optional callback triggered on record insert

To use the form you must provide it with all necessary metadata, I do this via a scripted API calls in the global scope. The apis property should be an object which stores these endpoints e.g:

{
  formData: '/api/<namespace>/react_form/fd/problem/-1?view=',
  refDisplay: '/api/<namespace>/react_form/ref_display'
}

You can find this code in the sn-scripts folder of the repo: getFormMetadata.js, getReferenceDisplay.js

This component will then consume the metadata from the api response and pass it to <SnForm/> to build the form

SnFormDemo SnFormRefs SnFormDates

🧩 Standalone Components

Within both the table and form components I make use of various standalone components. These are can be dropped in anywhere and do not have to exist only within tables or forms.

<SnTabs />

A wrapper around the shadcn tab components which allow you to pass in an array of tabs to be rendered. Each tab just needs to set a label and the ReactNode element to be rendered, if there are any possibilities of duplicate labels among the tabs then also set a key.

SnTabs props:

Prop Type Description
tabs string An Array of Tabs ({label: string, key?: string, content: ReactNode})
value ? string The tab to preset on load
onValueChange ? (val: string) => void Callback method to execute when a tab changes

<SnClippy/>

Your own personal clippy to be used in ServiceNow. Just give it a table and record then click the paperclip icon to view all the attachments in a shadcn sheet. From here you can delete attachments or add new onces using the drop zone in the footer of the sheet.

SnClippy props:

Prop Type Description
table string The table name of the record to load attachments from
guid string The sys_id of the record to load attachments from
instance ? string Only needed when testing on a dev server, but the SN instance name

<SnActivity/>

Given the current users sys_id, a table and a record sys_id, this component will build the activity formatter. Currently the formatter will only display journal input field history and allow you to post directly to any of these fields. Field changes, attachment updates and html are not currently supported.

SnActivity props:

Prop Type Description
table string The table name of the record to load the activity formatter from
guid string The sys_id of the record to load the activity formatter from
user string The sys_id of the current user
fullwidth ? boolean Setting this to true will display each row in the formatter in full width instead of the default left/right chat layout

<SnFilter/>

Used to display the condition builder of any given table. The component presents itself as a filter icon with the current query next to it. You can then click on the filter icon to open the full condition builder (Which is its own component you can also use, <SnConditionBuilder />).

SnFilter props:

Prop Type Description
table string The table name of the record to load the conditions from
encodedQuery ? string The initial query to build
initialOpenState ? open OR closed Whether to have the condition builder open on first load. It will default to closed.
onQueryBuilt ? (encoded: string) The callback called when the user executes the built condition by pressing the Run button

<SnDotwalkChoice />

Used as part of the condition builder, this component displays a list of ServiceNow fields whilst also allowing you to dotwalk through any of them which are reference fields. It is is a shadcn popover trigger button which displays a command menu when open.

SnDotwalkChoice props:

Prop Type Description
label ? string A field value name which appears as the label of the combo input, otherwise placeholder is shown
baseTable string The table name of the table who's fields you are displaying
disabled ? boolean When true the field is read only.
className ? string Additional class names that will be merged onto popovers trigger button
fieldsByTable Record<string, SnConditionMap> a state holding metadata for all fields, can be fetched from the api/now/ui/meta ServiceNow endpoint
setFieldsByTable SetStateAction The setter for the above state
onChange (updated: Partial<SnConditionRow>, table: string) Callback to run on change of the selected value

<SnScriptEditor />

A modern script editor built using CodeMirror V6, designed to work seamlessly with ServiceNow script fields. This editor includes formatting, jslint, themeing, plus all the built in codemirror commands such as searching and commenting etc.

SnScriptEditor props:

Prop Type Description
table string The table name of the record containing a script field
snType SnScriptFieldType The script field type based on the fields dictionary record (script/script_plain/html_template/css)
fieldName string The database column name of the script field
content ? string Initial value
readonly ? boolean When true the script field is locked
esLintConfig ? ESLintConfigAny An es lint config object in either V8 or V9 format
onChange ? (value: string) => void The callback called when a script is changed (runs on blur)

SnScript SnActivity SnFormAttachments

🧰 UI/UX Helpers

These components (imported from sn-shadcn-kit/ui) are small helpers that supplement the UI/UX of your application.

<SnGeneralConfirm />

A general-purpose confirmation dialog that opens when a message is provided and fires callbacks on continue/cancel.

SnGeneralConfirm props:

Prop Type Description
title ? string Optional dialog title
msg ? string Message content that triggers the dialog when set
continueCb ? () => void Callback when the user confirms
cancelCb ? () => void Callback when the user cancels

<SnSimpleTooltip />

A simple tooltip wrapper that displays a short string over a trigger element.

SnSimpleTooltip props:

Prop Type Description
content string Tooltip text to display
children ReactNode The trigger element

<SnLoadingSpinner />

A small loading spinner icon for inline loading states.

SnLoadingSpinner props:

Prop Type Description
className ? string Additional class names to override size/color

👥 Interacting With User Data

<SnAvatar />

Can be used to display a users avatar. This will show their photo as a thumbnail if supplied, else it will fall back to their initials

<SnAvatar name={name} image={image} className="size-12" />

<SnUserCard />

This will display the details of a given user

Prop Type Description
name string User's name
email string User's email address
phone ? string User's contact number
image ? string User's avatar image
primaryInfo ? string Information shown under the user's name
im ? string Instant messaging link to the user's contact

<SnGroupWrapper /> && <SnGroupCard />

SnGroupWrapper is designed to be easily used by simply passing in a groups sys_id, page size and a callback to build the instant messaging link per user. This wrapper will then load all the metadata for a group and feed it into SnGroupCard. If you just want to quickly load a group, this wrapper is the easiest way. Alternatively if you are loading multiple groups or want any custom logic around the group metadata then use SnGroupCard directly and pass in all the necessary props instead.

SnGroupWrapper props:

Prop Type Description
guid string sys_id of a group record
pagesize number Number of members to show per page
getImLink ? callback Function to build each users IM link

below is an example of how to use SnRecordPicker (which allows you to fetch data from a ServiceNow table in a drop down) to select a user and display their details

import { useState } from 'react'
import { SnUserCard } from "sn-shadcn-kit/user";
import { SnRecordPicker, SnRecordPickerItem } from 'sn-shadcn-kit/standalone'

export default function ServicenowUI() {
  const [selected, setSelected] = useState<SnRecordPickerItem | null>(null)

  return (
    <div className="">
      <div className="max-w-[400px] flex flex-col gap-4">
        <SnRecordPicker
          table="sys_user"
          fields={['name', 'email']}
          metaFields={['title', 'avatar', 'mobile_phone']}
          query="active=true^nameSTARTSWITHVince"
          value={selected}
          onChange={setSelected}
          placeholder="Select a user to view their card..."
        ></SnRecordPicker>
        {selected?.meta && (
          <SnUserCard
            name={selected.meta!.name.display_value}
            im={`https://teams.microsoft.com/l/chat/0/0?users=${selected.meta.email.value}`}
            email={selected.meta.email.value}
            phone={selected.meta.mobile_phone.value}
            image={`/${selected.meta.avatar.display_value}`}
            primaryInfo={selected.meta.title.display_value}
          ></SnUserCard>
        )}
      </div>
    </div>
  )
}

SnUserCardDemo


Hooks

A useful hook exported by this package which allows you to react to database changes in ServiceNow is useRecordWatch

This hook makes use of the same web socket connection ServicePortal and UI Builder use to provide real time updates. Just give it a table, query and a callback function, then when updates to watched records are made your callback function will be triggered with a message object of type SnAmbMessage. This will provide all information related to the database change.

Example use:

import { SnAmbMessage, useRecordWatch } from 'sn-shadcn-kit/amb'

export function ServicenowAmb() {
  const watcherCallback = (message: SnAmbMessage) => {
    console.log('Record changed: ', message)
  }

  useRecordWatch('problem', 'active=true', watcherCallback)
}

Utility hooks (sn-shadcn-kit/hooks)

High-level overviews of the hooks exported from src/hooks:

  • useAbortableController: Manages a single AbortController with renew and abort helpers; use it for fetches or async work you want to cancel on unmount or before a new run.
import { useAbortableController } from 'sn-shadcn-kit/hooks'

function Users() {
  const { getSignal } = useAbortableController()

  const load = async () => {
    const res = await fetch('/api/users', { signal: getSignal() })
    return res.json()
  }

  // call load() from an effect or event handler
}
  • useCancelableFn: Wraps an async function that accepts an AbortSignal and returns run/abort; use it to cancel in-flight requests tied to a single caller.
import { useCancelableFn } from 'sn-shadcn-kit/hooks'

function Search() {
  const { run, abort } = useCancelableFn((signal, q: string) =>
    fetch(`/api/search?q=${q}`, { signal }).then((r) => r.json())
  )

  // run('foo'); abort();
}
  • useDebouncedFn: Returns a debounced version of a function plus cancel; use it to delay rapid calls like search, resize, or validation.
import { useDebouncedFn } from 'sn-shadcn-kit/hooks'

function Filters() {
  const onChange = useDebouncedFn((value: string) => {
    console.log('apply filter', value)
  }, 300)

  // onChange('new value')
}
  • useIsMobile: Returns a boolean based on a breakpoint (default 768px); use it for JS-driven responsive behavior.
import { useIsMobile } from 'sn-shadcn-kit/hooks'

function Layout() {
  const isMobile = useIsMobile()
  return isMobile ? <MobileNav /> : <DesktopNav />
}
  • useSaveShortcut: Handles Cmd/Ctrl+S to trigger a callback or button click while preserving focus; use it to add a consistent “save” shortcut.
import { useSaveShortcut } from 'sn-shadcn-kit/hooks'

function Editor() {
  useSaveShortcut({ onTrigger: () => console.log('save') })
  return <textarea />
}
  • useSlashPrevention: Provides an onKeyDown handler that stops Mod-/ propagation when already handled; use it to prevent global listeners (such as snUtils) from double-handling shortcuts.
import { useSlashPrevention } from 'sn-shadcn-kit/hooks'

function Input() {
  const { onKeyDown } = useSlashPrevention()
  return <input onKeyDown={onKeyDown} />
}

📘 Types

This package exports helpful types for working with ServiceNow data:

import type { SnRow, SnRowItem } from 'sn-shadcn-kit/table'
import type { SnUser, SnGroup } from 'sn-shadcn-kit/user'
import type { SnRecordPickerItem, SnRecordPickerList } from 'sn-shadcn-kit/standalone'
  • SnRowItem corresponds to a fields value which is simply an object with both its value and display_value. SnRow is a record (array) of SnRowItems.
  • SnUser and SnGroup define the schema for a user and group object respectively
  • SnRecordPickerItem represents all the data you will get back when selecting a record using the SnRecordPicker component, and SnRecordPickerList is an array of these items

🪪 License

MIT © Two Portal Guys


About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published