Slender UI

Slender-ooy

The JavaScript-less component library for Svelte.

Introduction

To get started, here you go. But first…

Another UI library?

Yes.

Why?

To celebrate the best of HTML and CSS. And I’m tired of implementing the same things in slightly different ways.

Also, JavaScript is abused. Sprinkle it on for a better experience, but start with the basics. And it turns out you can push the basics quite far, so hopefully we can get better experiences for shopping on Tor.

See this by Stuart Langridge.

No JavaScript you say?

Kinda. Most components work without it, but they’re progressively enhanced for accessibility and extra smoothness when JS is available. To manage state, like in a modal or dropdown, there are checkboxes (and occasionally radio buttons). Lots and lots of checkboxes. And some CSS trickery.

Some requirements are outside the realms of HTML hacks, like the Transition and custom Listbox or Combobox components.

Installation

In your SvelteKit (or regular Svelte) project:

npm i slender-ui

If you’re using TailwindCSS

At the top of your project’s app.css or equivalent:

@import 'slender-ui/app.css' layer(components);
...

And in tailwind.config.cjs:

module.exports = {
  content: [..., require('slender-ui/purge')],
  ...
}

If you’re not using TailwindCSS

At the top of your +layout.svelte:

<script>
  import 'slender-ui/app.css';
  ...

Theming

You can theme the components to your heart’s delight with regular CSS or, like us heathens, use TailwindCSS.

CSS

We use CSS variables.

<Button label="Ain't nobody" />

<style>
  :root {
    --slender-color-neutral: 126, 34, 206; /* Global defaults are defined as RGB triples */
    --slender-border-radius: 2px;
  }
</style>

Getting specific

Each component uses slender- + its lowercase name as a class that you can target directly (e.g. the <Button /> will have a .slender-button class), but we recommend using custom properties (‘CSS variables’) instead:

<Button label="Ain't nobody" />

<style>
  :root {
    --slender-button-primary-bg-color: 190, 18, 60;
    --slender-button-primary-color: 255, 255, 255;
    --slender-button-border-radius: 9001px;
    --slender-button-md-padding-horizontal: 5rem;
  }
</style>

Variable names follow a --slender-{component}-{?type}-{property} format, such as --slender-button-background-color.

You can find the full list of CSS variables here.

TailwindCSS

The above is ideal for a consistent and maintainable design system, but on the odd occasion it’s useful to pass in class names directly:

<Button class="bg-cyan-300 text-cyan-900 hover:bg-cyan-400 rounded-none">
  Loves me better
</Button>

Components

Button

The design workhorse of Making Shit Happen. But slender.

<script>
  import { Button } from 'slender-ui'
</script>

<Button
  label="Makes me happy"
  variant="solid"
  color="primary"
  on:click={() => alert('Makes me feel this way!')}
  iconRight="feather.calendar"
/>

Color: primary

Variant: solid

Icon

    Drawer

    For all the things your designer forgot about.

    <script>
      import { Drawer } from 'slender-ui'
    </script>
    
    <Drawer.Button
      label="Slide on out"
      for="slender-drawer"
      color="primary"
    />
    
    <Drawer.Body position="left" class="px-4" id="slender-drawer">
      <h2>Well hello!</h2>
      <p>This is a drawer, what did you expect.</p>
    
      <Drawer.CloseButton variant="soft" label="Close me!" />
    </Drawer.Body>

    Well hello!

    This is a drawer, what did you expect.

    Icon

    Isn’t it iconic?

    All the icons are pre-generated SVGs and use an <img> tag to serve them, which can be slow in development but is performant in production and dramatically saves bundle size for your users by only loading the necessary icons.

    <script>
      import { Icon } from 'slender-ui'
    </script>
    
    <Icon name="feather.calendar" class="h-14 text-pink-500" />

    Many thanks to Feather, Heroicons and Ionicons.

    Menu (Dropdown)

    For all the extra options you need to implement after the project is finished.

    <script>
      import { Menu, Modal } from 'slender-ui'
    </script>
    
    <Menu.Button label="Hello" iconRight="feather.chevron-down" color="primary" for="slender-menu" />
    
    <Menu.Body color="primary" id="slender-menu">
      <Menu.Item iconLeft="feather.calendar">An icon</Menu.Item>
      <Menu.Item href="https://pointerpointer.com" target="_blank">This is a link</Menu.Item>
      <Menu.Item openButton for="menu-modal">A modal</Menu.Item>
    
      <Modal.Body id="menu-modal">
        <p>Hello :)</p>
    
        <Modal.CloseButton variant="soft" class="w-full">Close</Modal.CloseButton>
      </Modal.Body>
    </Menu.Body>

    Color: primary

    Variant: solid

    Modal

    Ye olde modal, for the “I’m not sure this needs a new page but I don’t know where to put it on the current page.”

    <script>
      import { Modal } from 'slender-ui'
    </script>
    
    <Modal.Button label="Ain't nobody" color="primary" for="slender-modal-123" />
    
    <Modal.Body id="slender-modal-123">
      <h3>Hello.</h3>
      <p>Got em.</p>
    
      <Modal.CloseButton label="Close" variant="soft" class="mt-4 w-full shadow-sm" />
    </Modal.Body>

    Navigation Progress

    <!-- +layout.svelte -->
    <script>
      import { NavigationProgress } from 'slender-ui'
    </script>
    
    <NavigationProgress />

    Notifications

    A toast, a flash, a notification… you get the gist.

    Let’s toast, let’s roast, then flash it away. It’s a notification, go hover and it’s here to stay.

    Probably my favourite of the components, as these make an SSR’d, no-JS site real slick.

    <!-- __layout.svelte -->
    <script>
      import { Notifications } from 'slender-ui'
    </script>
    
    <Notifications>
      <slot />
    </Notifications>

    You can either pass in an array of notifications to the component (great for JS-less SSR)…

    <script>
      ...
      const notifications = [
        {
          title: 'Well hello',
          color: 'success'
        }
      ]
    </script>
    
    <Notifications {notifications}>
      <slot />
    </Notifications>

    …or use the exported notify function from anywhere in the component subtree:

    <!-- SomeComponent.svelte -->
    <script>
      import { notify } from 'slender-ui'
    
      notify({ title: "One, two, three o'clock, four o'clock, rock" })
    </script>

    Although this works without JavaScript, ironically you’ll need it enabled for this demo.

    These use some tasty CSS magic to do away with the JS. Having said that, you need to enable it for the demo:

    Tabs

    Loves me better.
    Makes me feel this way.
    <script>
      import { Tabs } from 'slender-ui'
    </script>
    
    <Tabs.Group>
      <div class="flex space-x-2">
        <Tabs.Tab label="Anglo" color="primary" />
        <Tabs.Tab label="Saxon" color="primary" />
      </div>
    
      <div class="px-2 py-1 bg-white rounded shadow mt-2 w-full">
        <Tabs.Panel open>
          Loves me better.
        </Tabs.Panel>
    
        <Tabs.Panel>
          Makes me feel this way.
        </Tabs.Panel>
      </div>
    </Tabs.Group>

    Transition

    Alas, JS only.

    <script>
      import { Transition, Button } from 'slender-ui'
    
      let key = 0
    </script>
    
    <Transition
      {key}
      to={{ x: -50, rotate: -180, duration: 400 }}
      from={{ x: 100, scale: 1.5, rotate: 90, delay: 400 }}
    >
      <Button iconLeft="feather.refresh-ccw" label="Transition me!" on:mousedown={() => key++} />
    </Transition>

    Forms

    Checkbox

    <script>
      import { Checkbox } from 'slender-ui'
    </script>
    
    <Checkbox class="h-10 w-10" checked color="primary" />

    Color: primary

    Variant: solid

    Combobox (Autocomplete)

    For the fancy one you’ll need JavaScript, otherwise it falls back to a <datalist>.

      <script>
        import { Combobox } from 'slender-ui'
      
        const options = [
          'Birch',
          'Apple',
          'Beech',
          'Alder',
          'Hawthorn',
          'Hazel',
          'Holly',
          'Rowan',
          'Willow'
        ]
      </script>
      
      <Combobox {options} placeholder="Search for a tree..." color="primary" />

      Incrementer

      You’re in then you’re out, you’re up then you’re down, you’re wrong when it’s right.

      Yup, still no JS.

      10
      9
      8
      7
      6
      5
      4
      3
      2
      1
      0
      <script>
        import { Incrementer } from 'slender-ui'
      </script>
      
      <Incrementer color="primary" min={0} max={10} />

      Input

        <script>
          import { Input } from 'slender-ui'
        </script>
        
        <Input
          floatingLabel
          label="Enter your name"
          placeholder="Hello this is a placeholder"
        />

        Listbox (Select)

        For the fancy one you’ll need JavaScript, otherwise it falls back to a <select>.

        <script>
          import { Listbox } from 'slender-ui'
        
          const options = [
            'Birch',
            'Apple',
            'Beech',
            'Alder',
            'Hawthorn',
            'Hazel',
            'Holly',
            'Rowan',
            'Willow'
          ]
        </script>
        
        <Listbox {options} class="w-44" color="primary" />

        Color: primary

        Variant: solid

        Radio

        <script>
          import { Radio } from 'slender-ui'
        </script>
        
        <Radio class="h-10 w-10" name="gaga" color="primary" />
        <Radio class="h-10 w-10" name="gaga" color="primary" />

        Color: primary

        Variant: solid

        TextArea

        Enter your thoughts
          <script>
            import { TextArea } from 'slender-ui'
          </script>
          
          <TextArea
            floatingLabel
            label="Enter your thoughts"
          />

          Toggle

          A switch, a toggle, the mind does it boggle; this is no fox but a fancy checkbox!

          <script>
            import { Toggle } from 'slender-ui'
          </script>
          
          <Toggle size="large" color="primary" />

          Color: primary

          Variant: solid