diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue
index 8b8cbabf..f15bca45 100644
--- a/components/publish/PublishWidget.vue
+++ b/components/publish/PublishWidget.vue
@@ -63,14 +63,25 @@ const { editor } = useTiptap({
   onPaste: handlePaste,
 })
 
+function trimPollOptions() {
+  const indexLastNonEmpty = draft.params.poll!.options.findLastIndex(option => option.trim().length > 0)
+  const trimmedOptions = draft.params.poll!.options.slice(0, indexLastNonEmpty + 1)
+
+  if (currentInstance.value?.configuration
+      && trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
+    draft.params.poll!.options = trimmedOptions
+  else
+    draft.params.poll!.options = [...trimmedOptions, '']
+}
+
 function editPollOptionDraft(event: Event, index: number) {
   draft.params.poll!.options[index] = (event.target as HTMLInputElement).value
-  const indexLastNonEmpty = draft.params.poll!.options.findLastIndex(option => option.trim().length > 0)
-  draft.params.poll!.options = [...draft.params.poll!.options.slice(0, indexLastNonEmpty + 1), '']
+  trimPollOptions()
 }
 
 function deletePollOption(index: number) {
   draft.params.poll!.options.splice(index, 1)
+  trimPollOptions()
 }
 
 const expiresInOptions = [
@@ -326,18 +337,26 @@ onDeactivated(() => {
               border="~ base" flex-1 h10 pe-4 rounded-2 w-full flex="~ row"
               items-center relative focus-within:box-shadow-outline gap-3
               px-4 py-2
-              :placeholder="$t('polls.option_placeholder')"
+              :placeholder="$t('polls.option_placeholder', { current: index + 1, max: currentInstance?.configuration?.polls.maxOptions })"
+              class="option-input"
               @input="editPollOptionDraft($event, index)"
             >
-            <CommonTooltip placement="top" :content="$t('polls.remove_option')">
+            <CommonTooltip placement="top" :content="$t('polls.remove_option')" class="delete-button">
               <button
                 btn-action-icon class="hover:bg-red/75"
-                :disabled="index === draft.params.poll!.options.length - 1"
+                :disabled="index === draft.params.poll!.options.length - 1 && (index + 1 !== currentInstance?.configuration?.polls.maxOptions || draft.params.poll!.options[index].length === 0)"
                 @click.prevent="deletePollOption(index)"
               >
                 <div i-ri:delete-bin-line />
               </button>
             </CommonTooltip>
+            <span
+              v-if="currentInstance?.configuration?.polls.maxCharactersPerOption"
+              class="char-limit-radial"
+              aspect-ratio-1
+              h-10
+              :style="{ background: `radial-gradient(closest-side, rgba(var(--rgb-bg-base)) 79%, transparent 80% 100%), conic-gradient(${draft.params.poll!.options[index].length / currentInstance?.configuration?.polls.maxCharactersPerOption > 1 ? 'var(--c-danger)' : 'var(--c-primary)'} ${draft.params.poll!.options[index].length / currentInstance?.configuration?.polls.maxCharactersPerOption * 100}%, var(--c-primary-fade) 0)` }"
+            >{{ draft.params.poll!.options[index].length }}</span>
           </div>
         </form>
         <div
@@ -488,4 +507,18 @@ onDeactivated(() => {
     background-color: var(--c-bg-btn-disabled);
     color: var(--c-text-btn-disabled);
   }
+  .option-input:focus + .delete-button {
+    display: none;
+  }
+
+  .option-input:not(:focus) + .delete-button + .char-limit-radial {
+    display: none;
+  }
+
+  .char-limit-radial {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: 50%;
+  }
 </style>
diff --git a/composables/masto/publish.ts b/composables/masto/publish.ts
index 4430bd62..312ed2a2 100644
--- a/composables/masto/publish.ts
+++ b/composables/masto/publish.ts
@@ -40,9 +40,16 @@ export function usePublish(options: {
           || (draft.attachments.length === 0 && !draft.params.status)
           || failedMessages.length > 0
           || (draft.attachments.length > 0 && draft.params.poll !== null && draft.params.poll !== undefined)
-          || (draft.params.poll !== null && draft.params.poll !== undefined && draft.params.poll.options.length <= 1)
-          || (draft.params.poll !== null && draft.params.poll !== undefined && ![-1, draft.params.poll.options.length - 1].includes(draft.params.poll.options.findIndex(option => option.trim().length === 0)))
-          || (draft.params.poll !== null && draft.params.poll !== undefined && new Set(draft.params.poll.options).size !== draft.params.poll.options.length)
+          || ((draft.params.poll !== null && draft.params.poll !== undefined)
+              && (
+                draft.params.poll.options.length <= 1
+                || (![-1, draft.params.poll.options.length - 1].includes(draft.params.poll.options.findIndex(option => option.trim().length === 0)))
+                || (new Set(draft.params.poll.options).size !== draft.params.poll.options.length)
+                || (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
+                    && draft.params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
+                )
+              )
+          )
   })
 
   watch(() => draft, () => {
diff --git a/locales/en.json b/locales/en.json
index 380c756e..ea3af38e 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -314,7 +314,7 @@
     "disallow_multiple": "Disallow multiple choice",
     "expiration": "Poll expiration",
     "hide_votes": "Hide vote totals until the end",
-    "option_placeholder": "Poll choice",
+    "option_placeholder": "Poll choice {current}/{max}",
     "remove_option": "Remove choice",
     "settings": "Poll options",
     "show_votes": "Always show vote totals"