A simple, performant & aesthetic toolkit built on Solid.js and optimized for nice frontend apps. Live Demo
- Super lightweight, with only Solid.js and Lenis as dependencies - making it fast AF.
- Best practices included out-of-the-box, with 100% Lighthouse scores all around.
- Built for nice design, with fluid typography, smooth scrolling, animation helpers, and more.
- Robust set of CSS variables make it fast to make global changes, while still giving scalable granular control over styling.
- Clone the Repo
Use the install command or download the .zip from the Github repo.
gh repo clone treyhardin/Truman-Kit
- Install Dependencies
Open a terminal in the project root and run the install command:
npm i
- Have Fun!
The purpose of this starter kit is to make development easier and more fun for frontend-focused projects. Now that you're set up, keep reading for details on the quality of life features built into the starter kit.
The philosophy of the toolkit is not to be a wholistic component library, but rather a core set of tools to make it easier to get started on projects.
Solid is an incredible library for powerful yet lightweight reactivity. If you're a React developer, you'll feel right at home working with Solid - in fact, the Solid team has learned from some of the quirks of React and made a framework that's both highly capable and refreshingly simple.
Lenis is the current best-in-class option for smooth scrolling, with considerations for accessibility, cross-device compatibility, and customization. If smooth scroll isn't your thing, it can be easily removed without affecting any of the other features included.
The features included represent a minimalist approach to modern, interactive sites without depending on heavy packages and libraries.
The approach to type styles is based on fluid scaling, as opposed to breakpoints and pixel sizes. Just specify your intended font size at desktop and mobile via custom properties in index.css
, and each style will scale accordingly, without scaling smaller than your mobile font size.
Though we set these values in pixel units, the actual calculated values or based on a function of vw
and px
units to allow them to scale without interfering with accessibility standards around browser scaling.
Specify your preferred viewport sizes:
* {
--viewport-desktop: 1440; /* The width of the desktop designs */
--viewport-mobile: 375; /* The width of the mobile designs */
}
Set your font sizes (in pixels) on each style:
h1,
.h1 {
--font-size-desktop: 100;
--font-size-mobile: 54;
font-family: var(--font-primary);
font-weight: 400;
letter-spacing: -0.05em;
line-height: 0.95;
}
In this case, h1
(the element) and .h1
(the class) will calculate to 54px at a viewport of 375px or smaller, 100px at a viewport of 1440px, and will continue to scale up proportionally for larger viewports.
Color definitions in this project are broken out into Fundamentals and Tokens and are defined in index.css
.
All your core project colors will be set as Fundamentals. The naming of each color property is intentionally abstracted to follow some inherent rules that help with accessibility:
Name | Property | Description |
---|---|---|
Ink | --color-ink |
Darkest color, usually #000 or similar. Should have sufficient contrast against Reverse . |
Subdued | --color-subdued |
Visibly lighter than Ink but with sufficient contrast against Reverse . |
Neutral | --color-neutral |
Utility color commonly used for strokes or other areas without accessibility concerns. |
Light | --color-light |
Visibly darker than Reverse but with sufficient contrast against Ink . |
Reverse | --color-reverse |
Lightest color, usually #fff or similar. Should have sufficient contrast against Ink . |
Brand | --color-brand |
Any brand color you want! |
Brand Contrast | --color-brand-contrast |
A sufficiently contrasting color against Brand . Usually either Ink or Reverse . |
Brand Secondary | --color-brand-secondary |
An optional secondary brand color. |
Brand Secondary Contrast | --color-brand-secondary-contrast |
A sufficiently contrasting color against Brand Secondary . Usually either Ink or Reverse . |
Once you've set your Fundamentals, the Tokens are defined as references to the Fundamentals. This lets us easily support color theming, as we can override any Token based on user color preference or section-specific styling. Working this way also means we can set our colors once and never have to think again about accessible color contrast:
Name | Example | Description |
---|---|---|
Foreground | var(--color-ink) |
Global foreground color, usually either Ink or Reverse . |
Foreground Secondary | var(--color-subdued) |
If Foreground is Ink , this is Subdued . If Foreground is Reverse , this is Light . |
Background | var(--color-reverse) |
Global background color, usually either Ink or Reverse . |
Background Secondary | var(--color-light) |
If Background is Ink , this is Subdued . If Background is Reverse , this is Light . |
Link | var(--color-brand) |
Any color that's accessible against Background . |
Link Hover | var(--color-brand-secondary) |
Any color that's accessible against Background . |
A common setup for Color Tokens could look like this:
body[dark-mode = "false"] {
--color-foreground: var(--color-ink);
--color-foreground-secondary: var(--color-subdued);
--color-background: var(--color-reverse);
--color-background-secondary: var(--color-light);
--color-link: var(--color-brand);
--color-link-hover: var(--color-brand-secondary);
}
body[dark-mode = "true"] {
--color-foreground: var(--color-reverse);
--color-foreground-secondary: var(--color-light);
--color-background: var(--color-ink);
--color-background-secondary: var(--color-subdued);
--color-link: var(--color-brand);
--color-link-hover: var(--color-brand-secondary);
}
Animation in the kit is primarily based on CSS properties & transitions. To extend the native capabilities of CSS, we've also included helpers for Page Load, Viewport and Scroll animations. These are simple, lightweight wrappers built around native browser capabilities, especially the Intersection Observer API.
CSS doesn't currently support transitions triggered when an element comes into view, but the Intersection Observer API gives us everything we need to do just that.
Any element wrapped in <ViewportAnimation>
will get an attribute [data-animate="true"]
which is set to false
when the item is in viewport, based on the props
passed in:
bidirectional
- If true
, the animation will reset once you've scrolled past the element. Otherwise, the animation will only happen once. Defaults to true
.
rootMargin
- A passthrough value for rootMargin
in the Intersection Observer API. Allows for offsetting the intersection area within the viewport. Defaults to "50% 0% 10% 0%"
.
threshold
- A passthrough value for threshold
in the Intersection Observer API. Defines how much of the element should be intersecting before triggering the animation. Defaults to 0.6
delay
- A delay in ms before the animation should trigger, after the element is in view. Particularly useful for staggered animations in a loop.
This attribute is then used to create 'before' styling in CSS that helps you transition your elements into place.
Example Component:
<ViewportAnimation bidirectional={true} delay={i() * cardDelay}>
<div class={styles.card}>
<p class={styles.emoji}>π</p>
<div class={styles.cardText}>
<h3 class={styles.cardTitle}>Card Title</h3>
<p class={styles.cardDescription}>Description</p>
</div>
</div>
</ViewportAnimation>
Example CSS:
.card {
opacity: 1;
translate: 0 0;
transition: transform var(--anim-lg), opacity var(--anim-lg);
}
.card[data-animate="true"] {
transform: 0 60px;
opacity: 0;
}
Because we can't depend on an Intersection Observer event to fire at the right time for above-the-fold elements, we've included a dedicated <PageLoadAnimation>
component specifically for onLoad
animations. Any element wrapped in this component will get a [data-animate-load="true"]
which is removed by a DOMContentLoaded
listener. This ensures you're not artifically delaying page load, but still get control of CSS page load animations. This is particularly useful for header, hero, and preloader elements.
Example Component:
<section class={styles.heroSection}>
<PageLoadAnimation>
<div class={styles.sectionTitle}>
<h1 class={styles.heading}>Heading</h1>
<p class={styles.subheading}>Subheading.</p>
</div>
<button title='Get Started'>Get Started</button>
</PageLoadAnimation>
</section>
Example CSS:
.sectionTitle {
transition: opacity var(--anim-lg), translate var(--anim-lg);
}
.sectionTitle[data-animate-load="true"] {
opacity: 0;
translate: 0 60%;
}
Any element wrapped in <ScrollAnimation>
will get a custom property --scroll-ratio
that ranges from 0-1, with 0 being the position at which the section has just entered the viewport and 1 when the section has fully left the viewport. This can be used in CSS values to animate just about anything.
Though you can use as many of these wrappers as you like, you should avoid nesting one <ScrollAnimation>
component within another. This component works best on larger page sections that fill multiple view heights.
threshold
- A value from 0-1 that allows for shortening the length of the animation to ensure elements are fully visible before beginning their scroll animation. Defaults to 1
.
Example Component:
<ScrollAnimation threshold={0.2}>
<section class={styles.scrollAnimations}>
<div class={styles.scrollContainer}>
<For each={cards}>{(card, i) =>
<div class={styles.card}>
<p class={styles.cardEmoji}>{card.emoji}</p>
<h4 class={styles.cardTitle}>{card.title}</h4>
<p class={styles.cardDescription}>{card.description}</p>
</div>
}</For>
</div>
</section>
</ScrollAnimation>
Example CSS:
.scrollContainer {
width: max-content;
translate: calc((-100% + 100vw) * var(--scroll-ratio));
}
All spacing within the kit is defined using Spacing Tokens. In index.css
we have a set of pixel values from --space-3xs
through --space-3xl
that are referenced any place we'd typically use pixels/ems.
:root {
--space-3xl: 120px;
--space-2xl: 80px;
--space-xl: 60px;
--space-lg: 40px;
--space-md: 30px;
--space-sm: 20px;
--space-xs: 15px;
--space-2xs: 10px;
--space-3xs: 5px;
}
βοΈ Coming Soon: Further development of the spacing system may include a more fluid approach to spacing tokens that scales similarly to how typography scales. The current approach relies on breakpoints for any spacing values that need to change between viewport sizes.
Because this kit is meant to be used by both designers & developers, we've included a Figma File with variables and tokens set up to match the CSS variables used in the project. Feel free to clone and use it for your own projects!
This is just a personal tool I built to make my side projects easier to spin up. I'd love to hear thoughts on how it can be further improved or built out, so don't be a stranger.
Designed and Developed by Trey Hardin. Get in touch at [email protected].