Hoppa till innehållet

Skins ​

En skin är ett utseende för redigerarens gränssnitt: verktygsfältet, knapparna och grupperna i det, bubbelmenyerna, rullgardinsmenyerna, dialogerna, ramen runt innehållet och bildmarkeringens gränssnitt. Propen skin väljer den:

vue
<Tiptapify skin="pastel" />

Ett skin består av två delar: tokens (färger och storlekar som CSS-variabler) och form (strukturella varianter som en token inte kan uttrycka). De två inbyggda skinnen är default — det klassiska utseendet, ett upphöjt verktygsfält med upphöjda knappgrupper — och pastel, det lugna utseendet. Egna skin är likvärdiga: registerSkin() och inline-skinobjekt löses upp genom exakt samma mekanism, och de inbyggda skinnen är registrerade skin, inte specialfall.

Terminologi: skin kontra Vuetify-tema ​

  • skin — den användarvända termen för ett utseende på redigerarens gränssnitt: tokens + form. Propen är skin, typerna är TiptapifySkin*, registret är registerSkin(), rotklassen är tiptapify-skin-{name}.
  • tema — reserverat för Vuetifys ljusa/mörka teman. Tiptapify-komponenter låter sina theme-props matas från Vuetifys aktuella temanamn; skinsystemet rör dem aldrig.

De samverkar: de flesta inbyggda tokenfärger refererar till Vuetify-temavariabler (rgb(var(--v-theme-surface)), …), så skinsen följer ljust/mörkt automatiskt. De fast mörka cellerna i den klassiska kolumnen — storleksändringshandtaget (rgba(0, 0, 0, .5/.8)), den enkla bildkonturen, bubbelskuggan och menyernas/dialogernas elevationsskuggor — är medvetna: det klassiska utseendet målar neutralt mörkt gränssnitt i båda teman, och den uppmätta baslinjen fastlåser dem. En egen skin kan fritt referera till temavariabler — se tokentabellen.

Koncept ​

Tokens ​

En token är en CSS custom property med namnet --tf-*. Katalognyckeln är camelCase-formen av CSS-suffixet: toolbarBg → --tf-toolbar-bg, dialogTitleSize → --tf-dialog-title-size. Värdena är vanliga strängar — färger, fullständiga border-förkortningar, skuggor, utfyllnader, radier.

Varje skinregel i biblioteket finns en gång och läser sin token med en reserv som är lika med det klassiska (bas)värdet:

css
.tiptapify-toolbar-group {
  border-radius: var(--tf-group-radius, 4px);
}

En upplöst skin skickar bara ut sina åsidosättningar som variabler; CSS-reserverna bär basen. Variablerna hamnar på redigerarens rot — och, eftersom menyer och dialoger teleporteras ut från den, på varje menys SkinScope-omslag och på själva dialogkortet, så att tokens följer varje yta.

Form ​

Sex flaggor avgör vilka regeluppsättningar som gäller; tokens målar dem:

flaggavärdenvad den växlar
toolbarraised · flatVToolbar-elevation + ingen inre hårlinje kontra ett platt, tonat fält
groupsbox · flatverktygsfältssektioner som upphöjda VBtnGroup-lådor (markup från 0.3.2: sammanfogade knappar, grupptokens målar lådan) kontra vanliga flex-div-rader
buttonsvuetify · quietinbyggda Vuetify-knappar kontra spökknappar med ett neutralt nedtryckt läge
bubblecard · flatkvar av kompatibilitetsskäl: bubbelmenyerna är en tokenmålad rad med ikonknappar i båda formerna (deras knappar följer buttons)
dialogclassic · softavdelare under titeln, upphöjd stängning kontra mjukt kort, textstängning
menuvuetify · softrullgardinsmenyer: Vuetify-lista kontra det ljusa kortet med utfyllnad

Den upplösta formen kodas på redigerarens rot som sex klasser .tiptapify-shape-{flag}-{value}, och exponeras som shape: ComputedRef<ResolvedShape> på useTiptapifyConfig() för anpassade verktygsfältskomponenter.

Extends ​

En skin löses upp över den klassiska basen, därefter dess extends-kedja med roten först, och sist dess egna medlemmar:

classic (base tokens + classic shape) → extends chain → own colors/metrics/flags

Saknade länkar löses upp till den ackumulerade kedjan (med en dev-varning); en cykel löses upp till classic (med en dev-varning). En registrerad skin refereras med namn; ett inline-skinobjekt kan extends ett registrerat namn och hamnar aldrig i det globala registret — egna skins läcker aldrig mellan redigerare. Okända tokennycklar ignoreras med en dev-varning.

Liveexempel på alla tre formerna finns på sidan med Skins-exempel.

Tokenkatalog ​

Tabellerna nedan är v1-kontraktet: katalognycklarna (TiptapifySkinColors / TiptapifySkinMetrics) är de namn du kan sätta. Klassiska celler är CSS-reserverna — värdet som basreglerna målar när ingen skin åsidosätter token.

Verktygsfält / fält ​

tokenCSS-variabelklassiskpastel
toolbarBg--tf-toolbar-bgrgb(var(--v-theme-surface))rgba(var(--v-theme-on-surface), 0.03)
toolbarBorderBottom--tf-toolbar-border-bottomnone1px solid rgba(var(--v-border-color), var(--v-border-opacity))
toolbarFg--tf-toolbar-fginheritrgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity))

Objekt / grupper / avdelare ​

tokenCSS-variabelklassiskpastel
itemsPadding--tf-items-padding12px 16px6px 8px
itemsGap--tf-items-gapnormal ¹6px
itemsRowGap--tf-items-row-gap5px4px
groupGap--tf-group-gap0px2px
groupHeight--tf-group-height32pxauto
groupBg--tf-group-bgrgb(var(--v-theme-surface))transparent
groupRadius--tf-group-radius4px0px
groupShadow--tf-group-shadowVuetify-elevation 2 ²none
dividerWidth--tf-divider-width0px (avståndshållare: marginal 0 4px)1px
dividerHeight--tf-divider-height0px20px
dividerColor--tf-divider-colortransparentrgba(var(--v-border-color), var(--v-border-opacity))

¹ Den uppmätta klassiska cellen är normal — Vuetifys inbyggda flex-värde, vilket är det CSS-reserven bär. Katalogens basvärde 0px är den motsvarande uttryckliga längden för skinåsidosättningar.

² Celler som lyder "Vuetify-elevation n" är beskrivande: den exakta trippelskuggsträngen är det uppmätta värdet i den incheckade baslinjefixturen (src/skins/__fixtures__/skin-baseline.json) och är CSS-reserven. För groupShadow är det elevation 2 (0 3px 1px -2px …, 0 2px 2px 0 …, 0 1px 5px 0 …) — under groups: 'box' är sektionen en VBtnGroup (elevation="4"), och tokenregeln målar lådan med den här trippeln över gruppens egen elevation: lådan äger elevationen, de sammanfogade barnknapparna har ingen.

Knappar ​

Dessa tokens används bara under buttons: 'quiet' — se Nåbarhet.

tokenCSS-variabelquiet-värde (classic: inbyggd Vuetify)
buttonFg--tf-button-fgrgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))
buttonHoverBg--tf-button-hover-bgrgba(var(--v-theme-on-surface), 0.07)
buttonHoverFg--tf-button-hover-fgrgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity))
buttonActiveBg--tf-button-active-bgrgba(var(--v-theme-on-surface), 0.1)
buttonActiveHoverBg--tf-button-active-hover-bgrgba(var(--v-theme-on-surface), 0.14)
buttonRadius--tf-button-radius6px

Bubbelmenyer (markering, tom rad, bild) ​

tokenCSS-variabelklassiskpastel
bubbleBg--tf-bubble-bgrgb(var(--v-theme-surface))samma
bubbleBorder--tf-bubble-bordernone1px solid rgba(var(--v-border-color), var(--v-border-opacity))
bubbleShadow--tf-bubble-shadow0 4px 14px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08)0 6px 20px rgba(0, 0, 0, 0.12)
bubbleRadius--tf-bubble-radius6px10px
bubblePadding--tf-bubble-padding4pxsamma
bubbleGap--tf-bubble-gap2pxsamma
tokenCSS-variabelklassiskpastel
menuListPadding--tf-menu-list-padding8px 0px4px
menuRadius--tf-menu-radius4px8px
menuBorder--tf-menu-bordernone1px solid rgba(var(--v-border-color), var(--v-border-opacity))
menuShadow--tf-menu-shadowVuetify-elevation 8 ²0 4px 16px rgba(0, 0, 0, 0.08)
menuItemRadius--tf-menu-item-radius0px6px
menuItemPadding--tf-menu-item-paddingVuetify ³4px 8px
menuItemMinHeight--tf-menu-item-min-heightVuetify ³34px
menuItemActiveBg--tf-menu-item-active-bgVuetify ³rgba(var(--v-theme-on-surface), 0.06)

³ Den klassiska menyns list-ruta (radie, skugga, listutfyllnad) är en skinregel idag — SkinScope-tokenomslaget sitter mellan Vuetifys egen barnregel och listan, så biblioteket återställer de pixlarna som skinregler som läser tokens ovan. Objekt-pixlarna förblir inbyggda Vuetify (fixturen mäter Vuetifys standardvärden där): de fyra objekttokens är overksamma medan menu: 'vuetify'.

Dialoger ​

tokenCSS-variabelklassiskpastel
dialogShadow--tf-dialog-shadowVCard-elevation ²0 20px 48px rgba(0, 0, 0, 0.16), 0 2px 6px rgba(0, 0, 0, 0.06)
dialogRadius--tf-dialog-radius4px (VCard)12px
dialogHeaderPadding--tf-dialog-header-padding8px 16px16px 12px 12px 20px
dialogContentPadding--tf-dialog-content-padding16px 24px 24px4px 20px
dialogActionsPadding--tf-dialog-actions-padding8px16px 20px 20px
dialogTitleSize--tf-dialog-title-size1.25rem1.125rem
dialogTitleWeight--tf-dialog-title-weight400600
dialogFieldRadius--tf-dialog-field-radiusstandardfält (4px)8px (outlined)
dialogActionBtnMinHeight--tf-dialog-action-btn-min-heightVuetify (auto)38px
dialogActionBtnRadius--tf-dialog-action-btn-radiusVuetify (4px)8px
dialogGridGap--tf-dialog-grid-gap24px16px

Ram (redigerarens kantomslag) ​

tokenCSS-variabelklassiskpastel
frameBorder--tf-frame-border1px solid rgba(var(--v-border-color), var(--v-border-opacity)), ingen överkantfull ram
frameBg--tf-frame-bgtransparentrgb(var(--v-theme-surface))
frameShadow--tf-frame-shadownone0 1px 2px rgba(0, 0, 0, 0.05)

Under den platta verktygsfältsformen målar ramen också ett delta per sida (en överkant och nederkanten med 1,6× kantopacitet). Det deltat hör till formen, inte till en token: det är inte nåbart via token.

Bilder och storleksändringshandtag ​

tokenCSS-variabelklassiskpastel
imageRadius--tf-image-radius0.125rem4px
imageBg--tf-image-bgtransparentrgba(var(--v-theme-on-surface), 0.04)
imageOutline--tf-image-outline1px solid rgba(0, 0, 0, 0.25) (markerad)2px solid rgb(var(--v-theme-primary))
imageOutlineHover--tf-image-outline-hovertransparentrgba(var(--v-theme-primary), 0.35)
handleBg--tf-handle-bgrgba(0, 0, 0, 0.5)rgb(var(--v-theme-primary))
handleBgHover--tf-handle-bg-hoverrgba(0, 0, 0, 0.8) (hovring)rgb(var(--v-theme-primary))
handleBorder--tf-handle-bordernone2px solid rgb(var(--v-theme-surface))
handleShadow--tf-handle-shadownone0 0 0 1px rgba(0, 0, 0, 0.15)
handleRadius--tf-handle-radius2px50%

Bildverktygsfältet följer flaggan buttons: under den lugna formen får det den lugna ytan — dess 2px/4px-avstånd läser --tf-bubble-gap / --tf-bubble-padding med bildmenyns reserver; det finns ingen dedikerad token för bildverktygsfältet.

Inte i katalogen (nödutgångsterritorium) ​

Vissa dimensioner som nämns i designöversikten har ingen katalogtoken i v1: verktygsfältets höjd, ramens bredd, ramens radie, knappens bredd, knappens utfyllnad, dialogens bredd. Katalogtabellerna ovan är kontraktet; för dessa pixlar öppna nödutgången och stilsätt de stabila klasserna direkt.

Nåbarhet ​

Kontraktet, angivet en gång och efterlevt av varje grupp ovan:

En token målar en yta bara när formen aktiverar den ytan; inbyggda Vuetify-visuella element stilsätts via Vuetifys temafärger.

Per grupp:

  • Verktygsfält, ram, bild-/handtagsgränssnitt — aktiva i varje form; tokens målar dem, de klassiska cellerna är det inbyggda utseende som tokens faller tillbaka på.
  • Grupplåda (groupBg, groupRadius, groupHeight, groupShadow) — under groups: 'box' är sektionen en VBtnGroup (struktur från 0.3.2: knapparna sammanfogas, lådan bär elevationen); lådreglerna målar den med grupptokens, och --tf-group-height håller 32px-kontraktet med en !important (Vuetifys densitetsregel kämpar emot, som i 0.3.2). Under groups: 'flat' är sektionerna vanliga div-rader (transparenta, utan skugga, radie 0 är formens bas).
  • Avdelarnas hårlinjer (dividerWidth/Height/Color) — den synliga hårlinjen är en finess för groups: 'flat'; i lådformen är avdelaren en avståndshållare utan storlek (margin: 0 4px från basregeln).
  • Objektens utfyllnad/avstånd — den kompakta raden 6px 8px är kopplad till det platta fältet (toolbar: 'flat'), gruppavstånden till de platta sektionerna (groups: 'flat'); det upphöjda fältet behåller de klassiska måtten.
  • Knappar (buttonFg, buttonHover*, buttonActive*, buttonRadius) — overksamma under buttons: 'vuetify': de inbyggda Vuetify-knapparna målar sina egna tillstånd. Stilsätt dem med Vuetifys temafärger (primary, surface, …) — de klassiska formerna ärver dem medvetet.
  • Menyobjektens mått (menuItemRadius/Padding/MinHeight/ActiveBg) — overksamma under menu: 'vuetify'; Vuetify äger de objektpixlarna. Menyns list-ruta (radie/skugga/utfyllnad) är skinmålad i båda formerna (se menyanteckningen).
  • Dialogens mjuka gränssnitt (dialogActionBtn*, dialogFieldRadius, de mjuka utfyllnaderna, den saknade avdelaren) — målas under dialog: 'soft'; under dialog: 'classic' är dialogen ett VCard med de klassiska utfyllnaderna.
  • Bubbelgränssnitt — markeringens, tomradens och bildens menyer är en rad med ikonknappar i båda formerna, målade av bubbeltokens; bubble ändrar inte längre deras markup.

En åsidosättning som är lika med katalogbasen skickas inte ut — under en icke-klassisk form målar formens egen bas via CSS-reserven, så att "återställa" ett klassiskt värde (t.ex. sätta buttonActiveBg tillbaka till rgb(var(--v-theme-primary)) under buttons: 'quiet') tyst inte gör något. För att tvinga fram ett klassiskt utseende under en icke-klassisk form, sätt en åsidosättning som skiljer sig från katalogbasen — en likvärdig literal som rgba(var(--v-theme-primary), 1) målar identiskt och överlever hoppet.

Tokens kopplas medvetet inte in i inbyggda vägar — det är vad som håller default pixelidentisk med det klassiska utseendet medan allt går via katalogen.

Recept ​

Utöka pastel ​

Ärv den lugna formen och måla om några ytor. Registrerade namn fungerar på vilken redigerare som helst:

ts
// main.ts — call once, before any editor mounts
import { registerSkin } from 'tiptapify'

registerSkin('mint', {
  extends: 'pastel',
  colors: {
    toolbarBg: 'rgba(0, 128, 0, 0.05)',   // mint tint over the pastel wash
    handleBg: 'rgb(0, 100, 0)',           // deep green handles
    handleBgHover: 'rgb(0, 128, 0)',
    imageOutline: '2px solid rgb(0, 100, 0)',
    imageOutlineHover: 'rgba(0, 100, 0, 0.35)',
  },
})
vue
<Tiptapify skin="mint" />

Formen (flat/quiet/soft) kommer från pastel; orörda tokens behåller pastels värden — den lugna knapptonen förblir neutral, bara det du åsidosätter flyttas. Det här är skinen som renderas live på exempelsidan.

Utöka default ​

Behåll den klassiska strukturen, justera färgerna:

ts
registerSkin('paper', {
  extends: 'default',
  colors: { toolbarBg: 'rgb(var(--v-theme-background))' },
  metrics: { groupRadius: '8px', itemsPadding: '10px 14px' },
})

extends: 'default' är den implicita roten, så nyckeln kan utelämnas: { toolbar: 'flat', … } utgår också från classic.

Helt egen ​

Ingen extends, ingen registrering: skicka objektet till propen skin och sätt formflaggor och tokens fritt. Ett name sätter din egen etikett på rotklassen (se nedan); utan ett är den tiptapify-skin-inline.

vue
<script setup lang="ts">
import { defineSkin } from 'tiptapify'

const slate = defineSkin({
  name: 'slate',
  toolbar: 'flat',
  groups: 'flat',
  buttons: 'quiet',
  bubble: 'flat',
  dialog: 'soft',
  menu: 'soft',
  colors: { toolbarBg: 'rgb(30, 41, 59)', toolbarFg: 'rgb(226, 232, 240)' },
  metrics: { buttonRadius: '8px' },
})
</script>

<template>
  <Tiptapify :skin="slate" />
</template>

defineSkin är en identitetshjälpare för typade const-definitioner (den hamnar inte i något register). Formflaggor kan sättas individuellt — varje flagga du utelämnar förblir classic.

Nödutgång: rotklassen ​

Varje redigerarrot har tiptapify-skin-{name} (tiptapify-skin-inline för ett namnlöst inline-objekt). Biblioteket kopplar aldrig någon regel till ett skinnamn — klassen är din:

css
/* Style the frame of one skin, dimensions the catalog does not cover */
.tiptapify-skin-mint .tiptapify-frame {
  max-width: 720px;
  margin-inline: auto;
}

De stabila krokarna att skriva mot:

klassvar
tiptapify-skin-{name}redigerarens rot
tiptapify-shape-{flag}-{value} ×6redigerarens rot — den upplösta formen
tiptapify-toolbar · tiptapify-toolbar-items · tiptapify-toolbar-group · menu-dividerverktygsfältet och dess sektioner
tiptapify-quiet-buttonsverktygsfälts- och bubbelrötter under buttons: 'quiet'
tiptapify-bubble-row · tiptapify-bubble-dividerraden i varje bubbelmeny och hårlinjen mellan dess grupper
tiptapify-bubble-menu · floating-menu · tiptapify-image-menuraderna för markeringens, tomradens och bildens meny
tiptapify-btn--activeen nedtryckt knapp i en bubbelmeny (en aktiv markering, bildens flytläge)
tiptapify-menu · tiptapify-menu--softinnehållet i rullgardinens överlägg, bäraren av de mjuka menyreglerna
tiptapify-skin-scopetoken-scope-diven inuti teleporterade menyer
tiptapify-dialog · tiptapify-dialog--softdialogkortet och dess mjuka bärare
tiptapify-frameredigerarens kantomslag

Försvar mot konsumentteman. Skinlagret är byggt för att överleva admin-teman som lägger utfyllnad på och målar om Vuetifys interna delar: reglerna kopplas till form-/bärarklasser, använder dubblerade klasser (.v-list.v-list, .v-btn.v-btn.v-btn) för specificitet och behåller !important bara där basincheckningens admin-temafall visade att det var nödvändigt (menu.css-försvarsuppsättningen). Konventioner som får dina egna åsidosättningar att fungera på samma slagfält:

  • begränsa din CSS under .tiptapify-skin-{name} (eller en formklass) — aldrig på bara Vuetify-interna delar;
  • föredra tokens (--tf-*) för katalogdimensionerna, klasser för resten;
  • matcha dubbleringen när du åsidosätter Vuetify-komponenter (en enda .v-list-item förlorar mot skinreglerna).

Visuellt kontrakt (anteckning för bidragsgivare) ​

De beräknade stilarna för de inbyggda skinsen fastlåses av baslinjefixturen src/skins/__fixtures__/skin-baseline.json (13 sektioner × ljust/mörkt): CSS-lagret är klart när den uppmätta matrisen stämmer med den. Läsanteckningar för fixturen:

  • Hover-/aktiva dimensioner kan inte tvingas fram från sidan, så inspelningen läser deklarationstexten i den första träffande CSSOM-regeln för varje hover-selektor och löser upp den via ett probe-element inuti skinroten (fältet resolved) — det är den vinnande regelns kontrakt, inte en live-ögonblicksbild av :hover.
  • _meta.selectors.menu registrerar selektortexten från före SkinScope. Fixturen är fryst (normativa värden), så fältet är medvetet föråldrat; inspelningspaketet fastlåser den aktuella selektorn.
  • Fixturen bär sina godkända ändringar i _meta.notes: återställningen 2026-10-18 av VBtnGroup-markupen under groups: 'box' mätte om de klassiska barnknappsfälten groupBtn/buttonsActive (sammanfogade hörn, lådan äger elevationen). Pastelfälten är orörda — en medveten visuell ändring uppdaterar bara de berörda klassiska cellerna, registrerar en anteckning och levererar en rad i CHANGELOG.

Se även ​

Utgiven under MIT-licensen.