Slate Standard Save Button 
Adding a stadard save button to edit components.
Basic Save button 
While we're saying save button, this can be adapted for cancel buttons and other buttons in the footer of the edit layout.
Below we have an example of adding a button into our SlateModelEdit footer.
Please note, this is a partial implementation.
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n({ 'useScope': 'global' });
// This grabs the reference to the actual component.
const modelForm = useTemplateRef('model-form');
// This function checks against the form refrence, and it's exported
// completion status. Some components might have other properties 
// in defineExpose({})
const is_complete = computed(() => {
  
  // Check the exposed form's exported property.
  if (!modelForm.value.is_complete) return false;
  
  // Ad Extra completion checks here.
  // If nothing causees us to bail, we're good to go.
  return true;
});
</script>
<template>
  <SlateModelEdit
    ref="model-form"
    :id="id"
    :model="object">
    ...SNIP...
    <template #right-footer>
      <button
        @click="save()"
        :disabled="saving || !is_complete"
        :class="{ 'primary': isDirty && is_complete && !error, 'danger': error }">
        <SlateIcon icon="floppy-disk" />
        <span v-if="!saving">{{ t('MISC.save') }}</span>
        <span v-if="saving">{{ t('MISC.saving') }}...</span>
      </button>
    </template>
  </SlateModelEdit>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Things to note:
- Button is disabled when saving is in progress.
- Button is disabled when form is not complete.
- Dynamic class changes color of button to primary when enabled.
- Dynamic class changes color of button to red when there's an error.
- Icon is a floppy disk. Helps quick recognition.
- Translated text when saving is in progress.
Save function requirements. 
import { displayToast } from '@/slate_ui/SlateToast';
import { useRouter } from 'vue-router';
const router = useRouter();
function save(): void {
  // We ensure global environment is valid.
  // If we're already saving, bail. 
  if (saving.value) return;
  
  // If the templateRef for the for is reporting it's incomplete, bail.
  if (!is_complete.value) return;
  // Set our environment, so subsequent calls fail if they make it past
  // the button being disabled.  Clear the error value as well.
  saving.value = true;
  error.value = false;
  // This is the sync form for promises. The error handling in this form is
  // a bit easier to deal with than try {} catch(e) {}.
  object.saveToServer().then(
    () => {
        // On Success
        // Give the user feedback on the save.
        displayToast({
          'message': `${object.name} saved successfully`,
          'icon': 'floppy-disk',
          'theme': 'success',
        });
        // Clear the global environment so we can save again.
        saving.value = false;
        // Optional: Have the router move to a new path with the id from save.
        // This might need to be behind a conditional checking if the
        // id changed.
        router.push({ 
          'name': 'object-details', 
          'params': { 'id': object.id } }
        );
      });
    },
    (e) => {
      // On Error
      // Provide feedback to the user. Some users miss the save button 
      // going red.
      displayToast({
        'message': `Error: Unable to save request. ${e}`,
        'icon': 'triangle-exclamation',
        'theme': 'danger',
      });
      
      // Set our environment to an error state. Make sure to clear saving so
      // that we can fix issues and click save again.
      saving.value = false;
      error.value = true;
    },
  );
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Things to note here
- Double check that we're not alrady saving.
- On error we clear saving, so that we can fix the issue and click save again.
Adding Keyboard Shortcuts 
It's not much, but we can save users time with keyboard shortcuts. While we generally don't want to override the browser's keyboard shortcuts, the save function is a good place to go.
We use cmd-k for find, instead of cmd-f, because there it's a good chance someone wants to use the typical text search. For cmd-s though, it's highly unlikely the user wants to save the current view.
First we add an event listener capturing the key code and trigger the save function for the component. For this, we will leverage vueUse.
<script setup lang="ts">
import { debounce } from 'lodash-es';
import { useEventListener } from '@vueuse/core'
// This could be global, but is convienent here.
const isMac = /(Mac|iPhone|iPad)/i.test(navigator.platform);
// We are creating a debouncing function to try and prevent users from 
// spaming the save shortcut. This will trigger the actual save function
// no more than every 300ms, which should be enough time for the checks
// in the save() function to prevent double saves.
const saveKeyboardHandler = debounce(() => {
    save();
}, 300);
// Next we add a document listener on the document to catch the cmd-s no matter
// where we are focused. We use keydown instead of keyup to avoid user perceived
// delays in actions.
//
// The VueUse function also setups onMounted and more important beforeUnmounted
// functions. So we're not trying to save this page after we've navigated away.
useEventListener(document, 'keydown', ($event: KeyboardEvent) => {
  // Check the specific key that was pressed and if it's part of the keycombo.
  //
  // On macs, we want the metaKey, which is command. Any other we want ctrl.
  if($event.code === "KeyS" && (isMac ? $event.metaKey : $event.ctrlKey )) {
      // This stops the browser from trying to save the loaded page to the hd.
      $event.preventDefault();
    
      // Call our debounced function.
      saveKeyboardHandler();
  }
});
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Lets add an indicator to our save button. We'll add a new span at the bottom of the button's contents. We switch the combo key based on isMac.
...SNIP...
<button
    @click="save()"
    :disabled="saving || !is_complete"
    :class="{ 'primary': isDirty && is_complete && !error, 'danger': error }">
    <SlateIcon icon="floppy-disk" />
    <span v-if="!saving">{{ t('MISC.save') }} </span>
    <span v-if="saving">{{ t('MISC.saving') }}...</span>
    <span>( <kbd v-if="isMac">⌘S</kbd><kbd v-if="!isMac">Ctrl + S</kbd> )</span>
</button>
...SNIP...2
3
4
5
6
7
8
9
10
11