Style Guide
If you wish to contribute to Skeleton, please review our opinionated code style guide below.
Feature Branches
Prefix | Description |
---|---|
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.
<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"
.
{#if $$slots.lead}<slot name="lead" />{/if}
Use adaptive theme colors for component styling.
❌ <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.
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
<button class="... {$$props.class ?? ''}" {...prunedRestProps()}>Skeleton</button>
Component Props
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 resolve to a type of string
and allows our build process to identify props that support Tailwind Intellisense.
import type { CssClasses } from '../..';
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.
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.
// 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"
.
$: 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
).
<div class="tab {classesTab}">
<span class="tab-label {classesLabel}">Label</span>
</div>
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.