Skip to content

Nitip Design System (Astro)

Reusable tokens and components for every page of the Nitip template. The published Framer template is the reference for look and behavior; component names follow the Figma file so designers and developers talk about the same thing.

See it live at /style-guide (not linked from the site, not indexed).

src/
design-system/ ← this folder: page-agnostic building blocks
theme.css ← color, breakpoint, easing and animation tokens (Tailwind v4 @theme)
tokens/text.ts ← text styles (h01 … menu)
tokens/motion.ts ← springs and the height reveal
utils/ ← cn(), link helpers, motionStates(), image lookup
scripts/hover.ts ← hover states for data-hover / data-motion (loaded by the layout)
components/ ← actions, brand, navigation, layout, cards, media, data, patterns, forms, cursor, utils
types.ts ← data shapes the components take (ProjectCardData, …)
components/
site/ ← Header, Footer (design system + site content)
sections/ ← page sections, content passed in as props
content/ ← site.json, pages/*.json, collections/*.json, schemas.ts, routes.ts
lib/ ← content.ts (collections), to-ui.ts (content → props), format.ts, forms/
  1. Design-system components never import from src/content. Data comes in through props, so the same component works on any page.
  2. Pages pass content to sections; sections compose design-system components. Keep copy out of components.
  3. Links come from routes (routes.project(slug)) or the content, never hard-coded strings.
  4. Use tokens, not raw values: text.h02, bg-ink, border-line, springButton. If a new value repeats, add a token.
  5. Match Framer first. Any deliberate refinement is documented in the component’s comment.
  6. No framework runtime. Interactivity is plain TypeScript in each component’s <script> (Astro bundles it once per page) using Motion’s animate, hover and inView.
  7. New component? Put it in the right components/ group, add it to /style-guide and the table below.

A hoverable element gets data-hover; every element that changes on hover — the element itself included — gets data-motion with its two states (write the rest state inline too, so the page is right before scripts run):

<a href={href} data-hover data-motion={motionStates({ boxShadow: rest }, { boxShadow: hover })} style={`box-shadow: ${rest}`}>
{label}
<span data-motion={motionStates({ rotate: 0 }, { rotate: 45 })}><Icon name="arrow-up-right" /></span>
</a>

data-transition="soft" picks another spring (default button). Hover is ignored on touch; with reduced motion the change is instant.

Token Values
Breakpoints Phone < 810 · md Tablet 810–1199 · lg Desktop ≥ 1200
Colors ink #101010 · canvas #F6F6F6 · line #D9D9D9 · muted #818181 · accent #FC5000 · star #FDCA49 · danger #FF2244
Font Inter variable (font-sans), loaded by the Astro Fonts API in astro.config.mjs
Text styles text.h01 46/58/72 · h02 44/52/64 · h2 32/36/42 · h3 28/32/36 · h4 24/26/28 · h5 20/22/24 · h6 18/19/20 · hugeParagraph 24/28/32 · largeParagraph 18 · paragraph 16 · smallParagraph 14 · button 14 uppercase · menu 13 uppercase
Section spacing <Section>: sectionGutters 20/30/40, sectionPadding 60/80/100 · sectionGap 60/80/100 between header and content · pageTitlePadding 50 top for page titles (layout/section.ts)
Motion springButton (spring 0.4s, bounce 0.2) · springSoft (0.4s, no bounce) · springSlide (200/40) · springCursor (500/60) · reveal (height reveal) · ease-framer CSS tween for image zooms
Component Figma name Notes
PrimaryButton Primary Button (Default / No Icon) icon={false} for No Icon. Hover: shadow + arrow 45°
SecondaryButton Secondary Button Hover: underline + arrow -45°
BigButton Big Button Footer menu link
SocialMediaLink Social Media Hover: black row, white text
MenuButton Menu (Default / Hover / Active) Active has no hover
TextLink — (Framer link preset) 80% ink + underline on hover; external links open in a new tab
ArrowButton — (Framer slideshow arrow)
SwitchButton Switch Button (Default / Active) Square toggle, #222 outline; Default hover 50% opacity
LoadMoreButton Load More Square ink button, 150 × 40, no hover
Logo Logo (Black / White) tone="light" for dark backgrounds
Icon Phosphor icon set name, tone, size via class
Navigation Navigation (Default / Home / …) Active item from pathname; layout="stack" for mobile
Hamburger Hamburger (Default / Close) The header script opens the menu
Breadcrumbs — (Framer “Breadcrumbs”) Last item is the current page
Section, SectionHeader, SectionLabel, Divider Section / Title Section SectionHeader size="small" for Heading 6 titles; leadClass sets the lead width
PageTitle Page Title Breadcrumbs (optional), h1, divider, description (max 600, descriptionWidth="wide" 850)
ProjectCard Project With Details (Default) “View Project” cursor on the image; titleAs="h2" on index pages
ProjectSimpleCard Project Simple Card (Open / Close) Used by ProjectRecentList
ProjectListItem Project With Details (Desktop / Tablet / Phone List) Row with divider; View Details and image zoom on desktop
PostCard Post Card “Read” cursor
TestimonialCard Testimonial (Desktop / Tablet / Mobile)
TextPoint Text Point List row with dot
HighlightList — (Framer rich text) Bulleted list with a bold lead phrase
FactCard Fact Number tone="dark" for the ink card; ring cursor
TeamCard Team Portrait, name, role, email. No hover
FillImage Optimized image filling its positioned parent (src = /images/<file>)
CoverImage — (Framer Image + Cover) Bottom gradient with content; ring cursor
ParallaxImage — (Framer Parallax) speed/phoneSpeed/offset/scale; image sized so no gap ever shows; clip={false} to clip in the parent
ScrollTransform — (Framer Scroll Transform, In View) from/phoneFrom (x, y, scale, opacity) → rest while the frame scrolls into view
Chart Chart (Desktop / Desktop Start / Mobile) Grows on scroll; ring cursor
PricingCard Pricing Plan switch (hidden with one plan), included list, price, CTA
Rating, AvatarGroup Hero “User Rating”
AccordionItem Service / Careers row layout="service" | "career"; marker in the marker slot, panel in the default slot
ServiceList Service (Open / Close)
CareerList Careers (Open / Close) Dot marker; pageSize adds a left-aligned Load More
ValueList Value (Desktop / Tablet / Phone) Numbered rows. No hover
ProjectRecentList Project Recent List Hover opens a card
ProjectGallery Projects page Grid / List Switch + ProjectCard grid or ProjectListItem list
PostGrid Blog archive grid 1 / 2 / 3 columns, Load More, 4 or 6 posts per page
RichText — (Framer rich text) Post body: headings 40 above, paragraphs and lists 20; numbered and nested lists, bold leads
Slideshow, TestimonialSlideshow Testimonial Slide Show Autoplay 5s, drag, keyboard
Ticker — (Framer Ticker) 30 px/s, CSS only
BigFooter Big Footer “More Details” cursor
Form — (Framer form) Demo, static endpoint or server route (src/lib/forms/README.md); pending / success / error on the Submit button; resets on success only; honeypot
CheckboxGroup — (Framer checkboxes) Every checked option is sent under one name
FormField — (Framer input / textarea) Label + 40 px input or 200 px message box
SubmitButton Form (Default / Loading / Success / Error) Full width; status for a fixed state
Cursor Cursor (Dot / Active / View Project / Read / More Details) Rendered once in the layout; use data-cursor="…"
CurrentYear Always-current copyright year

<Cursor /> is rendered once in src/layouts/Layout.astro. Mark elements with data-cursor:

<a href={href} data-cursor="view-project"></a> <!-- also: "read", "more-details", "ring" -->