@coyalabs/bts-style
v1.3.22
Published
BTS Theme Svelte component templates
Maintainers
Readme
@coyalabs/bts-style
Coya Behind the Scenes - A Svelte 4 component library with a serif 1920s aesthetic.
Installation
npm install @coyalabs/bts-styleQuick Start
<script>
import { BasePage, Button, BaseText } from '@coyalabs/bts-style';
</script>
<BasePage>
<BaseText variant="title">Hello BTS Theme!</BaseText>
<Button theme="primary">Click Me</Button>
</BasePage>Design System
Colors
- Background:
#0B070D- Deep dark page background - Default Text:
#E3D8D8- Light text and icons - Toned:
#A18F8F- Muted icons and accents - Accent:
#FFEFF6- Interactive elements
Fonts
The package uses fonts from Google Fonts and Fontshare (loaded via CDN):
- Noto Serif KR (900) - Titles and button text
- Satoshi (400, 500, 700, 900) - Content and UI text
Theme Variants
All container-based components support these themes:
full- Standard glass container with subtle backgroundprimary- Slightly more prominent glass effectsecondary- Alternative glass stylingvague- Subtle thingfilled- Use for solid container sections (customizable)
Components
Base Components
BasePage
Root page component with automatic favicon setup, dark background, and decorative background elements.
Props:
favicon?: string | null- Custom favicon URL (defaults to BTS theme icon)showBGDetails?: boolean- Show/hide decorative background elements (default:true)chainColor?: string- Color of the animated chain (default:"#130F15")
Example:
<BasePage favicon="/custom-icon.svg">
<!-- Your app content -->
</BasePage>
<!-- Without background decorations -->
<BasePage showBGDetails={false}>
<!-- Clean page without gears/chains -->
</BasePage>
<!-- Custom chain color -->
<BasePage chainColor="#1a1520">
<!-- Content with custom chain color -->
</BasePage>Features:
- Sets min-height: 100vh
- Applies #0B070D background
- Automatically injects BTS favicon unless overridden
- Resets body/html margins
- Decorative BGDetails layer with animated gears and swinging chain
Background Details (BGDetails):
The BGDetails component renders decorative 1920s-style animated elements:
- Chain - A physics-simulated swinging chain in the top-left corner
- Gears - Rotating interlocked gears in the top-right corner
- Single Gear - A slowly rotating gear in the bottom-left corner
These elements are purely decorative (pointer-events: none) and sit behind your content. Disable with showBGDetails={false}.
BaseContainer
Container with customizable corners and themes.
Props:
theme?: 'full' | 'primary' | 'secondary' | 'filled'- Visual theme (default:'full')padding?: string- CSS padding value (default:'1.5rem')borderRadiusTopLeft?: string- Top-left corner radius (default:'25px')borderRadiusTopRight?: string- Top-right corner radius (default:'25px')borderRadiusBottomLeft?: string- Bottom-left corner radius (default:'25px')borderRadiusBottomRight?: string- Bottom-right corner radius (default:'25px')
Example:
<BaseContainer theme="primary" borderRadiusTopLeft="35px">
<slot />
</BaseContainer>BaseText
Typography component with three text variants.
Props:
variant: 'title' | 'content' | 'button'- Text style variantcontent?: string | null- Raw text content to render directlymarkdown?: boolean- Parsecontentas sanitized markdownas?: string | null- Override the wrapper element (divis used automatically for markdown)textModifier?: string- Size adjustmenttextWeightModifier?: string- Weight adjustment
Variants:
title- Noto Serif KR 900, 30px - For headingscontent- Satoshi 700, 18px - For body textbutton- Noto Serif KR 900, 17px - For button labels
Example:
<BaseText variant="title">Page Title</BaseText>
<BaseText variant="content">This is content text.</BaseText>
<BaseText
variant="content"
content={messageBody}
markdown={true}
/>Markdown notes:
- Markdown rendering only applies when using the
contentprop. - Output is sanitized before rendering.
- Supports GFM-style links, emphasis, headings, lists, blockquotes, tables, inline code, and fenced code blocks.
BaseIcon
SVG icon wrapper with color variants.
Props:
svg: string- SVG markup stringvariant?: 'default' | 'toned'- Color variant (default:'default')size?: string- Icon size (default:'18px')
Example:
<script>
import { BaseIcon, icons } from '@coyalabs/bts-style';
</script>
<BaseIcon svg={icons.folder} variant="toned" size="24px" />Structure Components
TextHeader
Header component with title and optional subtitle.
Props:
title: string- Main heading textsubtitle?: string- Optional subheading
Example:
<TextHeader
title="Welcome"
subtitle="Get started with BTS Theme"
/>Interactive Components
Button
Clickable button extending BaseContainer with icon support.
Props:
theme?: 'full' | 'primary' | 'secondary' | 'filled'- Button themeicon?: string- Left icon SVGactionIcon?: string | null- Right icon SVG (default: arrow, null to hide)iconRotation?: number- Left icon rotation in degreesactionIconRotation?: number- Right icon rotation in degreesiconSize?: string- Left icon size (default:'18px')actionIconSize?: string- Right icon size (default:'18px')disabled?: boolean- Disable interaction (default:false)loading?: boolean- Show loading spinner, hide content, and disable button (default:false)loadingSize?: string- Spinner size when loading (default:'18px')- All BaseContainer corner radius props
Events:
- Forwards all events including
on:click
Example:
<script>
import { Button, icons } from '@coyalabs/bts-style';
let isLoading = false;
async function handleClick() {
isLoading = true;
await someAsyncOperation();
isLoading = false;
}
</script>
<Button
theme="primary"
icon={icons.folder}
actionIconRotation={-45}
on:click={() => console.log('Clicked!')}
>
Open Folder
</Button>
<!-- Button without action icon -->
<Button actionIcon={null} theme="secondary">
Simple Button
</Button>
<!-- Loading state -->
<Button loading={isLoading} on:click={handleClick}>
Submit Form
</Button>
<!-- Disabled state -->
<Button disabled theme="secondary">
Disabled Button
</Button>States:
- Hover: Subtle background lightening
- Active/Pressed: Visual feedback
- Disabled: Non-interactive, reduced opacity,
not-allowedcursor - Loading: Shows spinner, disables interaction (opacity 0.7)
IconButton
Circular icon-only button with hover effects.
Props:
svg: string- Icon SVG markupvariant?: 'default' | 'toned'- Icon color variantsize?: string- Button size (default:'20px')
Events:
- Forwards all events including
on:click
Example:
<script>
import { IconButton, icons } from '@coyalabs/bts-style';
</script>
<IconButton
svg={icons.cross}
variant="toned"
on:click={() => closeModal()}
/>Effects:
- Hover: Scale 1.1, subtle background
- Active: Scale 0.95
LoadingSpinner
Animated loading spinner using the light gear from the BTS theme assets. Insertable into any component (buttons, containers, inline text, etc.).
Props:
size?: string- Spinner size (default:'20px')speed?: number- Rotation speed in seconds (default:2)color?: string | null- Custom color (default:'white')
Example:
<script>
import { LoadingSpinner } from '@coyalabs/bts-style';
</script>
<!-- Basic usage -->
<LoadingSpinner />
<!-- Custom size and speed -->
<LoadingSpinner size="40px" speed={1.5} />
<!-- Inside a button -->
<Button theme="primary">
Loading <LoadingSpinner size="16px" />
</Button>
<!-- Custom color -->
<LoadingSpinner size="2rem" color="#FFEFF6" />Features:
- Smooth infinite rotation animation
- Uses
inline-flexdisplay for easy insertion anywhere - Defaults to
currentColorto inherit parent text color - Configurable rotation speed
- Based on the light gear asset from assetGear.js
InputBox
Text input field with icon support and theme matching.
Props:
value?: string- Input value (bindable)placeholder?: string- Placeholder texttype?: string- Input type (default:'text')multiline?: boolean- Render a textarea instead of an input (default:false)maxRows?: number- Maximum visible rows for auto-growing multiline inputs (default:4)expandOnNewline?: boolean- Keep a multiline input visually single-line until the value contains a newline (default:false)newlineOnCtrlEnter?: boolean- For multiline inputs, plain Enter submits the nearest form or bubbles to parent handlers, whileCtrl+EnterandShift+Enterinsert a newline (default:false)resize?: 'none' | 'both' | 'horizontal' | 'vertical'- Textarea resize mode whenmultilineis enabled (default:'vertical')theme?: 'full' | 'primary' | 'secondary' | 'filled'- Visual themeicon?: string- Left icon SVG- All BaseContainer corner radius props
Example:
<script>
import { InputBox, icons } from '@coyalabs/bts-style';
let username = '';
</script>
<InputBox
bind:value={username}
placeholder="Enter username..."
icon={icons.pen}
theme="primary"
/>
<InputBox
bind:value={username}
placeholder="Write a longer note..."
icon={icons.pen}
multiline={true}
maxRows={6}
resize="vertical"
/>
<InputBox
bind:value={username}
placeholder="Looks like one line until you press Enter..."
icon={icons.pen}
multiline={true}
expandOnNewline={true}
maxRows={4}
/>
<InputBox
bind:value={username}
placeholder="Press Enter to submit, Ctrl+Enter for a newline"
icon={icons.pen}
multiline={true}
newlineOnCtrlEnter={true}
maxRows={4}
/>CodeBlock
Code display component with syntax highlighting-ready styling and copy-to-clipboard functionality.
Props:
code: string- Code content to displaylanguage?: string- Optional language label (e.g.,'javascript','python')theme?: 'full' | 'primary' | 'secondary' | 'filled'- Visual theme (default:'secondary')showCopy?: boolean- Show copy button (default:true)- All BaseContainer corner radius props
Example:
<script>
import { CodeBlock } from '@coyalabs/bts-style';
const myCode = `function hello() {
console.log('Hello, world!');
}`;
</script>
<CodeBlock
code={myCode}
language="javascript"
theme="secondary"
/>
<!-- Without language label -->
<CodeBlock
code="npm install @coyalabs/bts-style"
showCopy={true}
/>Features:
- Monospace font display (
Courier New) - Horizontal scrolling for long lines
- Copy button with visual feedback (check icon for 2 seconds)
- Optional language label in header
- Styled scrollbar matching theme
- Preserves whitespace and formatting
Visual Design:
- Code displayed in
#E3D8D8color - Language label in toned color (
#A18F8F) - Custom scrollbar with theme colors
- Copy button uses IconButton with toned variant
- Header with language and copy button (if enabled)
ScrollContainer
Reusable overflow wrapper with theme-matching custom scrollbar styling.
Props:
overflowX?: 'auto' | 'scroll' | 'hidden' | 'visible'- Horizontal overflow mode (default:'auto')overflowY?: 'auto' | 'scroll' | 'hidden' | 'visible'- Vertical overflow mode (default:'auto')width?: string- Container width (default:'100%')height?: string- Container height (default:'auto')maxWidth?: string- Max width (default:'100%')maxHeight?: string- Max height (default:'none')scrollbarSize?: string- Scrollbar thickness/height (default:'8px')trackColor?: string- Scrollbar track color (default:'rgba(161, 143, 143, 0.1)')thumbColor?: string- Scrollbar thumb color (default:'rgba(161, 143, 143, 0.3)')thumbHoverColor?: string- Scrollbar thumb hover color (default:'rgba(161, 143, 143, 0.5)')borderRadius?: string- Scrollbar track/thumb radius (default:'4px')hideScrollbarUntilScroll?: boolean- Hide scrollbars until the container has been scrolled from its initial position (default:false)
Example:
<script>
import { ScrollContainer } from '@coyalabs/bts-style';
</script>
<ScrollContainer overflowY="auto" maxHeight="320px">
<div style="min-height: 640px;">
Scrollable content with BTS-themed scrollbar.
</div>
</ScrollContainer>
<ScrollContainer overflowY="auto" maxHeight="320px" hideScrollbarUntilScroll={true}>
<div style="min-height: 640px;">
Scrollbars stay hidden until the first real scroll.
</div>
</ScrollContainer>Features:
- Reusable themed scrollbar styles from a single component
- Supports both horizontal and vertical overflow use cases
- Works with slot content (lists, code areas, panels, etc.)
- Includes Firefox
scrollbar-color/scrollbar-widthsupport
Toggle
Animated toggle switch with on/off states.
Props:
checked?: boolean- Toggle state (bindable)
Example:
<script>
import { Toggle } from '@coyalabs/bts-style';
let enabled = false;
</script>
<Toggle bind:checked={enabled} />
<p>State: {enabled ? 'On' : 'Off'}</p>Tooltip
Hover tooltip that displays above or below an icon.
Props:
icon: string- Icon SVG markuptext: string- Tooltip text contenticonSize?: string- Icon size (default:'18px')
Example:
<script>
import { Tooltip, icons } from '@coyalabs/bts-style';
</script>
<Tooltip
icon={icons.folder}
text="This is a helpful explanation"
iconSize="20px"
/>Features:
- Automatic positioning to fit on screen
- Min width 150px, max width 300px
TabBar
Vertical tab navigation with support for categorized groups.
Props:
tabs: Array<TabItem>- Array of tabs and separatorsactiveTab: string- Currently active tab ID (bindable)onTabChange?: (tabId: string) => void- Callback when tab changes
TabItem Type:
type TabItem = {
id?: string; // Required for tabs, omit for separators
label: string; // Tab/separator text
type?: 'tab' | 'separator'; // Default: 'tab'
// Icon props (spread directly onto Button):
icon?: string; // Icon SVG markup
iconRotation?: number; // Icon rotation in degrees
iconSize?: string; // Icon size (default: '18px')
iconToned?: boolean; // Use toned icon variant
}Any extra properties on a tab item beyond id, label, and type are forwarded directly to the underlying Button component as props. This means all Button icon-related props (icon, iconRotation, iconSize, iconToned) can be set per-tab without needing to be individually declared.
Example:
<script>
import { TabBar, icons } from '@coyalabs/bts-style';
let currentTab = 'home';
const tabs = [
{ type: 'separator', label: 'Navigation' },
{ id: 'home', label: 'Home', icon: icons.folder },
{ id: 'about', label: 'About' },
{ type: 'separator', label: 'Account' },
{ id: 'settings', label: 'Settings' },
{ id: 'profile', label: 'Profile' }
];
</script>
<TabBar
{tabs}
bind:activeTab={currentTab}
onTabChange={(id) => console.log('Tab:', id)}
/>Features:
- Vertical layout, 175px fixed width
- Active tab uses
primarytheme, inactive usessecondary - Adaptive corner radius: 25px at group boundaries, 18px for middle tabs
- Text separators for grouping tabs into categories
- Categories have 1rem spacing between them
- Buttons within categories have 4px spacing
Corner Radius Behavior:
- First tab in a group: top corners 25px
- Last tab in a group: bottom corners 25px
- Middle tabs: all corners 18px
- Separators create group boundaries
TreeDirectory
Expandable file/folder tree with recursive structure.
Props:
items: Array<TreeItem>- Tree data structureshowCount?: boolean- Show item counts in folders (default:false)itemIcon?: string- Custom icon for itemstopFoldersExpanded?: boolean- Expand top-level folders initially (default:false)draggable?: boolean- Enable drag and drop functionality (default:false)currentPath?: string- Current folder path for nested items (default:'')level?: number- Internal: Current nesting level (default:0)
TreeItem Type:
type TreeItem = {
name: string;
type: 'file' | 'folder' | 'custom';
children?: TreeItem[];
data?: any; // Custom data attached to item
variant?: 'full' | 'primary' | 'secondary' | 'filled' | 'special-filled'; // Theme variant
suffixIcon?: string; // SVG icon displayed on the right
isPending?: boolean; // Show pending state with reduced opacity
}Events:
on:itemClick- Fired when any item is clickedevent.detail.item- The clicked item objectevent.detail.type- Either'file'or'folder'
on:rightClick- Fired when any item is right-clickedevent.detail.item- The right-clicked item objectevent.detail.type- Either'file'or'folder'event.detail.event- The original mouse event (for positioning context menus)
on:dragStart- Fired when dragging a file starts (requiresdraggable={true})event.detail.item- The dragged item objectevent.detail.sourcePath- Path of the source folder
on:itemDrop- Fired when a file is dropped (requiresdraggable={true})event.detail.item- The dropped item dataevent.detail.sourcePath- Original folder pathevent.detail.targetPath- Destination folder path
Example:
<script>
import { TreeDirectory, icons } from '@coyalabs/bts-style';
const fileTree = [
{
name: 'src',
type: 'folder',
variant: 'primary',
children: [
{
name: 'App.svelte',
type: 'file',
variant: 'secondary',
suffixIcon: icons.pen
},
{ name: 'main.js', type: 'file' }
]
},
{
name: 'README.md',
type: 'file',
suffixIcon: '<svg>...</svg>'
}
];
function handleItemClick(event) {
const { item, type } = event.detail;
console.log(`Clicked ${type}:`, item.name);
}
function handleRightClick(event) {
const { item, type, event: mouseEvent } = event.detail;
console.log(`Right-clicked ${type}:`, item.name);
// Use mouseEvent.clientX and mouseEvent.clientY for positioning context menus
}
function handleDrop(event) {
const { item, sourcePath, targetPath } = event.detail;
console.log(`Moved ${item.name} from ${sourcePath} to ${targetPath}`);
}
</script>
<TreeDirectory
items={fileTree}
showCount={true}
topFoldersExpanded={true}
draggable={true}
on:itemClick={handleItemClick}
on:rightClick={handleRightClick}
on:itemDrop={handleDrop}
/>Methods:
<script>
let tree;
// Get current state
const state = tree.getState();
// Restore state
tree.setState(state);
// Expand all folders
tree.expandAll();
// Collapse all folders
tree.collapseAll();
</script>
<TreeDirectory bind:this={tree} items={fileTree} />Features:
- Slide animations (150ms, skipped on initial render)
- Recursive item counting
- State persistence
- Event forwarding for click handling
- Initial expansion option for top-level folders
- Adaptive corner radius based on nesting
- Padding increases with depth
- Per-item theme variants (override default folder/file themes)
- Suffix icon support for displaying icons on the right side
- Optional drag and drop functionality for files
- Visual feedback during drag operations (folder highlight)
- Path tracking for nested folder structures
Drag and Drop:
When draggable={true}:
- Only files can be dragged (folders are drop targets)
- Dragged files show reduced opacity
- Folders highlight when dragged over
- Drop on folders to move files into them
- Drop on empty space to move to current level
on:itemDropevent provides source and target paths for handling moves
Customization:
Item Variants:
Each item can have a custom variant to override the default theme:
- Folders default to
'primary' - Files default to
'secondary' - Set
varianton any item to use a different theme
Suffix Icons: Display additional icons on the right side of items:
{
name: 'config.json',
type: 'file',
suffixIcon: '<svg>...</svg>' // Any SVG string
}Custom Items:
Use type: 'custom' to render custom components via slot:
<TreeDirectory items={[
{ name: 'folder', type: 'folder', children: [...] },
{ name: 'custom-widget', type: 'custom', data: {...} }
]}>
<svelte:fragment slot="custom" let:item let:isNested let:isLast>
<YourCustomComponent {item} {isNested} {isLast} />
</svelte:fragment>
</TreeDirectory>Corner Radius Behavior:
- Custom items act as list terminators for corner radius calculations
- If the next item is of
type: 'custom', the current item's bottom corners will be fully rounded (25px) - This creates visual separation between standard tree items and custom slot components
Popup System
Popup
Global modal popup overlay (singleton).
Usage:
<script>
import { Popup, popupStore } from '@coyalabs/bts-style';
</script>
<!-- Place once at app root -->
<Popup />Features:
- Fade overlay (250ms)
- Fly dialog animation (300ms, backOut easing)
- Close button with toned icon
- ESC key support (built-in)
popupStore
Writable store for controlling the popup.
Methods:
open()
popupStore.open(
title: string,
component: SvelteComponent,
props?: object & {
popupWidthRatio?: number
},
subtitle?: string
)Opens popup with custom component.
popupWidthRatio multiplies the default popup width while keeping the dialog capped to the viewport.
Use 1 for the current width, values below 1 for narrower dialogs, and values above 1 for wider dialogs.
Example:
<script>
import { popupStore } from '@coyalabs/bts-style';
import MyCustomPopup from './MyCustomPopup.svelte';
</script>
<button on:click={() =>
popupStore.open(
'Settings',
MyCustomPopup,
{ userId: 123, popupWidthRatio: 1.25 },
'Configure your preferences'
)
}>
Open Settings
</button>confirm()
popupStore.confirm(
title: string,
message: string,
options?: {
onConfirm?: () => void,
onCancel?: () => void,
confirmText?: string,
cancelText?: string,
popupWidthRatio?: number
}
)Shows confirmation dialog.
Example:
<button on:click={() =>
popupStore.confirm(
'Delete Item',
'Are you sure? This cannot be undone.',
{
onConfirm: () => deleteItem(),
onCancel: () => console.log('Cancelled'),
confirmText: 'Delete',
cancelText: 'Keep',
popupWidthRatio: 1.15
}
)
}>
Delete
</button>alert()
popupStore.alert(
title: string,
message: string,
options?: {
onOk?: () => void,
okText?: string,
popupWidthRatio?: number
}
)Shows alert dialog.
Example:
popupStore.alert(
'Success',
'Your changes have been saved!',
{
onOk: () => navigateToHome(),
okText: 'Got it',
popupWidthRatio: 0.9
}
);prompt()
popupStore.prompt(
title: string,
message: string,
options?: {
onSubmit?: (value: string) => void,
onCancel?: () => void,
placeholder?: string,
submitText?: string,
cancelText?: string,
popupWidthRatio?: number
}
)Shows input prompt dialog.
Example:
popupStore.prompt(
'Enter Name',
'Please provide your display name:',
{
onSubmit: (name) => updateProfile(name),
placeholder: 'Your name...',
submitText: 'Save',
cancelText: 'Skip',
popupWidthRatio: 0.95
}
);close()
popupStore.close()Closes the current popup.
Popup Presets
The popup system includes three pre-built popup components that can be triggered via popupStore methods.
ConfirmPopup
A two-button confirmation dialog with confirm and cancel actions.
Features:
- Primary theme confirm button (left)
- Secondary theme cancel button (right)
- Callbacks:
onConfirm,onCancel - Customizable button text
Triggered via:
popupStore.confirm(title, message, options)Visual Layout:
- Message text displayed as subtitle
- Two buttons side-by-side
- Confirm button uses primary theme
- Cancel button uses secondary theme
AlertPopup
A single-button alert dialog for notifications.
Features:
- Single OK button with primary theme
- Callback:
onOk - Customizable button text
- Auto-closes on OK click
Triggered via:
popupStore.alert(title, message, options)Visual Layout:
- Message text displayed as subtitle
- Single centered OK button
- Primary theme button
PromptPopup
An input dialog that prompts the user for text input.
Features:
- InputBox component for text entry
- Submit button (primary theme)
- Cancel button (secondary theme)
- Callbacks:
onSubmit(value),onCancel - Customizable placeholder and button text
Triggered via:
popupStore.prompt(title, message, options)Visual Layout:
- Message text displayed as subtitle
- InputBox with customizable placeholder
- Two buttons: Submit (primary) and Cancel (secondary)
- Submit returns the input value to callback
Example with all options:
popupStore.prompt(
'Rename File',
'Enter a new name for this file:',
{
placeholder: 'filename.txt',
submitText: 'Rename',
cancelText: 'Cancel',
onSubmit: (newName) => {
console.log('New name:', newName);
renameFile(newName);
},
onCancel: () => console.log('Rename cancelled')
}
);Toast System
Toast
Global toast notification container (singleton).
Usage:
<script>
import { Toast, toastStore } from '@coyalabs/bts-style';
</script>
<!-- Place once at app root -->
<Toast />Features:
- Bottom-right stacking
- Smooth slide animations (300ms in, 200ms out)
- Auto-dismiss with configurable duration
- Manual dismiss via close button
- Non-blocking, less intrusive than popups
toastStore
Writable store for controlling toast notifications.
Methods:
show()
toastStore.show(
message: string,
duration?: number // Default: 4000ms, 0 = no auto-dismiss
): string // Returns toast IDShows a toast notification.
Example:
<script>
import { toastStore } from '@coyalabs/bts-style';
</script>
<button on:click={() => toastStore.show('File saved successfully!')}>
Save
</button>
<!-- Custom duration -->
<button on:click={() => toastStore.show('Processing...', 2000)}>
Process
</button>
<!-- No auto-dismiss -->
<button on:click={() => toastStore.show('Important message', 0)}>
Important
</button>dismiss()
toastStore.dismiss(id: string)Dismisses a specific toast by ID.
Example:
<script>
const toastId = toastStore.show('Click to dismiss', 0);
</script>
<button on:click={() => toastStore.dismiss(toastId)}>
Dismiss Toast
</button>dismissAll()
toastStore.dismissAll()Dismisses all active toasts.
Example:
<button on:click={() => toastStore.dismissAll()}>
Clear All Toasts
</button>Visual Design:
- Uses Button component with secondary theme
- Help icon on the left (info indicator)
- Close icon (crossb) on the right
- Stacks vertically with 0.75rem gap
- Slides in from right, slides out to right
- Fixed position at bottom-right (2rem from edges)
- Z-index 10000 (above popups)
Best Practices:
Place <Toast /> once at your app root alongside <Popup />:
<!-- App.svelte -->
<script>
import { BasePage, Popup, Toast } from '@coyalabs/bts-style';
</script>
<BasePage>
<!-- App content -->
</BasePage>
<Popup />
<Toast />Special Components
Glowy components with unique styling and animations.
SpecialAction
A special-themed button with gradient background and optional tooltip.
Props:
label: string- Button texttooltipText?: string- Optional tooltip text displayed on the right- All Button corner radius props (
borderRadiusTopLeft,borderRadiusTopRight,borderRadiusBottomLeft,borderRadiusBottomRight)
Example:
<script>
import { SpecialAction } from '@coyalabs/bts-style';
</script>
<SpecialAction
label="AI Generate"
tooltipText="Uses AI to generate content"
/>
<!-- With custom corners -->
<SpecialAction
label="AI Generate"
borderRadiusTopLeft="35px"
borderRadiusBottomRight="35px"
/>Features:
- Purple gradient background (
special-filledtheme) - AI icon on the left
- Tooltip positioned at far right (when provided)
- Enhanced hover effect with brighter gradient
- Pressed state with darker gradient
For?
- AI actions.
Styling:
- Background: Linear gradient purple tones
- Enhanced glow effects on hover
- User-select disabled for better UX
SpecialParagraph
Animated text component that reveals words sequentially with fade and blur effects.
Props:
text: string- The text content to animatewordDelay?: number- Delay between each word appearing in ms (default:50)startDelay?: number- Initial delay before animation starts in ms (default:0)animationDuration?: number- Animation duration for each word in ms (default:300)variant?: 'title' | 'content' | 'button'- Text styling variant (default:'content')textModifier?: string- Font size adjustment (default:'0px')autoPlay?: boolean- Start animation automatically on mount (default:true)
Methods:
play()- Start/restart the animationreset()- Reset to hidden stateshowAll()- Show all words immediately
Example:
<script>
import { SpecialParagraph } from '@coyalabs/bts-style';
let paragraph;
</script>
<!-- Auto-playing paragraph -->
<SpecialParagraph
text="This text will animate in word by word with smooth effects"
wordDelay={80}
variant="content"
/>
<!-- Manual control -->
<SpecialParagraph
bind:this={paragraph}
text="Click the button to animate!"
autoPlay={false}
/>
<button on:click={() => paragraph.play()}>Play Animation</button>Animation Effects:
- Each word fades in from opacity 0 to 1
- Slides up from 8px translateY offset
- Blur transitions from 4px to 0
- Smooth easing transitions
For?
- Fancier AI.
Use Cases:
- Intro text animations
- Loading state messages
- Drawing attention to important content
- Storytelling and narrative interfaces
ContextMenu
Categorized menu component with separator support and animated scale-in from a specified corner.
Props:
items: Array<ContextMenuItem>- Array of menu items and separatorsselectedValue?: any- Currently selected item valueonSelect?: (value: any) => void- Callback when item is selectedorigin?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'- Animation origin corner (default:'top-left')
ContextMenuItem Type:
type ContextMenuItem = {
label: string;
value?: any; // Required for items, omit for separators
disabled?: boolean;
type?: 'item' | 'separator'; // Default: 'item'
}Example:
<script>
import { ContextMenu } from '@coyalabs/bts-style';
let selectedValue = 'cut';
const menuItems = [
{ label: 'Edit', type: 'separator' },
{ label: 'Cut', value: 'cut' },
{ label: 'Copy', value: 'copy' },
{ label: 'Paste', value: 'paste', disabled: true },
{ label: 'View', type: 'separator' },
{ label: 'Zoom In', value: 'zoomIn' },
{ label: 'Zoom Out', value: 'zoomOut' }
];
</script>
<ContextMenu
items={menuItems}
{selectedValue}
origin="bottom-right"
onSelect={(val) => handleAction(val)}
/>Features:
- Category grouping with separator labels
- Selected item highlighting
- Disabled item support with reduced opacity
- Hover effects on enabled items
- Filled theme container
- Automatic category spacing and borders
- Text ellipsis for long labels
- Smooth scale-in animation from specified corner (350ms with backOut easing)
- Scale-out animation on dismiss (200ms)
Visual Layout:
- Categories separated by labeled dividers
- First category has no top border
- Subsequent categories have subtle top border
- 0.5rem padding around separators
- 4px spacing between items
Dropdown
Select dropdown with collapsible options menu.
Props:
label: string- Default button text before selectionicon?: string- Optional left icon SVGtheme?: 'full' | 'primary' | 'secondary'- Button theme (default:'full')width?: string- Fixed width for dropdown (default:'200px')options: Array<DropdownOption>- Array of selectable optionsvalue?: any- Currently selected value (bindable)onChange?: (value: any) => void- Callback when selection changes- All BaseContainer corner radius props
DropdownOption Type:
type DropdownOption = {
label: string;
value: any;
disabled?: boolean;
type?: 'item' | 'separator'; // Optional: use for category separation
}Example:
<script>
import { Dropdown, icons } from '@coyalabs/bts-style';
let selectedValue = 'option1';
const options = [
{ label: 'Basic', type: 'separator' },
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Advanced', type: 'separator' },
{ label: 'Option 3', value: 'option3' },
{ label: 'Disabled', value: 'option4', disabled: true }
];
</script>
<Dropdown
label="Select an option"
icon={icons.folder}
theme="primary"
width="250px"
{options}
bind:value={selectedValue}
onChange={(val) => console.log('Selected:', val)}
/>Features:
- Fixed width with text truncation (ellipsis)
- Expand icon rotates 180° when open
- Slide animation for menu (150ms)
- Click outside to close
- Uses ContextMenu component internally
- Support for category separators
- Selected item highlighted
- Disabled items shown with reduced opacity
LinearList
Vertical list component with customizable actions for each item.
Props:
items: Array<ListItem>- Array of list items
ListItem Type:
type CustomAction = {
label: string;
actionIcon?: string;
}
type ListItem = {
data?: any; // Custom data attached to item
customActions?: CustomAction[];
removeButton?: boolean;
}Events:
on:action- Fired when any custom action is clickedevent.detail.index- Item indexevent.detail.actionLabel- Action label that was clickedevent.detail.item- The item object
on:remove- Fired when remove button is clickedevent.detail.index- Item indexevent.detail.item- The item object
Example:
<script>
import { LinearList, icons } from '@coyalabs/bts-style';
const items = [
{
data: { id: 1, name: 'First Item' },
customActions: [
{ label: 'Edit', actionIcon: icons.pen },
{ label: 'View' }
],
removeButton: true
},
{
data: { id: 2, name: 'Second Item' },
customActions: [
{ label: 'Download' }
],
removeButton: true
}
];
function handleAction(event) {
const { index, actionLabel, item } = event.detail;
console.log(`${actionLabel} clicked on item ${index}`, item);
}
function handleRemove(event) {
const { index, item } = event.detail;
console.log(`Remove item ${index}`, item);
}
</script>
<LinearList
{items}
on:action={handleAction}
on:remove={handleRemove}
>
{#snippet children({ item, index })}
<div>{item.data.name}</div>
{/snippet}
</LinearList>Features:
- Left-aligned content slot with
itemandindexprops - Right-aligned action buttons (horizontal)
- Optional remove button (icon-only, toned variant)
- 1px bottom border separator (rgba(161, 143, 143, 0.24))
- No border on last item
- 10px vertical padding per item
- No horizontal padding
- No spacing between items
- Event-based action handling
Visual Layout:
- Each item is a flex row with space-between
- Content on the left, actions on the right
- Actions have 0.5rem gap between them
- Remove button appears at the end of actions
- Borders are internal strokes (bottom edge only)
Separator
Decorative SVG separator with tiled middle section.
Props:
height?: string- Separator height (default:'12px')width?: string- Separator width (default:'100%')margin?: string- CSS margin (default:'2rem 0')
Example:
<script>
import { Separator } from '@coyalabs/bts-style';
</script>
<Separator />
<Separator height="20px" margin="3rem 0" />Features:
- Three-part design: left piece, tiled middle, right piece
- Inline SVG data URLs for performance
- Scales to container width
- Elegant visual break between sections
Icons
The package exports a collection of built-in SVG icons.
Usage:
<script>
import { icons, BaseIcon } from '@coyalabs/bts-style';
</script>
<BaseIcon svg={icons.arrow} />
<BaseIcon svg={icons.folder} />
<BaseIcon svg={icons.icon_expand} />
<BaseIcon svg={icons.cross} />
<BaseIcon svg={icons.pen} />Available Icons:
arrow- Right arrow navigationfolder- Folder iconicon_expand- Expand/collapse chevroncross- Close/dismiss Xpen- Edit/write pen
Custom Icons:
You can use any SVG string with icon-supporting components:
<script>
const myIcon = '<svg>...</svg>';
</script>
<Button icon={myIcon}>Custom Icon</Button>
<BaseIcon svg={myIcon} />Styling
Corner Radius Customization
All components extending BaseContainer support individual corner radius props:
<Button
borderRadiusTopLeft="35px"
borderRadiusTopRight="35px"
borderRadiusBottomLeft="15px"
borderRadiusBottomRight="15px"
theme="primary"
>
Asymmetric Button
</Button>Theme Customization
You can customize filled theme backgrounds by targeting CSS variables or extending components.
Best Practices
Layout
<BasePage>
<main style="max-width: 900px; margin: 0 auto; padding: 3rem 2rem;">
<TextHeader title="My App" subtitle="Welcome" />
<!-- Your content -->
</main>
</BasePage>Popup Management
Place <Popup /> once at your app root:
<!-- App.svelte -->
<script>
import { BasePage, Popup } from '@coyalabs/bts-style';
</script>
<BasePage>
<!-- App content -->
</BasePage>
<Popup />Icons
For consistent styling, prefer using BaseIcon over raw SVG:
<!-- Good -->
<BaseIcon svg={myIcon} variant="toned" />
<!-- Less ideal -->
{@html myIcon}Development
Local Development
Link the package locally for testing:
# In the package directory
cd @bts-theme/bts-theme
npm run package
npm link
# In your project
npm link @coyalabs/bts-styleAfter making changes:
npm run packageNote: You may need to clear Vite cache after rebuilding:
rm -rf node_modules/.vitePublishing
npm run release # Bumps version, packages, and publishesTypeScript Support
All components include TypeScript definitions. Import types as needed:
import type { TreeItem } from '@coyalabs/bts-style';Package Structure
@coyalabs/bts-style/
├── dist/ # Compiled package
├── public/ # Static assets
│ ├── favicon.svg # Default BTS favicon
│ └── PLACE_YOUR_IMAGES_HERE.txt
├── src/
│ ├── Base/ # Base components
│ ├── Components/ # Interactive components
│ ├── Structure/ # Layout components
│ ├── icons.js # Icon definitions
│ └── index.ts # Main export
└── package.jsonLicense
MIT
Repository
github.com/sparklescoya/svelte-bts-theme
Credits
Created with ❤️ using Svelte 5
