skeleton
  1. resources
  2. contribute
  3. components

Components

Guidelines for contributing to Skeleton component packages.

Packages

Framework packages are are found within the Skeleton monorepo as shown below. Each framework packages exports a set of components and related feature, while also being wrapped in an a dedicated app framework. Each app framework acts as sandbox for testing features within that framework’s “native” environment alongside the Astro enviroment for the documentation website.

PackageFrameworkApp Framework
/packages/skeleton-svelteSvelte 5SvelteKit
/packages/skeleton-reactReactVite/React

Dev Server

Run run each app framework, point your terminal at the respective package and run the following command in your terminal.

pnpm run dev

Server Ports

Note that you may run both the documentation site and each framework package apps in parallel. The following represents the default localhost address and port for each project. This will be displayed in the terminal when starting each dev server.

  • Documentation Site: http://localhost:4321/
  • Svelte Package App: http://localhost:5173/
  • React Package App: http://localhost:5173/

TIP: Each framework package you run will increment the next port address by 1 (ex: 5174, 5175, etc).

Add Components

Components are housed in the following location per framework:

FrameworkDirectory
Svelte/src/lib/components
React/src/components

Use the following file path conventions when creating a new component:

/components
/ComponentName
ComponentName.{svelte|tsx|...}
types.ts

Props

Component style props follow a structured convention for better semantics, which helps avoid naming conflicts.

TIP: Note that the parent element does not use a prefix. foo and bar are placeholders for a semantic element names.

let {
// Parent Element
base: '...',
bg: '...',
classes: '...',
// Child Element
fooBase: '...',
fooBg: '...',
fooClasses: '...',
// Child or Grandchild Element
barBase: '...',
barBg: '...',
barPadding: '...',
barClasses: '...',
} = $props<ExampleProps>();

Categories

This includes three categories of props per element. These should be implemented in the following order top-to-bottom.

  • base - houses the base utility classes, which enables faux-headless styling.
    • Replaces: cBase class definitions from Skeleton v2.
    • Parent base props are not prefixed, which helps with parity cross-framework
    • Child base props are prefixed: titleBase, panelBase, controlBase.
  • {property} - individual style props that houses one or more “swappable” utility classes.
    • Naming should follow Tailwind convention, except for single letter descriptors (padding instead of p).
    • Parent props are not prefixed: background, margin, border.
    • Child props are prefixed: titleBg, controlMargin, panelBorder.
  • classes - allows you to extend or override with an arbitrary set of classes..
    • Replaces the inconsistent use of slotX and regionX classes in Skeleton v2.
    • Parent instances are not prefixed: classes
    • Child instances are prefixed: titleClasses, controlClasses, panelClasses

Visual Reference

The following diagram illustrates how props are added to each individual template element, including the order.

diagram

TIP: If you have multiple style props (green), keep them sandwiched between base/classes props.

Direct Implementation

By default, all component props are passed straight to the template to avoid unnecessary boilerplate.

<script lang="ts">
let {
base = '...',
bg = '...',
padding = '...',
classes = '...'
} = $props<ExampleProps>();
</script>
<div class="{base} {bg} {padding} {classes}">...</div>

Dynamic Style Props

In some scenarios, you may need to conditionally update or swap between one or more sets of utilty classes. For this, we will use an “interceptor” pattern as demonstrated below. The rx naming convention denotes the value is “reactive”.

<script lang="ts">
let {
active: true,
// ...
fooActive = '...',
fooInactive = '...'
// ...
} = $props<ExampleProps>();
// Interceptor
const rxActive = $state(active ? fooActive : fooInactive);
</script>
<div class="... {rxActive} ...">...</div>

TIP: for React, utilize a useState() hook.


JSDocs

Features can be documented inline within the code via JSDoc. This provides additional context through Intellisense features of text editors and IDEs. These comments are required for all Typescript type definitions. Keep the description short and semantic.

export interface ExampleProps {
/** Sets base styles. */
base?: string;
/** Set vertical spacing styles. */
spaceY?: string;
/** Provide arbitrary CSS classes. */
classes?: string;
}

Composed Pattern

To keep Skeleton’s component syntax familiar cross-framework, we will make use of a composed pattern. The specific implementation will differ per each supported framework.

React

For React, this is handled via a dot notation syntax.

<Accordion>
<Accordion.Item>
<Accordion.Control>(control)</Accordion.Control>
<Accordion.Panel>(panel)</Accordion.Panel>
</Accordion.Item>
</Accordion>

To implement this, first import the reactCompose utility into the component file:

import { reactCompose } from '../../utils/react-compose';

Then scaffold your component exports at the bottom of the file:

export const Accordion = reactCompose(
AccordionRoot, // -> <Accordion>
{
Item: AccordionItem, // -> <Accordion.Item>
Control: AccordionControl, // -> <Accordion.Control>
Panel: AccordionPanel, // -> <Accordion.Panel>
},
);

Svelte 5

This is handled by pairing child components with Svelte Snippets.

<Accordion>
<AccordionItem>
{#snippet controlLead()}(lead){/snippet}
{#snippet control()}(control){/snippet}
{#snippet panel()}(panel){/snippet}
</AccordionItem>
</Accordion>

Additional Resources