Skip to content

Instantly share code, notes, and snippets.

@tijnjh
Last active June 13, 2026 10:41
Show Gist options
  • Select an option

  • Save tijnjh/c95b30b44076d7447ed4dcecaeec6605 to your computer and use it in GitHub Desktop.

Select an option

Save tijnjh/c95b30b44076d7447ed4dcecaeec6605 to your computer and use it in GitHub Desktop.
<script lang="ts">
import type { ClassValue } from "svelte/elements";
const {
label,
onclick,
class: className,
as = "div",
}: {
label: string;
onclick?: VoidFunction;
class?: ClassValue;
as?: keyof HTMLElementTagNameMap;
} = $props();
</script>
<svelte:element this={as} class={["relative inline-flex", className]}>
<span>
{label}
</span>
<span class="absolute inset-0 overflow-hidden rounded-full">
<input
type="checkbox"
aria-label={label}
// @ts-expect-error
switch
onchange={onclick}
/>
</span>
</svelte:element>
<style>
input {
position: absolute;
left: 50%;
top: 50%;
margin: 0;
opacity: 0;
cursor: pointer;
transform: translate(-50%, -50%) scale(6);
transform-origin: center;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
</style>
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
export function HapticTrigger<T extends ElementType = "div">({
as,
onClick,
children,
checked = false,
...props
}: {
as?: T;
onClick: (checked: boolean) => void;
checked?: boolean;
children?: ReactNode;
} & Omit<
ComponentPropsWithoutRef<T>,
"as" | "onClick" | "children" | "style"
>) {
const Tag = as ?? "div";
return (
<Tag
{...props}
style={{
position: "relative",
display: "inline-flex",
}}
>
{children}
<input
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
margin: 0,
opacity: 0,
cursor: "pointer",
clipPath: "inset(0 round 999px)",
WebkitTapHighlightColor: "transparent",
touchAction: "manipulation",
}}
type="checkbox"
checked={checked}
onChange={(e) => onClick(e.currentTarget.checked)}
// @ts-expect-error
switch=""
/>
</Tag>
);
}
<script setup lang="ts">
import type { ClassValue } from "vue";
const {
label,
class: className,
as = "div",
} = defineProps<{
label: string;
class?: ClassValue;
as?: keyof HTMLElementTagNameMap;
}>();
const emit = defineEmits<{
(e: "click"): void;
}>();
</script>
<template>
<component :is="as" class="relative inline-flex" :class="className">
<span>
{{ label }}
</span>
<span class="absolute inset-0 overflow-hidden rounded-full">
<input
type="checkbox"
:aria-label="label"
switch
@change="emit('click')"
/>
</span>
</component>
</template>
<style scoped>
input {
position: absolute;
left: 50%;
top: 50%;
margin: 0;
opacity: 0;
cursor: pointer;
transform: translate(-50%, -50%) scale(6);
transform-origin: center;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
</style>
@tijnjh

tijnjh commented Jun 9, 2026

Copy link
Copy Markdown
Author

inspired from https://github.com/m1ckc3s/project-fathom

a react implementation is there too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment