SL

Matte Follow

Buttons

About

A plus becomes a check when you follow. Includes White, Black and Blue palettes and an interactive demo.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Matte Follow to your project and fits it to your theme.

Code

How Matte Follow is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import {useState as useTransferState,useEffect as useTransferEffect,useRef as useTransferRef} from 'react';
'use client';
import {useState} from 'react';
import Component from '@/components/ui/matte-follow';
// Demo only. Replace handlers with your real application operations.
function wait(signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(new DOMException('Canceled','AbortError'));return;}const timer=setTimeout(()=>{signal?.removeEventListener('abort',abort);resolve();},700);function abort(){clearTimeout(timer);reject(new DOMException('Canceled','AbortError'));}signal?.addEventListener('abort',abort,{once:true});});}

const defaults={palette:"white",presentation:"follow"};
const presentations={"follow":"Original","follow-text":"Text","follow-icon":"Icon"};
export default function Demo(props={}){
 const [transferPalette,setTransferPalette]=useTransferState(defaults.palette),[transferPresentation,setTransferPresentation]=useTransferState(defaults.presentation);
 useTransferEffect(()=>{if(['white','black','blue'].includes(props.palette))setTransferPalette(props.palette);if(Object.hasOwn(presentations,props.presentation))setTransferPresentation(props.presentation);},[props.palette,props.presentation]);
 const s={...defaults,...props,palette:transferPalette,presentation:transferPresentation};
 const [message,setMessage]=useState("Local demonstration. No server data is changed.");
 return <div style={{width:"100%",minHeight:360,padding:"clamp(12px, 4vw, 32px)",boxSizing:"border-box",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:24,color:"var(--color-foreground)"}}><DemoSurface palette={s.palette} presentation={s.presentation} callbacks={{}} onError={error=>setMessage(error.message)}/><p role="status" style={{margin:0,maxWidth:380,textAlign:"center",fontSize:12,lineHeight:1.5,color:"var(--color-muted-foreground)",width:"100%",minHeight:"3lh",overflowWrap:"anywhere"}}>{message}</p><div aria-label="Demo options" style={{display:'flex',alignItems:'center',justifyContent:'center',gap:16,flexWrap:'wrap'}}><div role="group" aria-label="Palette" style={{display:'flex',gap:8}}>{[['white','#fafbfd'],['black','#292d33'],['blue','#55749a']].map(([value,color])=><button key={value} type="button" aria-label={value[0].toUpperCase()+value.slice(1)+' palette'} aria-pressed={s.palette===value} onClick={()=>setTransferPalette(value)} style={{width:28,height:28,padding:3,borderRadius:'50%',border:s.palette===value?'2px solid var(--color-primary)':'1px solid var(--color-border)',background:color,backgroundClip:'content-box',cursor:'pointer'}}/>)}</div>{Object.keys(presentations).length>1&&<select aria-label="Presentation" value={s.presentation} onChange={e=>setTransferPresentation(e.target.value)} style={{font:'inherit',fontSize:12,padding:'8px 12px',borderRadius:9,border:'1px solid var(--color-border)',background:'var(--color-background)',color:'var(--color-foreground)'}}>{Object.entries(presentations).map(([value,label])=><option key={value} value={value}>{label}</option>)}</select>}</div></div>;
}

function DemoSurface(props){
 const surface=useTransferRef(null);
 function onReady(controller){
  const root=surface.current?.querySelector('[data-sl-react] > div:first-child')?.shadowRoot;
  if(root&&!root.querySelector('[data-sl-demo-layout]')){
   const css=document.createElement('style');
   css.setAttribute('data-sl-demo-layout','');
   css.textContent=':host{display:block!important;min-inline-size:0} :host > button{margin-inline:auto} :host > p[role="status"]{margin:10px 0 0;min-block-size:4lh;text-align:center;overflow-wrap:anywhere}';
   root.append(css);
  }
  props.onReady?.(controller);
 }
 return <div ref={surface} data-sl-demo-surface style={{width:'min(100%, 400px)',minHeight:144,padding:'24px clamp(12px, 4vw, 24px) 12px',borderRadius:16,background:'#f7f9fb',color:'#364152',boxSizing:'border-box'}}><Component {...props} onReady={onReady}/></div>;
}

More by SL

View all
  • Playback Scrubber

    Playback Scrubber

    58
  • Matte Alert Feedback

    Matte Alert Feedback

    44
  • Notification Stack

    Notification Stack

    33
  • Time Window Slider

    Time Window Slider

    26
  • Matte Meter Slider

    Matte Meter Slider

    17
  • Commands Menu

    Commands Menu

    15
  • Matte Opacity Slider

    Matte Opacity Slider

    13
  • Undo Feedback

    Undo Feedback

    9

More in Buttons

View all
  • Texture Button

    Texture Button

    104
  • Button

    Button

    12
  • Native Button

    Native Button

    27
  • Button

    Button

    19
  • Button

    Button

    78
  • HeroUI Button Group

    HeroUI Button Group

    5
  • Button

    Button

    1
  • App Store Button

    App Store Button

    476