Style Guide

If you wish to contribute to Skeleton, please review our opinionated code style guide below.


Feature Branches

PrefixDescription
docs/* Updates to the documentation pages or text copy.
feat/* New features, components, or far-reaching updates.
chore/* Simple and localized updates.
bugfix/* Commits that address or fix issues.

Each wildcard (*) should be replaced with short and semantic descriptions using kebab-case.

feat/my-new-component-name

File Names

  • Feature directories should be singular and title case: ../LightSwitch/..
  • Components should be singular and title case: LightSwitch.svelte
  • Svelte Actions should be singular, lowercase, and use Typescript: clipboard.ts
  • Tailwind Element stylesheets should be plural and lowercase: buttons.css
  • Documentation should be lowercase and use dashes: /routes/components/radio-groups/+page.svelte
  • Tests should be suffixed with *.test.ts, matching feature conventions: LightSwitch.test.ts

Conventions

Ensure relevant events bubble via event forwarding.

html
<button on:click on:mouseover>Skeleton</button>

Slot names should be short, semantic, and agnostic. Avoid names that are too specific, such as name="icon".

html
{#if $$slots.lead}<slot name="lead" />{/if}

Use adaptive theme colors for component styling.

html
<div class="bg-orange-500">Skeleton</div><div class="bg-secondary-500">Skeleton</div>

If you need to include miscellaneous attributes that were not defined as properties, use Svelte's $$restProps. Be careful though, this can overwrite the element's $$props.class attribute. To avoid this, delete the class key from $$restProps. We recommend introducing a prunedRestProps function as shown below.

javascript
function prunedRestProps(): any {
	delete $$restProps.class;
	return $$restProps;
}
html
<button class="... {$$props.class ?? ''}" {...prunedRestProps()}>Skeleton</button>

Component Props

typescript
export let flavor = 'Chocolate';
export let visible = false;
export let parameters: Record<string, string> = { foo: 'bar' };
  • Each prop should be a single word, all lowercase, and semantic. Match Tailwind class names if possible.
  • If you need multiple words, use camel-casing (ex: ringWidth).
  • Typescript will automatically handle primitive types that can be trivially inferred, such as string, number, or boolean.
  • Make sure to set relevant default values when possible.
  • When an existing prop is modified, consider documenting an example if relevant.

Tailwind Class Props

For props that pass one or more CSS utility classes, make sure to import and append the CSSClasses type. This resolves to a type of string and allows our build process to identify props that support Tailwind Intellisense.

typescript
import type { CssClasses } from '../..';
typescript
export let background: CssClasses = 'bg-primary-500'; // background color
export let color: CssClasses = 'text-primary-500'; // text color
export let rounded: CssClasses = 'rounded-xl'; // border radius
  • Color props should follow standard CSS style conventions (ex: color for text color).
  • Never pass class props as arrays or objects, strings are always preferred (ex: border border-primary-500).
  • Always pass the entire Tailwind class name. Tailwind does not support dynamic class names.

CSS Styling Conventions

Skeleton utilizes an opinionated set of conventions for defining structural and component props for CSS utility classes within components. Please review existing components for examples of this in practice.

Base Classes

The default classses for a component template element. Note the "c" is short for classes.

typescript
let cBase = 'bg-surface-500 p-4 rounded'; // parent element styles
let cLabel = 'text-base'; // child element label styles

Dynamic Classes

To dynamically modify classes based on a variable or prop, use a reactive statement as follows.

typescript
// Prop for outlined state
export let outlined = false;

// Create a reactive property that uses a tertiary statement
$: classesOutlined = outlined ? 'border-2 border-primary-500' : 'border-none';

Reactive Classes

We use the following pattern to combine base and dynamic classes. Note the parent element classes includes $$props.classes to enable arbitrary classes passed by the user via class="my-custom-class".

typescript
$: classesTab = `${cBase} ${classesOutlined} ${{$$props.classes ?? ''}}`; // parent element
$: classesLabel = `${cBaseLabel}`; // child element

Applying Classes

  • The first class should be an "id" class, which semantically describes the element for global overrides (ex: tab)
  • Then followed immediately by the reactive class set (ex: classesTab).
html
<div class="tab {classesTab}">
	<span class="tab-label {classesLabel}">Label</span>
</div>

Dynamic Transitions

Skeleton has a convention for implementing dynamic transitions within components. Please follow the guidelines below to ensure you are following our standard for this process.

TIP: You may reference existing components to view this in practice: Accordion

Implementation

Define transition types in a context="module" script tag so you can use it as generic for the standard script tag attributes.

typescript
<script context="module">
	import { slide } from 'svelte/transition';
	import {
		type Transition,
		type TransitionParams,
		type CssClasses, prefersReducedMotionStore
	} from '../../index.js';

	// eslint-disable-next-line @typescript-eslint/no-unused-vars
	type SlideTransition = typeof slide; // switch with your transition
	type TransitionIn = Transition;
	type TransitionOut = Transition;
</script>

Supply the generics in the standard script tag attributes.

html
<script
	lang="ts"
	generics="TransitionIn extends Transition = SlideTransition, TransitionOut extends Transition = SlideTransition"
>
// ...
</script>

Define the following properties:

  • transition - default to !$prefersReducedMotionStore to adhere to the Reduced motion rules.
  • transitionIn - the transition on entry.
  • transitionInParams - the parameters for the entry transition.
  • transitionOut - the transition on exit.
  • transitionOutParams - the parameters for the entry transition.

Implementing Transitions

See the dedicated Transitions page for examples of how to implement custom transitions for a component.

Documentation

TIP: Review existing component pages to view how this is presented in practice: Accordions

Add the transitionX props to DocsShellSettings to indicate that dynamic transitions are available.

typescript
const settings: DocsShellSettings = {
	// ...
	transitionIn: 'slide',
	transitionOut: 'slide'
};

Pitfalls

Below are a few pitfalls we've encountered when creating Skeleton. Do your best to avoid these whenever possible.

  • Never construct utility class names, Tailwind does not support this feature.
  • Avoid style blocks and @apply in component files. This will bloat the stylesheet bundle size.
  • Do not mix script-defined and inline Tailwind classes. Doing so can have a negative impact on the readability of the code.
  • Avoid switch-case statements to create shorthand property values (ex: sm, md, lg). This restricts customization.
  • Keep Skeleton icon library agnostic. Embed SVGs or unicode instead, which can be passed via a slot.